Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding instance-based CreateInvokableHttpClient #1319

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
26 changes: 25 additions & 1 deletion src/Dapr.Client/DaprClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public abstract class DaprClient : IDisposable
/// The client will read the <see cref="HttpRequestMessage.RequestUri" /> property, and
/// interpret the hostname as the destination <c>app-id</c>. The <see cref="HttpRequestMessage.RequestUri" />
/// property will be replaced with a new URI with the authority section replaced by <paramref name="daprEndpoint" />
/// and the path portion of the URI rewitten to follow the format of a Dapr service invocation request.
/// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request.
/// </para>
/// </summary>
/// <param name="appId">
Expand Down Expand Up @@ -448,6 +448,30 @@ public HttpRequestMessage CreateInvokeMethodRequest<TRequest>(string appId, stri
/// <returns>A <see cref="Task{T}" /> that will return the value when the operation has completed.</returns>
public abstract Task<HttpResponseMessage> InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);

#nullable enable
/// <summary>
/// <para>
/// Creates an <see cref="HttpClient"/> that can be used to perform Dapr service invocation using <see cref="HttpRequestMessage"/>
/// objects.
/// </para>
/// <para>
/// The client will read the <see cref="HttpRequestMessage.RequestUri" /> property, and
/// interpret the hostname as the destination <c>app-id</c>. The <see cref="HttpRequestMessage.RequestUri" />
/// property will be replaced with a new URI with the authority section replaced by the HTTP endpoint value
/// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request.
/// </para>
/// </summary>
/// <param name="appId">
/// An optional <c>app-id</c>. If specified, the <c>app-id</c> will be configured as the value of
/// <see cref="HttpClient.BaseAddress" /> so that relative URIs can be used. It is mandatory to set this parameter if your app-id contains at least one upper letter.
/// If some requests use absolute URL with an app-id which contains at least one upper letter, it will not work, the workaround is to create one HttpClient for each app-id with the app-ip parameter set.
/// </param>
/// <returns>An <see cref="HttpClient" /> that can be used to perform service invocation requests.</returns>
/// <remarks>
/// </remarks>
public abstract HttpClient CreateInvokableHttpClient(string? appId = null);
#nullable disable

/// <summary>
/// Perform service invocation using the request provided by <paramref name="request" />. If the response has a non-success
/// status an exception will be thrown.
Expand Down
60 changes: 60 additions & 0 deletions src/Dapr.Client/DaprClientGrpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,66 @@ public override async Task<HttpResponseMessage> InvokeMethodWithResponseAsync(Ht
}
}

/// <summary>
/// <para>
/// Creates an <see cref="HttpClient"/> that can be used to perform Dapr service invocation using <see cref="HttpRequestMessage"/>
/// objects.
/// </para>
/// <para>
/// The client will read the <see cref="HttpRequestMessage.RequestUri" /> property, and
/// interpret the hostname as the destination <c>app-id</c>. The <see cref="HttpRequestMessage.RequestUri" />
/// property will be replaced with a new URI with the authority section replaced by the instance's <see cref="httpEndpoint"/> value
/// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request.
/// </para>
/// </summary>
/// <param name="appId">
/// An optional <c>app-id</c>. If specified, the <c>app-id</c> will be configured as the value of
/// <see cref="HttpClient.BaseAddress" /> so that relative URIs can be used. It is mandatory to set this parameter if your app-id contains at least one upper letter.
/// If some requests use absolute URL with an app-id which contains at least one upper letter, it will not work, the workaround is to create one HttpClient for each app-id with the app-ip parameter set.
/// </param>
/// <returns>An <see cref="HttpClient" /> that can be used to perform service invocation requests.</returns>
/// <remarks>
/// </remarks>
#nullable enable
public override HttpClient CreateInvokableHttpClient(string? appId = null)
{
var handler = new InvocationHandler
{
InnerHandler = new HttpClientHandler(),
DefaultAppId = appId
};

//Apply the Dapr API token if it's set on the client
if (this.apiTokenHeader.HasValue)
{
handler.DaprApiToken = this.apiTokenHeader.Value.Value;
}

if (this.httpEndpoint is not null)
{
//DaprEndpoint performs validation
handler.DaprEndpoint = this.httpEndpoint.AbsoluteUri;
}

var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.UserAgent.Add(UserAgent());

if (appId is not null)
{
try
{
httpClient.BaseAddress = new Uri($"http://{appId}");
}
catch (UriFormatException inner)
{
throw new ArgumentException("The appId must be a valid hostname.", nameof(appId), inner);
}
}

return httpClient;
}
#nullable disable

public async override Task InvokeMethodAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNull(request, nameof(request));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System;
using Xunit;

namespace Dapr.Client
{
public partial class DaprClientTest
{
[Fact]
public void CreateInvokableHttpClient_WithAppId_FromDaprClient()
{
var daprClient = new MockClient().DaprClient;
var client = daprClient.CreateInvokableHttpClient(appId: "bank");
Assert.Equal("http://bank/", client.BaseAddress.AbsoluteUri);
}

[Fact]
public void CreateInvokableHttpClient_InvalidAppId_FromDaprClient()
{
var daprClient = new MockClient().DaprClient;
var ex = Assert.Throws<ArgumentException>(() =>
{
// The appId needs to be something that can be used as hostname in a URI.
_ = daprClient.CreateInvokableHttpClient(appId: "");
});

Assert.Contains("The appId must be a valid hostname.", ex.Message);
Assert.IsType<UriFormatException>(ex.InnerException);
}

[Fact]
public void CreateInvokableHttpClient_WithoutAppId_FromDaprClient()
{
var daprClient = new MockClient().DaprClient;

var client = daprClient.CreateInvokableHttpClient();
Assert.Null(client.BaseAddress);
}
}
}
Loading