grpc / grpc-web

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

gRPC-Web Basics Tutorial is incomplete #1001

Open krzysztofmajewski opened 3 years ago

krzysztofmajewski commented 3 years ago

I'm working on a proof-of-concept using gRPC-Web. I was able to get the Quick Start tutorial working. My next challenge is to adapt this demo by writing a gRPC-Web client in JavaScript that talks to my (very simple) gRPC server written in Python. Is this possible? I am following the Basics Tutorial but it is incomplete. In particular, some of the client-side Javascript code is missing, and the code snippet that is there is different from the corresponding code in the Docker containers from the Quick Start tutorial.

I am able to build the JavaScript stubs for my client. However, my gRPC server (Python) never sees any requests from the client (JavaScript). Not sure if this is due to a bug in my JavaScript, a bug in the Envoy proxy config, or something else. Troubleshooting is difficult because the Docker containers are based on a very minimal Linux install that is missing basic Unix tools.

I can offer to help document this once I get it working, but I would need some help from your team to get it working in the first place.

stanley-cheung commented 3 years ago

One reason why your Python server might not be receiving the requests could be because of how we set up the basic example.

So here https://github.com/grpc/grpc-web/tree/master/net/grpc/gateway/examples/echo#run-the-envoy-proxy, you will notice that, from the Envoy's docker image's perspective, there's a docker image via the --link parameter that's named node-server. In the envoy.yaml file, we are redirecting traffic to effectively to node-server:9090 here. Docker will resolve the true address of your node-server docker image, which most likely is running somewhere in localhost. So you might need to change that too.

krzysztofmajewski commented 3 years ago

Here is my envoy.yaml:

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: demo_service
                  max_grpc_timeout: 0s
              cors:
                allow_origin_string_match:
                - prefix: "*"
                allow_methods: GET, PUT, DELETE, POST, OPTIONS
                allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
                max_age: "1728000"
                expose_headers: custom-header-1,grpc-status,grpc-message
          http_filters:
          - name: envoy.filters.http.grpc_web
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.router
  clusters:
  - name: demo_service
    connect_timeout: 0.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
    load_assignment:
      cluster_name: cluster_0
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: host.docker.internal
                    port_value: 50051
krzysztofmajewski commented 3 years ago

In other words, my gRPC server (Python) is not running in a container, it's running directly on the host machine. Hence the address: host.docker.internal. Let me know if you need any other info. Thanks for your help!

stanley-cheung commented 3 years ago

In that case, when you run the Envoy container, did you try adding --network=host to the docker run command?

So you are saying that this envoy.yaml file works fine when the backend is in Node. The Node server does receive messages forwarded from Envoy. But if you swap the Node server with a Python server, but also listening on the same port 50051, then it doesn't work?

What if you set the environment variable GRPC_TRACE=all and GRPC_VERBOSITY=debug before you start the Python server?

krzysztofmajewski commented 3 years ago

I'm sorry to say that I'm on a Windows host (not my choice) and the host networking driver only works on Linux hosts. However, if I'm reading the Docker docs correctly, host.docker.internal should do the trick. Normally I would try pinging the host from inside the container, but unfortunately ping is not available in this container. If you think it's worth installing Envoy on a more fully-featured Linux container, I can certainly try that. If there is already a more fully-featured Envoy image pre-built that I can use, please point me to it.

krzysztofmajewski commented 3 years ago

Re: environment variables: I can certainly try that. But I'm already printing a line of text whenever my Python server receives a request. When I connect with my Python client, the line is printed. When I try to connect with my Javascript gRPC-Web client, the line is not printed. Would it help if I shared the Envoy logfile?

stanley-cheung commented 3 years ago

If your example works end-to-end with a Node server, then it seems that your Envoy instance (and the forwarding) is done correctly. So it seems that the issue may be with your Python server. Does your Python server work in a separate way?

For Envoy, our testing uses the docker image published by the envoy team like this: https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/docker/envoy/Dockerfile#L15

krzysztofmajewski commented 3 years ago

The example that works is the one from the Quick Start tutorial, where there is a gRPC server written in Node that runs in a container. What I'm trying to achieve by following the Basics Tutorial is to write a JavaScript client (running in the browser) that talks to my own gRPC server written in Python (running on the host, not in a container). My Envoy Dockerfile looks very similar to yours. Bottom line: the information in the Basics Tutorial was insufficient for me to succeed at this. I would like to show a proof-of-concept using gRPC-Web, and I cannot.

krzysztofmajewski commented 3 years ago

I've installed Envoy in a full-featured ubuntu container. I am able to access my gRPC server at host.docker.internal:50051 from that container, by using the telnet command (see attached screenshot). So, in principle Envoy should also be able to access it. Please let me know next steps for troubleshooting this.

image

stanley-cheung commented 3 years ago

The host.docker.internal address to be used inside a docker container is my understanding too. So you seem to be doing the correct thing here. I am at a loss here - if the node server examples works, then you are demonstrating that the grpc-web flows works end-to-end. If for some reason swapping out just the server it stops working, it seems like an isolated environment issue.

krzysztofmajewski commented 3 years ago

To be clear, I'm swapping out the Node server from the Quick Start tutorial with my own python server which implements different RPCs. So, the client code has also changed. I tried following the Basics Tutorial to make this work, but it seems there is not enough information there. I proposed to troubleshoot the Envoy config first, but it is possible the error is elsewhere (in my Javascript, for example). The only hypothesis I've eliminated so far is that my Python server is unreachable from the container running the Envoy proxy. The telnet screenshot above shows that my server is reachable from the container, do you agree?

stanley-cheung commented 3 years ago

Just shooting in the dark here: if you are also swapping the service, perhaps you also have to match these lines? https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/envoy.yaml#L24-L26 Is the Envoy cluster name also matching up between L26 and L40? Is your new frontend still creating the client passing hostname = localhost:8080?

krzysztofmajewski commented 3 years ago

Yes, I define a new cluster called demo_service: https://github.com/krzysztofmajewski/gRPC-demo/blob/master/web/envoy.yaml#L26

You can see the rest of the code here. I started with the code in the Quick start and tried to hack it to match my gRPC service by following the Basics tutorial. I did my best, but the tutorial seems incomplete. For example, the Quick Start code contains echoapp.js as well as client.js, but the Basics Tutorial only mentions a client.js.

RohitRox commented 3 years ago

@krzysztofmajewski I double checked the envoy.yaml in your repo. I can confirm it is fine and it works. I actually copied your yaml in my working grpc-web project with go backend, and it works. If it helps, I have been using envoy straight via docker-compose file

version: '3.7'
services:
  envoy:
    image: envoyproxy/envoy:v1.14.3
    ports:
      - XX:XX
      - XXX:XXX
    volumes:
      - ./envoy.yaml:/etc/envoy/envoy.yaml

Also I would suggest to test your grpc server with something like https://github.com/fullstorydev/grpcurl to make sure it is working as expected

yoavmil commented 3 years ago

I am strugling with the same issue. Cant connect Angular frontend to C++ gRPC server.

krzysztofmajewski commented 3 years ago

Thanks for testing @RohitRox but in the meantime we've decided not to go with gRPC. The front-end side of things didn't seem mature, based on the incomplete documentation.