api: accept canonical gRPC URI form on the gRPC endpoint - #1817
Conversation
The Dapr SDKs warn that http/https are deprecated as gRPC URI schemes — the prefix denotes a name resolver, not a transport. - Add `DaprDefaults.NormalizeGrpcEndpoint`. Returns http/https unchanged; rewrites `dns://host:port?tls=true` to `https://host:port` and `?tls=false` to `http://host:port` before the URI reaches `GrpcChannel.ForAddress` (which is HttpClient-backed and does not understand the dns scheme). Throws on `dns://` without a parseable `tls=true|false` and on any other scheme. - `DaprClientBuilder.Build` and `DaprGenericClientBuilder .BuildDaprClientDependencies` now normalize the gRPC endpoint up front and feed the normalized URI to both the http/https switch and `GrpcChannel.ForAddress`. The redundant scheme check on the gRPC path is gone; HTTP endpoint validation is unchanged. - `GetDefaultGrpcEndpoint` normalizes the env-var value before handing it to `BuildEndpoint` (otherwise `UriBuilder` would strip the `?tls=` query while reassembling). - `UseGrpcEndpoint` docstrings now mention the canonical form. - Tests: unit coverage for `NormalizeGrpcEndpoint` (incl. case-insensitive `TLS=True`, missing tls, unparseable tls, unsupported scheme), env-var path via `DAPR_GRPC_ENDPOINT=dns://...`, and dns acceptance / dns- without-tls rejection on both `DaprClientBuilder` and `DaprJobsClientBuilder` (which exercises the shared generic builder used by Workflow/Messaging/AI/etc). HTTP endpoints and `InvocationHandler` are unchanged. Signed-off-by: Luis Rascao <luis.rascao@gmail.com>
|
@lrascao Could you please add some integration tests demonstrating that this doesn't hit a wall when it encounters the runtime? You're right that from the client side, there's more than can be done here (as you did), but for the most part, the SDK is generally just a wrapper for functionality exposed by the runtime. So while yes, the client using |
|
@lrascao Also, the warning about HTTP is because the SDKs are transitioning to only connect to the runtime via gRPC, so the intent is that developers have some heads up that they need to specify a gRPC endpoint/port for their apps so callbacks work (if using those building blocks). |
There was a problem hiding this comment.
Pull request overview
This PR updates the Dapr .NET SDK client builders to accept a “canonical” gRPC DNS-style endpoint (dns://...?...) by normalizing it into an http:// or https:// URI before creating the GrpcChannel, and adds unit tests to cover the new behavior.
Changes:
- Added
DaprDefaults.NormalizeGrpcEndpointto rewritedns://host:port?tls=<bool>intohttp(s)://host:port(and reject unsupported/malformed inputs). - Updated
DaprClientBuilder.BuildandDaprGenericClientBuilder.BuildDaprClientDependenciesto normalize gRPC endpoints before scheme checks andGrpcChannel.ForAddress. - Added/updated unit tests covering the normalization logic and builder acceptance/rejection of the DNS form.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/Dapr.Jobs.Test/DaprJobsClientBuilderTests.cs | Updates scheme validation expectations and adds acceptance/rejection tests for dns://...?... endpoints. |
| test/Dapr.Common.Test/DaprDefaultTest.cs | Adds unit tests for NormalizeGrpcEndpoint and env-var handling of DNS-form gRPC endpoints. |
| test/Dapr.AspNetCore.Test/DaprClientBuilderTest.cs | Updates scheme validation expectations and adds acceptance/rejection tests for dns://...?... endpoints. |
| src/Dapr.Common/DaprGenericClientBuilder.cs | Normalizes gRPC endpoint before creating the channel and removes redundant scheme validation. |
| src/Dapr.Common/DaprDefaults.cs | Introduces NormalizeGrpcEndpoint and normalizes env-var gRPC endpoints prior to BuildEndpoint. |
| src/Dapr.Client/DaprClientBuilder.cs | Normalizes gRPC endpoint before creating the channel and removes redundant scheme validation. |
Comments suppressed due to low confidence (2)
src/Dapr.Client/DaprClientBuilder.cs:174
- The comment above the Http2UnencryptedSupport switch is both misspelled (“maksecure”) and misleading: this switch enables unencrypted/plaintext HTTP/2 support for gRPC over
http://, not “secure” calls. Updating it will avoid confusion about why the switch is set only for the http scheme.
if (grpcEndpoint.Scheme.Equals(Uri.UriSchemeHttp))
{
// Set correct switch to maksecure gRPC service calls. This switch must be set before creating the GrpcChannel.
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
src/Dapr.Common/DaprGenericClientBuilder.cs:199
- The comment above the Http2UnencryptedSupport switch says it is for “secure gRPC service calls”, but the switch name and behavior are the opposite: it enables unencrypted/plaintext HTTP/2 support for
http://endpoints. Adjusting this comment will make the intent clearer and prevent future refactors from removing it incorrectly.
if (grpcEndpoint.Scheme.Equals(Uri.UriSchemeHttp))
{
// Set correct switch to make secure gRPC service calls. This switch must be set before creating the GrpcChannel.
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (uri.Scheme == DnsScheme) | ||
| { | ||
| var useTls = TryParseTlsQuery(uri.Query); | ||
| if (useTls is null) | ||
| { | ||
| throw new InvalidOperationException( | ||
| $"The gRPC endpoint '{endpoint}' uses the 'dns' scheme but is missing or has an invalid 'tls' " + | ||
| "query parameter. Use the canonical gRPC URI form 'dns://host:port?tls=true' or 'dns://host:port?tls=false'."); | ||
| } | ||
|
|
||
| var scheme = useTls.Value ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; | ||
| return new UriBuilder { Scheme = scheme, Host = uri.Host, Port = uri.Port }.ToString(); | ||
| } |
Description
The Dapr SDKs warn that http/https are deprecated as gRPC URI schemes — the prefix denotes a name resolver, not a transport.
DaprDefaults.NormalizeGrpcEndpoint. Returns http/https unchanged; rewritesdns://host:port?tls=truetohttps://host:portand?tls=falsetohttp://host:portbefore the URI reachesGrpcChannel.ForAddress(which is HttpClient-backed and does not understand the dns scheme). Throws ondns://without a parseabletls=true|falseand on any other scheme.DaprClientBuilder.BuildandDaprGenericClientBuilder .BuildDaprClientDependenciesnow normalize the gRPC endpoint up front and feed the normalized URI to both the http/https switch andGrpcChannel.ForAddress. The redundant scheme check on the gRPC path is gone; HTTP endpoint validation is unchanged.GetDefaultGrpcEndpointnormalizes the env-var value before handing it toBuildEndpoint(otherwiseUriBuilderwould strip the?tls=query while reassembling).UseGrpcEndpointdocstrings now mention the canonical form.NormalizeGrpcEndpoint(incl. case-insensitiveTLS=True, missing tls, unparseable tls, unsupported scheme), env-var path viaDAPR_GRPC_ENDPOINT=dns://..., and dns acceptance / dns- without-tls rejection on bothDaprClientBuilderandDaprJobsClientBuilder(which exercises the shared generic builder used by Workflow/Messaging/AI/etc). HTTP endpoints andInvocationHandlerare unchanged.Checklist