Grpc-web: gRPC requires HTTP/2

Created on 4 Dec 2017  路  17Comments  路  Source: improbable-eng/grpc-web

Hi,

I'm running a gRPC (python implementaton) server on port 50052.
In order to access it from the web I launched a reverse proxy that binds to this server (localhost:50052) and listen for incoming HTTP messages at localhost:8081.

When I try to call my gRPC server with the help of my proxy like this:

        const cCByFRequest = new CCByFRequest();
        cCByFRequest.setLfp("target");
        cCByFRequest.setFList(["f1", "f2"])
        cCByFRequest.setEList(["e1", "e2"])
        cCByFRequest.setLte(true)
        // console.log(cCByFRequest)
        grpc.unary(o.getCCByF, {
            debug: true,// optional - enable to output events to console.log
            request: cCByFRequest,
            host: 'http://localhost:8081',
            onEnd: res => {
                console.log(res)
                // const { status, statusMessage, headers, message, trailers } = res;
                // console.log("Status:"+statusMessage)
                // if (status === Code.OK && message) {
                //     console.log("all ok. got book: ", message.toObject());
                // }
            }
        });

I don't get any answer back. Here is the output from my console:

main.js:10653 Fetch finished loading: OPTIONS "http://localhost:8081/o/getCCByF".
main.js:74333 [WDS] Hot Module Replacement enabled.
getCCByF:1 POST http://localhost:8081/o/getCCByF net::ERR_EMPTY_RESPONSE
main.js:75101 {status: 13, statusMessage: "Response closed without headers", headers: BrowserHeaders, message: null, trailers: BrowserHeaders}

And when I access my server proxy at localhost:8081, it tells me
gRPC requires HTTP/2

Any idea what's wrong?

needs-repro

Most helpful comment

i'm getting gRPC requires HTTP/2 when i hit hostname.tld:8080 from Chrome

grpcwebproxy runs smoothly:

time="2018-03-28T17:51:02Z" level=info msg="dialing to target with scheme: \"\"" system=system
time="2018-03-28T17:51:02Z" level=info msg="listening for http on: [::]:8080"
time="2018-03-28T17:51:02Z" level=info msg="ccResolverWrapper: sending new addresses to cc: [{localhost:1337 0  <nil>}]" system=system
time="2018-03-28T17:51:02Z" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc42018aea0, CONNECTING" system=system
time="2018-03-28T17:51:03Z" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc42018aea0, READY" system=system

All 17 comments

Hi @mase5,

Sorry for the delay in getting back to you. Would you be able to provide a complete example of what you're attempting to do? A sample repo would go a long way to help resolve this.
Unfortunately, with only the above information I am not sure where the problem may lie.

I ran into the same issue. It happened when I attempted to mount the grpc-web wrapper into a go-chi router. Just going back to the standard net/http mux made is disappear again.

So this is likely not related to grpc-web itself.

Will close as we cannot reproduce this at the moment.

i'm getting gRPC requires HTTP/2 when i hit hostname.tld:8080 from Chrome

grpcwebproxy runs smoothly:

time="2018-03-28T17:51:02Z" level=info msg="dialing to target with scheme: \"\"" system=system
time="2018-03-28T17:51:02Z" level=info msg="listening for http on: [::]:8080"
time="2018-03-28T17:51:02Z" level=info msg="ccResolverWrapper: sending new addresses to cc: [{localhost:1337 0  <nil>}]" system=system
time="2018-03-28T17:51:02Z" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc42018aea0, CONNECTING" system=system
time="2018-03-28T17:51:03Z" level=info msg="pickfirstBalancer: HandleSubConnStateChange: 0xc42018aea0, READY" system=system

I'm was getting the same Response closed without headers error, then I tried using http as described in https://github.com/improbable-eng/grpc-web/issues/194, but the server doesn't seem to even start that way.

I too see gRPC requires HTTP/2 when visiting the default localhost:8080 in my browser, so I assume the server works, and that I'm calling the APIs correctly because TypeScript doesn't complain about how I'm calling the methods.

@easyCZ Can we re-open this ticket or #200 ? I'm also observing the same thing in my web browser client using grpcwebproxy-0.6.3 and grpcwebproxy-0.7.0:

screen shot 2019-02-06 at 12 07 55 pm

And when I use curl:

curl -D - http://localhost:9000/Proxy/Ping -H 'Content-Type: application/grpc-web+proto'
HTTP/1.1 500 Internal Server Error
Date: Wed, 06 Feb 2019 20:09:26 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 21
Connection: keep-alive

gRPC requires HTTP/2

Have you got a minimal reproduction example of this? This is the kind of error you'd get if you forgot to wrap your server in the proxy and cURLed the gRPC server directly by mistake.

I reproduced above.

Could you share more information about your setup? The reason I'm thinking it might just be a setup problem is because all tests we have in this repo would be failing if, as this bug implies, well, nothing really worked.

@johanbrandhorst Hello, I have encountered such a problem. How to solve this problem?I did not find a solution

Hi,

I too got the 500 error. So I debugged it.

If you look here https://github.com/improbable-eng/grpc-web/blob/master/go/grpcwebproxy/main.go#L129 , you see that when you receive any request, it forwards it to a wrapped grpc server.

