0

I've built a gRPC service on aspnetcore, running it locally as an exe. However, I will need to host it on IIS in production, so I have been comparing the behavior when hosted on IIS, and I've run into a large discrepancy in the exception handling. The output is not the same from the perspective of the gRPC client. I have found a kludge to work around the issue but I'm wondering if I'm missing something.

Let's say my unary rpc throws an exception like this:

var status = new Status(StatusCode.InvalidArgument, "Value of arg abc is invalid");
var metadata = new Metadata() { { "Additional-Information", "Value of arg abc is invalid" } };
throw new RpcException(status, metadata);

When not hosting in IIS, the client gets a response with statusCode = 3, no metadata (aka http headers), and 6 trailers (in the following order):

"content-type" : "application/grpc"
"date" : "Wed, 21 Jun 2023 17:13:17 GMT"
"server" : "Kestrel" 
"content-length" : 0
"additional-information" : "Value of arg abc is invalid"

This is exactly what I would expect (3 is the value of StatusCode.InvalidArgument). I would consider this to be the expected behavior.

But when I am hosting on IIS, the output to the client is quite different. The client gets a response with statusCode = 13, these metadata (aka http headers, and again in the order I see them output):

"content-type" : "application/grpc"
"server" : "Microsoft-IIS/10.0"
"additional-information" : "Value of arg abc is invalid"
"grpc-status" : 3
"grpc-message" : "Value of arg abc is invalid"
"date" : "Wed, 21 June 2023 17:19:28 GMT"

and I get no trailers. This is definitely not the expected behavior.

It seems that, in between my throwing the exception and the client getting a response, something is removing all trailers, adding what had been trailers as metadata (aka http headers), and overriding statusCode to 13 which is the value of StatusCode.Internal.

I have not had luck finding which layer of middleware is doing this, or finding other stackoverflow questions about this behavior, but I did manage to basically replicate the expected behavior, when hosted on IIS. I did this by adding a Grpc.Core.Interceptors.Interceptor, having it catch the RpcException, setting ServerCallContext.Status to the Status in the RpcException, appending the trailers from the exception by using ServerCallContext.ResponseTrailers.Add(), and then returning an empty response, rather than rethrowing the exception. The output is not precisely the same, but its close enough; I end up with statusCode 3, and I get the "additional-information" trailer, but I also get 3 items in the http header, "content-type", "server", and "date".

Its not exactly the same, but still, that output means that clients can seamlessly switch between a server hosted on IIS and one hosted directly on Kestrel in an exe, without changing client code.

But its a big kludge. My question is, can I get the expected behavior when hosting on IIS without a kludge like this? Is there some middleware I can configure or remove that will lead to the expected behavior, whether hosted on IIS or not?

rrreee
  • 753
  • 1
  • 6
  • 20

0 Answers0