Skip to content

Commit 95cf7fc

Browse files
authored
Improve error message with invalid headers (#2068)
1 parent f056e80 commit 95cf7fc

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -355,21 +355,28 @@ protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)
355355
throw new InvalidOperationException("Response headers can only be sent once per call.");
356356
}
357357

358-
foreach (var entry in responseHeaders)
358+
foreach (var header in responseHeaders)
359359
{
360-
if (entry.Key == GrpcProtocolConstants.CompressionRequestAlgorithmHeader)
360+
if (header.Key == GrpcProtocolConstants.CompressionRequestAlgorithmHeader)
361361
{
362362
// grpc-internal-encoding-request is used in the server to set message compression
363363
// on a per-call bassis.
364364
// 'grpc-encoding' is sent even if WriteOptions.Flags = NoCompress. In that situation
365365
// individual messages will not be written with compression.
366-
ResponseGrpcEncoding = entry.Value;
366+
ResponseGrpcEncoding = header.Value;
367367
HttpContext.Response.Headers[GrpcProtocolConstants.MessageEncodingHeader] = ResponseGrpcEncoding;
368368
}
369369
else
370370
{
371-
var encodedValue = entry.IsBinary ? Convert.ToBase64String(entry.ValueBytes) : entry.Value;
372-
HttpContext.Response.Headers.Append(entry.Key, encodedValue);
371+
var encodedValue = header.IsBinary ? Convert.ToBase64String(header.ValueBytes) : header.Value;
372+
try
373+
{
374+
HttpContext.Response.Headers.Append(header.Key, encodedValue);
375+
}
376+
catch (Exception ex)
377+
{
378+
throw new InvalidOperationException($"Error adding response header '{header.Key}'.", ex);
379+
}
373380
}
374381
}
375382

src/Grpc.AspNetCore.Server/Internal/HttpResponseExtensions.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -31,7 +31,14 @@ public static void ConsolidateTrailers(this HttpResponse httpResponse, HttpConte
3131
foreach (var trailer in context.ResponseTrailers)
3232
{
3333
var value = (trailer.IsBinary) ? Convert.ToBase64String(trailer.ValueBytes) : trailer.Value;
34-
trailersDestination.Append(trailer.Key, value);
34+
try
35+
{
36+
trailersDestination.Append(trailer.Key, value);
37+
}
38+
catch (Exception ex)
39+
{
40+
throw new InvalidOperationException($"Error adding response trailer '{trailer.Key}'.", ex);
41+
}
3542
}
3643
}
3744

test/FunctionalTests/Client/TrailerMetadataTests.cs test/FunctionalTests/Client/MetadataTests.cs

+65-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -26,7 +26,7 @@
2626
namespace Grpc.AspNetCore.FunctionalTests.Server;
2727

2828
[TestFixture]
29-
public class TrailerMetadataTests : FunctionalTestBase
29+
public class MetadataTests : FunctionalTestBase
3030
{
3131
[Test]
3232
public async Task GetTrailers_UnaryMethodSetStatusWithTrailers_TrailersAvailableInClient()
@@ -224,4 +224,67 @@ async Task UnaryDeadlineExceeded(HelloRequest request, IAsyncStreamWriter<HelloR
224224

225225
AssertHasLogRpcConnectionError(StatusCode.InvalidArgument, "Validation failed");
226226
}
227+
228+
[Test]
229+
public async Task ServerTrailers_UnaryMethodThrowsExceptionWithInvalidTrailers_FriendlyServerError()
230+
{
231+
Task<HelloReply> UnaryCall(HelloRequest request, ServerCallContext context)
232+
{
233+
var trailers = new Metadata();
234+
trailers.Add(new Metadata.Entry("Name", "This is invalid: \u0011"));
235+
return Task.FromException<HelloReply>(new RpcException(new Status(StatusCode.InvalidArgument, "Validation failed"), trailers));
236+
}
237+
238+
// Arrange
239+
SetExpectedErrorsFilter(writeContext => true);
240+
241+
var method = Fixture.DynamicGrpc.AddUnaryMethod<HelloRequest, HelloReply>(UnaryCall);
242+
243+
var channel = CreateChannel();
244+
245+
var client = TestClientFactory.Create(channel, method);
246+
247+
// Act
248+
var call = client.UnaryCall(new HelloRequest());
249+
250+
var ex = await ExceptionAssert.ThrowsAsync<RpcException>(() => call.ResponseAsync).DefaultTimeout();
251+
252+
// Assert
253+
Assert.AreEqual(StatusCode.Unknown, ex.StatusCode);
254+
Assert.AreEqual("Bad gRPC response. HTTP status code: 500", ex.Status.Detail);
255+
256+
HasLogException(ex => ex.Message == "Error adding response trailer 'name'." && ex.InnerException!.Message == "Invalid non-ASCII or control character in header: 0x0011");
257+
}
258+
259+
[Test]
260+
public async Task ServerHeaders_UnaryMethodThrowsExceptionWithInvalidTrailers_FriendlyServerError()
261+
{
262+
async Task<HelloReply> UnaryCall(HelloRequest request, ServerCallContext context)
263+
{
264+
var headers = new Metadata();
265+
headers.Add(new Metadata.Entry("Name", "This is invalid: \u0011"));
266+
await context.WriteResponseHeadersAsync(headers);
267+
return new HelloReply();
268+
}
269+
270+
// Arrange
271+
SetExpectedErrorsFilter(writeContext => true);
272+
273+
var method = Fixture.DynamicGrpc.AddUnaryMethod<HelloRequest, HelloReply>(UnaryCall);
274+
275+
var channel = CreateChannel();
276+
277+
var client = TestClientFactory.Create(channel, method);
278+
279+
// Act
280+
var call = client.UnaryCall(new HelloRequest());
281+
282+
var ex = await ExceptionAssert.ThrowsAsync<RpcException>(() => call.ResponseAsync).DefaultTimeout();
283+
284+
// Assert
285+
Assert.AreEqual(StatusCode.Unknown, ex.StatusCode);
286+
Assert.AreEqual("Exception was thrown by handler. InvalidOperationException: Error adding response header 'name'. InvalidOperationException: Invalid non-ASCII or control character in header: 0x0011", ex.Status.Detail);
287+
288+
HasLogException(ex => ex.Message == "Error adding response header 'name'." && ex.InnerException!.Message == "Invalid non-ASCII or control character in header: 0x0011");
289+
}
227290
}

0 commit comments

Comments
 (0)