From 14f11f8d844b6859cefb8974362ad39cbd3fb623 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jul 2024 03:34:16 -0500 Subject: [PATCH 1/4] Added override to DaprClient that produces an invokable HttpClient much like the static method, but using the DaprApiToken already persisted (meaning this can be created from the injected DaprClient instance) Signed-off-by: Whit Waldo --- src/Dapr.AspNetCore/Dapr.AspNetCore.csproj | 1 + src/Dapr.Client/DaprClient.cs | 27 +++++++++- src/Dapr.Client/DaprClientGrpc.cs | 61 ++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj b/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj index 54996e4b..7261a4c8 100644 --- a/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj +++ b/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj @@ -3,6 +3,7 @@ This package contains the reference assemblies for developing services using Dapr and AspNetCore. + enable diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 4f89d866..5ee7b588 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -58,7 +58,7 @@ public abstract class DaprClient : IDisposable /// The client will read the property, and /// interpret the hostname as the destination app-id. The /// property will be replaced with a new URI with the authority section replaced by - /// 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. /// /// /// @@ -448,6 +448,31 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, stri /// A that will return the value when the operation has completed. public abstract Task InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default); +#nullable enable + /// + /// + /// Creates an that can be used to perform Dapr service invocation using + /// objects. + /// + /// + /// The client will read the property, and + /// interpret the hostname as the destination app-id. The + /// property will be replaced with a new URI with the authority section replaced by + /// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request. + /// + /// + /// + /// An optional app-id. If specified, the app-id will be configured as the value of + /// 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. + /// + /// The HTTP endpoint of the Dapr process to use for service invocation calls. + /// An that can be used to perform service invocation requests. + /// + /// + public abstract HttpClient CreateInvokableHttpClient(string? appId = null, string? daprEndpoint = null); +#nullable disable + /// /// Perform service invocation using the request provided by . If the response has a non-success /// status an exception will be thrown. diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index af245afc..0c66d8ed 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -450,6 +450,67 @@ public override async Task InvokeMethodWithResponseAsync(Ht } } + /// + /// + /// Creates an that can be used to perform Dapr service invocation using + /// objects. + /// + /// + /// The client will read the property, and + /// interpret the hostname as the destination app-id. The + /// property will be replaced with a new URI with the authority section replaced by + /// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request. + /// + /// + /// + /// An optional app-id. If specified, the app-id will be configured as the value of + /// 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. + /// + /// + /// An that can be used to perform service invocation requests. + /// + /// + #nullable enable + public override HttpClient CreateInvokableHttpClient(string? appId = null, string? daprEndpoint = 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 (daprEndpoint is not null) + { + //DaprEndpoint performs validation + handler.DaprEndpoint = daprEndpoint; + } + + 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)); From 45c8c6e39321d3117b9d71c51b079c8da28a920d Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jul 2024 03:47:25 -0500 Subject: [PATCH 2/4] Adds unit tests for implementation mirroring functionality of existing unit tests for static method Signed-off-by: Whit Waldo --- ...lientTest.CreateInvokableHttpClientTest.cs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs diff --git a/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs b/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs new file mode 100644 index 00000000..b064b23d --- /dev/null +++ b/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------ +// 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", daprEndpoint: "http://localhost:3500"); + Assert.Equal("http://bank/", client.BaseAddress.AbsoluteUri); + } + + [Fact] + public void CreateInvokableHttpClient_InvalidAppId_FromDaprClient() + { + var daprClient = new MockClient().DaprClient; + var ex = Assert.Throws(() => + { + // 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(ex.InnerException); + } + + [Fact] + public void CreateInvokableHttpClient_WithoutAppId_FromDaprClient() + { + var daprClient = new MockClient().DaprClient; + var client = daprClient.CreateInvokableHttpClient(daprEndpoint: "http://localhost:3500"); + Assert.Null(client.BaseAddress); + } + + [Fact] + public void CreateInvokableHttpClient_InvalidDaprEndpoint_InvalidFormat_FromDaprClient() + { + var daprClient = new MockClient().DaprClient; + Assert.Throws(() => + { + _ = daprClient.CreateInvokableHttpClient(daprEndpoint: ""); + }); + + // Exception message comes from the runtime, not validating it here. + } + + [Fact] + public void CreateInvokableHttpClient_InvalidDaprEndpoint_InvalidScheme_FromDaprClient() + { + var daprClient = new MockClient().DaprClient; + var ex = Assert.Throws(() => + { + _ = daprClient.CreateInvokableHttpClient(daprEndpoint: "ftp://localhost:3500"); + }); + + Assert.Contains("The URI scheme of the Dapr endpoint must be http or https.", ex.Message); + } + } +} From 14c2172c12c689eaa02b7aa96fa5780be6d6120a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jul 2024 04:03:07 -0500 Subject: [PATCH 3/4] Removed DaprEndpoint property as it should utilize that registered with the DaprClient instance. If a developer wants to opt-out, they should use the static invocation method instead. Updated unit tests to exclude invalid endpoints as that's handled by the DaprClient instead now. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 5 ++-- src/Dapr.Client/DaprClientGrpc.cs | 9 +++--- ...lientTest.CreateInvokableHttpClientTest.cs | 29 ++----------------- 3 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 5ee7b588..fb929e74 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -457,7 +457,7 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, stri /// /// The client will read the property, and /// interpret the hostname as the destination app-id. The - /// property will be replaced with a new URI with the authority section replaced by + /// 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. /// /// @@ -466,11 +466,10 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, stri /// 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. /// - /// The HTTP endpoint of the Dapr process to use for service invocation calls. /// An that can be used to perform service invocation requests. /// /// - public abstract HttpClient CreateInvokableHttpClient(string? appId = null, string? daprEndpoint = null); + public abstract HttpClient CreateInvokableHttpClient(string? appId = null); #nullable disable /// diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 0c66d8ed..548e8468 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -458,7 +458,7 @@ public override async Task InvokeMethodWithResponseAsync(Ht /// /// The client will read the property, and /// interpret the hostname as the destination app-id. The - /// property will be replaced with a new URI with the authority section replaced by + /// property will be replaced with a new URI with the authority section replaced by the instance's value /// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request. /// /// @@ -467,12 +467,11 @@ public override async Task InvokeMethodWithResponseAsync(Ht /// 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. /// - /// /// An that can be used to perform service invocation requests. /// /// #nullable enable - public override HttpClient CreateInvokableHttpClient(string? appId = null, string? daprEndpoint = null) + public override HttpClient CreateInvokableHttpClient(string? appId = null) { var handler = new InvocationHandler { @@ -486,10 +485,10 @@ public override HttpClient CreateInvokableHttpClient(string? appId = null, strin handler.DaprApiToken = this.apiTokenHeader.Value.Value; } - if (daprEndpoint is not null) + if (this.httpEndpoint is not null) { //DaprEndpoint performs validation - handler.DaprEndpoint = daprEndpoint; + handler.DaprEndpoint = this.httpEndpoint.AbsoluteUri; } var httpClient = new HttpClient(handler); diff --git a/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs b/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs index b064b23d..99fbd497 100644 --- a/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs +++ b/test/Dapr.Client.Test/DaprClientTest.CreateInvokableHttpClientTest.cs @@ -22,7 +22,7 @@ public partial class DaprClientTest public void CreateInvokableHttpClient_WithAppId_FromDaprClient() { var daprClient = new MockClient().DaprClient; - var client = daprClient.CreateInvokableHttpClient(appId: "bank", daprEndpoint: "http://localhost:3500"); + var client = daprClient.CreateInvokableHttpClient(appId: "bank"); Assert.Equal("http://bank/", client.BaseAddress.AbsoluteUri); } @@ -44,32 +44,9 @@ public void CreateInvokableHttpClient_InvalidAppId_FromDaprClient() public void CreateInvokableHttpClient_WithoutAppId_FromDaprClient() { var daprClient = new MockClient().DaprClient; - var client = daprClient.CreateInvokableHttpClient(daprEndpoint: "http://localhost:3500"); + + var client = daprClient.CreateInvokableHttpClient(); Assert.Null(client.BaseAddress); } - - [Fact] - public void CreateInvokableHttpClient_InvalidDaprEndpoint_InvalidFormat_FromDaprClient() - { - var daprClient = new MockClient().DaprClient; - Assert.Throws(() => - { - _ = daprClient.CreateInvokableHttpClient(daprEndpoint: ""); - }); - - // Exception message comes from the runtime, not validating it here. - } - - [Fact] - public void CreateInvokableHttpClient_InvalidDaprEndpoint_InvalidScheme_FromDaprClient() - { - var daprClient = new MockClient().DaprClient; - var ex = Assert.Throws(() => - { - _ = daprClient.CreateInvokableHttpClient(daprEndpoint: "ftp://localhost:3500"); - }); - - Assert.Contains("The URI scheme of the Dapr endpoint must be http or https.", ex.Message); - } } } From eee54c7b4d29c1969bd93b2627d237fe9a8bd66b Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 3 Sep 2024 18:38:49 -0500 Subject: [PATCH 4/4] Removed nullability annotation as it's not supposed to be there for this PR Signed-off-by: Whit Waldo --- src/Dapr.AspNetCore/Dapr.AspNetCore.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj b/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj index 7261a4c8..54996e4b 100644 --- a/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj +++ b/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj @@ -3,7 +3,6 @@ This package contains the reference assemblies for developing services using Dapr and AspNetCore. - enable