"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):
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:
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).
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