question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

gRPC status code header destroyed on error

See original GitHub issue

Been using this grpc-web client on a project for a while, but recently started handling specific grpc errors on the front-end. Our requests hit an Envoy proxy, which does its own httpToGrpc status conversion, but in the grpc-web-client code specifically we are also losing the grpc-status header.

Through some local testing, it looks like the problem comes from https://github.com/improbable-eng/grpc-web/blob/master/ts/src/client.ts#L99

const code = httpStatusToCode(status);
this.props.debug && debug("onHeaders.code", code);
const gRPCMessage = headers.get("grpc-message") || [];
this.props.debug && debug("onHeaders.gRPCMessage", gRPCMessage);
if (code !== Code.OK) {
  const statusMessage = this.decodeGRPCStatus(gRPCMessage[0]);
  this.rawOnError(code, statusMessage);
  return;
}

My problem could be solved if instead of passing the newly created code to rawOnError, it checked for a grpc-status header first and used that value before defaulting to the new code. Something like:

const code = parseInt(headers.get("grpc-status"), 10) || httpStatusToCode(status);

This seems to be the simplest way to fix it, although there may be reasons why this is unwanted.

Option 2: If we made sure the headers were being passed correctly, we could just pull them off the response. Right now when it throws an error it makes a new Metadata() and hands that off, but it that isn’t very helpful.

rawOnError(code: Code, msg: string) {
    this.props.debug && debug("rawOnError", code, msg);
    if (this.completed) return;
    this.completed = true;
    this.onEndCallbacks.forEach(callback => {
      detach(() => {
        callback(code, msg, new Metadata());
      });
    });
  }

If instead, rawOnError took an optional third parameter of the headers

rawOnError(code: Code, msg: string, headers?: Metadata) {

and passed those (if they exist) to the callback, we could accomplish essentially the same thing.

callback(code, msg, headers || new Metadata());

I could submit a PR for this anytime, just wanted to know if there were any extra thoughts about potential hiccups or problems with either of these. Personally, I would opt for the first option for simplicity.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:2
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
gunn4rcommented, Aug 29, 2018

@easyCZ After digging through this more, it appears the tests that are testing “error” cases are not actually ever hitting the error case here:

      const code = httpStatusToCode(status);
   
      this.props.debug && debug("onHeaders.code", code);
      
      const gRPCMessage = headers.get("grpc-message") || [];
      
      this.props.debug && debug("onHeaders.gRPCMessage", gRPCMessage);
      
      if (code !== Code.OK) {
        const statusMessage = this.decodeGRPCStatus(gRPCMessage[0]);
        this.rawOnError(code, statusMessage);
        return;
      }

      this.rawOnHeaders(headers);

(This is in client.ts)

The test server returns 200 responses even when “testing” the grpc error statuses, and with the current way the responses are handled that gets converted into a grpc status of 0 by the httpStatusToCode method.

So in the tests, while the test server returns a grpc error status, the http status is 200, thus bypassing the if statement. Many tests test for “headers / trailers” which are never available inside the error case. So it looks like to me that the tests are giving false positives.

I still think the appropriate change is to NOT do the http code conversion if there is a valid grpc-status on the headers. However this would mean that for users who are currently returning http 200 but with a grpc-error code, they would now be hitting the error case and I can see this being a breaking change. That said, I still think its an appropriate change.

Are you guys willing to address this issue? What are your thoughts?


Here is an example of a test that is exhibiting this behavior: (https://github.com/improbable-eng/grpc-web/blob/master/test/ts/src/invoke.spec.ts#L283)

headerTrailerCombos((withHeaders, withTrailers) => {
        it("should report status code for error with headers + trailers", (done) => {
          let didGetOnHeaders = false;
          let didGetOnMessage = false;

          const ping = new PingRequest();
          ping.setFailureType(PingRequest.FailureType.CODE);
          ping.setErrorCodeReturned(12);
          ping.setSendHeaders(withHeaders);
          ping.setSendTrailers(withTrailers);

          grpc.invoke(TestService.PingError, {
            debug: DEBUG,
            transport: transport,
            request: ping,
            host: testHostUrl,
            onHeaders: (headers: grpc.Metadata) => {
              DEBUG && debug("headers", headers);
              didGetOnHeaders = true;
            },
            onMessage: (message: Empty) => {
              didGetOnMessage = true;
            },
            onEnd: (status: grpc.Code, statusMessage: string, trailers: grpc.Metadata) => {
              DEBUG && debug("status", status, "statusMessage", statusMessage, "trailers", trailers);
              assert.deepEqual(trailers.get("grpc-status"), ["12"]);
              assert.deepEqual(trailers.get("grpc-message"), ["Intentionally returning error for PingError"]);
              assert.strictEqual(status, grpc.Code.Unimplemented);
              assert.strictEqual(statusMessage, "Intentionally returning error for PingError");
              assert.ok(didGetOnHeaders);
              assert.ok(!didGetOnMessage);
              done();
            }
          });
        });
      });

This test is checking for headers/trailers with a grpc error code. It works currently because the server while it does add the grpc-status 12 to the response, the http status is 200. It never hits the rawOnError inside the if (code !== Code.OK) { block, and instead always hits the this.rawOnHeaders(headers); just after that check. The primary difference being that the headers are provided to rawOnHeaders but not to rawOnError.

When I make the proposed change it causes many of the tests to fail because they are no longer hitting the rawOnHeaders and instead hitting the rawOnError.

0reactions
jonny-improbablecommented, Sep 4, 2018

I believe this was fixed by #226.

Read more comments on GitHub >

github_iconTop Results From Across the Web

GRPC Core: Status codes and their use in gRPC
Code Number Description OK 0 Not an error; returned on success. FAILED_PRECONDITION 9 OUT_OF_RANGE 11
Read more >
Status Response Codes | Google Maps Booking API
Code Status Notes 0 OK Return on Success 3 INVALID_ARGUMENT The client specified an invalid argument. 5 NOT_FOUND Some requested entity wasn't found.
Read more >
gRPC over HTTP2
Request-Headers are delivered as HTTP2 headers in HEADERS + CONTINUATION ... expect broken deployments to send non-200 HTTP status codes in responses as ......
Read more >
@google-cloud/common-grpc documentation
gRPC always returns an error proto message. We // pass that "error" into retry-request to act as the HTTP response, // so it...
Read more >
python gRPC error: "error": "13 INTERNAL: Failed to serialize ...
This is a server-side proto codec error. It usually happen because of: Inconsistent generated proto library between client/server; ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found