deeplay-io/nice-grpc

"Response closed without headers" when a GRPC error is returned

Closed this issue · 7 comments

The server I'm calling is throwing a GRPC error like this:

responseObserver.onError(StatusProto.toStatusRuntimeException(
  StatusProto.newBuilder()
     .setCode(ALREADY_EXISTS_VALUE)
     .addDetails(Any.pack(StringValue.of("email"))
     .build()
))

This works via gprcurl:

Resolved method descriptor:
rpc SignUp ( .service.user.SignUpRequest ) returns ( .service.user.SignUpReply );

Request metadata to send:
(empty)

Response headers received:
(empty)

Response trailers received:
content-type: application/grpc
Sent 1 request and received 0 responses
ERROR:
  Code: AlreadyExists
  Message:
  Details:
  1)    {
          "@type": "type.googleapis.com/google.protobuf.StringValue",
          "value": "email"
        }

But in the web, I get :

ClientError: /service.user.KawonUserService/SignUp UNKNOWN: Response closed without headers
    at createUnaryMethod.ts:78:18
    at grpc-web-client.umd.js:1:11610
    at Array.forEach (<anonymous>)
    at e3.rawOnError (grpc-web-client.umd.js:1:11572)
    at e3.onTransportEnd (grpc-web-client.umd.js:1:10438)
    at grpc-web-client.umd.js:1:17299
    at nrWrapper (account-details:21:29518)

Looking in the network console, I can see I do get a 200 back with these in the response headers as I'd expect (I think):
image

What's going wrong here?
I guess technically there aren't any response headers received according to grpcurl - but is this a problem?

I am using Envoy with the grpc-web filter (no custom config).
I have a CORS filter with expose_headers: grpc-status,grpc-message.

This works fine when the response is successful.

I've been experimenting and I found out that if I send a valid response first e.g. responseObserver.onNext(SignUpReply.newBuilder().build()); immediately preceding on the onError, I now get the correct grpc error code back.

However, now the grpc-status and grpc-message appear to be in the http response instead, along with the grpc-status-details-bin. It's not clear if I have access to this:

image

Seems like if I return more than one response I'm having the same issue as from this report:
#199

But when I only return an error these actually do go into the headers, but apparently nice-grpc-web can't parse that.

So in the streaming case where I send an onNext then onError, I can use the onTrailer in nice-grpc-web to get the grpc-status-details-bin. I don't know how I can get it in the case that it's in the http headers instead of the response body.

Ok, I tracked this down further.

If I don't set a message on the Status object I build in the server, then I get an empty grpc-message in the headers, which causes the client to throw the error I was seeing (response closed without headers).
image

If I do set it, then it works as expected and gives me the correct code + access to the status details (I found nice-grpc-error-details to help me with that).

I'm not sure if this is expected behaviour. I don't think it should crash like this if the server doesn't provide a message but does provide all of the other error metadata.

Hey, thank you for your report and research.

I'm trying to reproduce this issue. Can you please share your full envoy config?

I was having this issue using project-contour as the server. I ended up needing to add the grpc headers to the corsPolicy.exposeHeaders prop. e.g.:

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: proxy-name
spec:
  virtualhost:
    fqdn: dev.my-service.myapp.com 
    corsPolicy:
      allowCredentials: true
      allowOrigin:
        - "*" 
      allowMethods:
        - GET
        - POST
        - OPTIONS
      allowHeaders:
        - authorization
        - cache-control
        - x-grpc-web
        - User-Agent
        - x-accept-content-transfer-encoding
        - x-accept-response-streaming
        - x-user-agent
        - x-grpc-web
        - grpc-timeout
        - Grpc-Message
        - Grpc-Status
        - content-type
      exposeHeaders: # <------------------- Right here
        - Grpc-Message
        - Grpc-Status
    tls:
      secretName: my-cert
  routes:
    - conditions:
      - prefix: /
      services:
        - name: my-service 
          port: 80

thanks for leading me down the right path @rikbrown

This should be fixed in #274, or at least return a better error message. Please feel free to reopen.