Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/Dapr.Client/DaprClientGrpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ public override async Task<BindingResponse> InvokeBindingAsync(BindingRequest re
}

var options = CreateCallOptions(headers: null, cancellationToken);
RemoveBinaryMetadata(options.Headers);
try
{
return await client.InvokeBindingAsync(envelope, options);
Expand Down Expand Up @@ -2308,6 +2309,28 @@ private CallOptions CreateCallOptions(Metadata headers, CancellationToken cancel
return options;
}

/// <summary>
/// Removes binary gRPC metadata entries (keys ending in "-bin", such as grpc-trace-bin) because
/// the runtime copies InvokeBinding call metadata into string binding component metadata, where
/// raw bytes break components that require text values. Trace context still propagates via the
/// traceparent and tracestate headers.
/// </summary>
private static void RemoveBinaryMetadata(Metadata headers)
{
if (headers is null)
{
return;
}

for (var i = headers.Count - 1; i >= 0; i--)
{
if (headers[i].Key.EndsWith("-bin", StringComparison.OrdinalIgnoreCase))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoshVanL Should this look for endswith("bin") instead of "-bin" given the example here of "x-ms-meta-daprgrpctracebin"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WhitWaldo no I think keeping the -bin is correct:

https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md

Note that HTTP2 does not allow arbitrary octet sequences for header values so binary header values must be encoded using Base64 as per https://tools.ietf.org/html/rfc4648#section-4. Implementations MUST accept padded and un-padded values and should emit un-padded values. Applications define binary headers by having their names end with "-bin". Runtime libraries use this suffix to detect binary headers and properly apply base64 encoding & decoding as headers are sent and received.

x-ms-meta-daprgrpctracebin is not a proper gRPC binary header AFAIK

{
headers.RemoveAt(i);
}
}
}

/// <summary>
/// Makes Grpc call using the cancellationToken and handles Errors.
/// All common exception handling logic will reside here.
Expand Down
31 changes: 31 additions & 0 deletions test/Dapr.Client.Test/InvokeBindingApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Dapr.Client.Test;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Threading;
Expand Down Expand Up @@ -156,6 +157,36 @@ public async Task InvokeBindingAsync_WithRequest_ValidateRequest()
});
}

[Fact]
public async Task InvokeBindingAsync_StripsBinaryHeadersButKeepsTraceparent()
{
await using var client = TestClient.CreateForDaprClient();

var previousActivity = Activity.Current;
using var activity = new Activity("test-binding");
activity.SetIdFormat(ActivityIdFormat.W3C);
activity.Start();

try
{
var invokeRequest = new InvokeRequest() { RequestParameter = "Hello " };
var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.InvokeBindingAsync<InvokeRequest>("test", "create", invokeRequest);
});

request.Dismiss();

// The runtime copies InvokeBinding call metadata into string binding component metadata,
// which must be valid text, so binary (-bin) gRPC metadata must not be sent on this call.
request.Request.Headers.Any(h => h.Key.EndsWith("-bin", StringComparison.OrdinalIgnoreCase)).ShouldBeFalse();
request.Request.Headers.Contains("traceparent").ShouldBeTrue();
}
finally
{
Activity.Current = previousActivity;
}
}

[Fact]
public async Task InvokeBindingAsync_WithCancelledToken()
Expand Down
Loading