grpc / grpc-web

gRPC for Web Clients
https://grpc.io
Apache License 2.0
8.59k stars 765 forks source link

Metadata is empty, but Network tab shows it #1376

Open EByrdS opened 11 months ago

EByrdS commented 11 months ago

In short, the metadata of my web-grpc client is always empty.

Proto definition

Let's say my proto file looks like:

// some/path/myservice.proto
syntax = "proto3";

import "google/protobuf/empty.proto";

package some.path.myservice;

service MyService {
  rpc RespondMetadata(google.protobuf.Empty) returns (google.protobuf.Empty) {}
}

Code generation

I generated code for my proto files using "grpc-web": "^1.4.2".

And protoc:

$ protoc --version
libprotoc 24.3

The command is:

protoc -I=. --js_out=import_style=commonjs,binary:. \
  --grpc-web_out=import_style=typescript,mode=grpcweb:. \
  some/path/myservice.proto

Server-side

The server of this gRPC sets the metadata header some-header in Golang:

func  (s myServer) RespondMetadata(ctx context.Context, request *emptypb.Empty) (*emptypb.Empty, error) {
    // ... Do some other stuff
        // All side-effects resulting from this call happen sucessfully in the server. e.g. printing to console

    // Set the metadata key value pair as header
    header := metadata.New(map[string]string{"some-header": "some-value"})
    err := grpc.SetHeader(ctx, header)
    if err != nil {
        return nil, fmt.Errorf("Failed to set header: %w", err)
    }

    return &emptypb.Empty{}, nil
}

I know this is working fine because I can create a Golang client for this server and successfully read the headers, both by dialing into the grpc port directly, or by dialing an Envoy proxy that I have already set up.

Metadata transformed into HTTP headers

If I create my grpc client for the browser using grpc-web, I can see the metadata present in the HTTP Response Headers of the browser Network tab:

image

Issue within browser (grpc-web) typescript code

However, in the code the metadata is always empty.

I initialize my client in the following way:

import { MyServiceClient } "some/path/MyServiceClientPb";

const client = new MyServiceClient(serverUrl, null, null)

And I have tried calling the method and reading metadata in the following ways:

      const grpcRequest = new google_protobuf_empty_pb.Empty();

      const call = client.respondMetadata(grpcRequest, {}, (err, response) => {
        if (err) {
          console.log("There was an error", err);
        } else {
          // response is of type google.protobuf.Empty
          // So I can't read metadata from here
          console.log("Successfull response: ", response) 
        }
      });

      call.on('status', (status) => {
        console.log("Status metadata: ", status.metadata) // This is empty: {}
        console.log("Status!:", status)
      });
      call.on('metadata', (metadata) => {
        console.log("Received metadata: ", metadata); // This is empty: {}
        const value = metadata['some-header'];

        console.log('The received header is: ', value); // this is undefined
      })

Any ideas how can I read response metadata? Or why are these approaches not working?

sampajano commented 11 months ago

Hi! Thanks for reporting!

I'm not really familiar with the go grpc server, and how metadata is propagated, but we do have a demo with a Node server which echos the metadata and prints it out.

You can run it using:

docker-compose up --build node-server envoy ts-client

And you can see the metadata being printed out in the console, with the following code: https://github.com/grpc/grpc-web/blob/1ab0bdc25b0d94428cb9d20cced5dcac00740a06/net/grpc/gateway/examples/echo/ts-example/client.ts#L70-L75

Could you try the above example, and then compare the base64 decoded payload of what you got on your setup v.s. the demo?

It should help understand where the discrepancies are.

Thanks :)

vdhanan commented 8 months ago

hey @EByrdS did you ever figure this out? i'm running into the same issue...