Eventually that leads to here: https://github.com/grpc/grpc-go/blob/master/server.go#L761 that line calls the following check in the transport package:
if r.ProtoMajor != 2 { return nil, errors.New("gRPC requires HTTP/2") }

This check fails since we're not making any grpc requests at this point, just a simple http call. It's still being handled by a grpc server, hence why we get "gRPC requires HTTP/2".

Now if you look right above line 761, It's clearly stated that:

// To share one port (such as 443 for https) between gRPC and an
// existing http.Handler, use a root http.Handler such as:
//
// if r.ProtoMajor == 2 && strings.HasPrefix(
// r.Header.Get("Content-Type"), "application/grpc") {
// grpcServer.ServeHTTP(w, r)
// } else {
// yourMux.ServeHTTP(w, r)
// }

So the solution, I think is to replace https://github.com/improbable-eng/grpc-web/blob/master/go/grpcwebproxy/main.go#L129 with:

  if strings.HasPrefix(req.Header.Get("Content-Type"), "application/grpc-web-text") {
          wrappedGrpc.ServeHTTP(resp, req)
      } else {
          http.DefaultServeMux.ServeHTTP(resp, req)
      }

"application/grpc-web-text" might be a bit simplistic, but from the quick tests I did, it suffices.

Thoughts?

Why? The proxies job is to proxy the requests, it sounds to me like what's happening here is that non-grpc requests are erroring. Maybe we should improve the error message but it doesn't sound wrong to me. Am I missing something?

I think that it's exactly that, the proxy isn't doing it's job. It's forwarding non grpc requests to the grpc server. Unless I'm mistaking, the grpc server is expecting only grpc requests, and it's the grpc server that's giving back the error "gRPC requires HTTP/2", not the proxy.

On this line: https://github.com/improbable-eng/grpc-web/blob/master/go/grpcwebproxy/main.go#L129

Sure, that sounds reasonable, but what I mean is, why should it be the proxies responsibility to stop these requests?

It wouldn't stop them, it would just routes them correctly.
http requests to the http server,
grpc requests to the grpc server.

As of right now, even the /metrics http path is not working. (at least not for me using Firefox) For the same reason as stated above.

Does it (/metrics) work for you?

Does it (/metrics) work for you?

I haven't got a setup to try this unfortunately, but I didn't know we were serving a HTTP server in addition to proxying the requests. @jonny-improbable or @MarcusLongmuir will probably be better placed to comment on this.

Hello,

I'm commenting because I had CORS issues with the server which I managed to solve, and it may help others (or maybe help me understand why it didn't work for me in the first case). Basically, I implemented the wrappedGrpc following the react example, and that server works fine when using WithCors...(false). Then I copied the grpc server code basically line by line into my own implementation:

wrappedServer := grpcweb.WrapServer(grpcServer,
    grpcweb.WithCorsForRegisteredEndpointsOnly(false))

handler := func(resp http.ResponseWriter, req *http.Request) {
    wrappedServer.ServeHTTP(resp, req)
}

httpServer := http.Server{
    Addr: grpcHttpAddress,
    Handler: http.HandlerFunc(handler),
}

go func () {
    logger.Fatal("", zap.Error(httpServer.ListenAndServe()))
}()

Then I used an angular devserver attempting an unary call to both grpc services:

grpc.unary(GrpcService.DoSomething, {
  request: request,
  host: http://localhost:9010, // My own grpc server implementation
  onEnd: (res) => {
    console.log(res);
  },
});
grpc.unary(GrpcService.DoSomething, {
  request: request,
  host: http://localhost:9090, // Boilerplate grpc server, lacks implementation, used to check connections only
  onEnd: (res) => {
    console.log(res);
  },
});

So exact same call, both being to localhost but on two different ports, but my new grpc server got CORS preflight Options (which wouldn't get handled, gets the gRPC requires HTTP/2), and the other boilerplate server would get a POST request instead of doing CORS preflight and work just fine. Why this is, I don't know. Seeing that it's all locally on localhost I don't know why CORS is even called at all, and if it were to be called due to different ports (which is inevitable) then why would it be called only for my implementation and not the boilerplate one? Maybe the computer gods are out to get me.

Anyway I did this to solve:

wrappedServer := grpcweb.WrapServer(grpcServer,
    grpcweb.WithAllowedRequestHeaders([]string{"*"}),
    grpcweb.WithCorsForRegisteredEndpointsOnly(false),
    grpcweb.WithOriginFunc( func (origin string) bool {
        // Only allow localhost on angular dev port and port 80 (prod port within docker DNS)
        return origin == "http://localhost:4200" || origin == "http://localhost"
    }))

handler := func(resp http.ResponseWriter, req *http.Request) {
    if wrappedServer.IsGrpcWebRequest(req) {
        wrappedServer.ServeHTTP(resp, req)
    } else if wrappedServer.IsAcceptableGrpcCorsRequest(req) {
        // Options request, manually handle instead of using http server (of which there is none)
        resp.Header().Set("Access-Control-Allow-Origin", "*")
        resp.Header().Set("Access-Control-Allow-Headers", "*")
    }
}

I hope it may help someone who's as beginner at implementing CORS as I am!

Was this page helpful?
0 / 5 - 0 ratings