Search code examples
reactjsgrpcenvoyproxygrpc-web

React + gRPC don't reach envoy container


I was trying to build a very basic web page in React that would send gRPC request to a backend written in Rust.

I followed these guides:

https://daily.dev/blog/build-a-chat-app-using-grpc-and-reactjs

https://github.com/grpc/grpc-web/tree/master/net/grpc/gateway/examples/helloworld

However, I cannot get the gRPC request to reach the envoy proxy in the docker container (envoy is the only component running inside a container so far).

This is my proto file:

syntax = "proto3";

package Base;

service Hello {
    rpc HelloWorld(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {}
message HelloResponse {
    string message = 1;
}

This is my App.js file (basically as it gets created by the react bootstrapper, I just added the function and the button)

import logo from './logo.svg';
import './App.css';

import { HelloClient } from './base_grpc_web_pb';
import { HelloRequest } from './base_pb';

const client = new HelloClient("http://" + window.location.hostname + ":8080", null, null);

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
      <button
        onClick={sendRequest}
        style={{
          padding: "7px 38px",
          fontSize: "1.2em",
          boxSizing: "content-box",
          borderRadius: "4px",
        }}
      >
        Join
      </button>
    </div>
  );
}

function sendRequest() {
  const request = new HelloRequest();
  
  console.log("CLICK");

  client.helloWorld(request, {}, (err, response) => {
    if (err) return console.log("BBBBB error", err);
    console.log("AAAAA RESPONSE", response.getMessage());
  });
}


export default App;

This is my envoy configuration (I am on Ubuntu 22.04, so no need to override the address with host.docker.internal):

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

static_resources:
  listeners:
    - name: proxy
      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.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                codec_type: auto
                stat_prefix: ingress_http
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: local_service
                      domains: ["*"]
                      routes:
                        - match: { prefix: "/" }
                          route:
                            cluster: hellors
                            timeout: 0s
                            max_stream_duration:
                              grpc_timeout_header_max: 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
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
                  - name: envoy.filters.http.cors
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
    - name: hellors
      connect_timeout: 0.25s
      type: logical_dns
      http2_protocol_options: {}
      lb_policy: round_robin
      # win/mac hosts: Use address: host.docker.internal instead of address: localhost in the line below
      load_assignment:
        cluster_name: cluster_0
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: 0.0.0.0
                      port_value: 9090

Dockerfile for envoy:

FROM envoyproxy/envoy-dev:latest
COPY envoy.yaml /etc/envoy/envoy.yaml
RUN chmod go+r /etc/envoy/envoy.yaml

In the console, after I click the button I get this error message:

http://localhost:8080/Base.Hello/HelloWorld [HTTP/1.1 503 Service Unavailable 5ms]

Followed by

message: "Http response at 400 or 500 level", [...]

If I try to curl the port it seems like it isn't listening:

>$ curl -v http://localhost:8080
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 503 Service Unavailable
< content-length: 145
< content-type: text/plain
< date: Thu, 28 Apr 2022 16:03:55 GMT
< server: envoy
< 
* Connection #0 to host localhost left intact
upstream connect error or disconnect/reset before headers. reset reason: connection failure, transport failure reason: delayed connect error: 111

At this point I am quite stuck, all the configuration files seem ok, yet I still can't reach the envoy proxy. Any suggestions?

I have stumbled upon this post that suggests to use nginx, however I don't understand why that would be needed, especially since the gRPC examples I was looking at don't use it.


Solution

  • In your case, the Http response at 400 or 500 level is probably caused by the fact that your gRPC service is not reachable from Envoy.

    You said that Envoy is running in a Docker container. If so, the cluster hellors in your Envoy config:

    clusters:
    - name: hellors
      load_assignment:
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: 0.0.0.0
                  port_value: 9090
    

    cannot be reached because you specify the address 0.0.0.0. Docker containers use their own networks, so inside that network, the 0.0.0.0:9090 is not defined, because your gRPC service is running on your host.

    You may want to try running Envoy in your host network (--net=host).

    Or if your gRPC service can be containerized, you can run Envoy and the gRPC service in the same Docker network.