Search code examples
dockergrpcenvoyproxygrpc-web

`Protocol Error` when trying to connect Envoy to gRPC service using gRPC Web


I have a web app using gRPC Web to interact with my gRPC service through a dockerized Envoy Proxy. When I try calling the endpoint exposed in Envoy, I receive the following error:

gRPC Error (code: 14, codeName: UNAVAILABLE, message: upstream connect error or disconnect/reset before headers. reset reason: protocol error, details: [], rawResponse: null, trailers: {content-length: 0})

Here is my client side code:

class FiltersService {
  static ResponseFuture<Filters> getFilters() {    
    GrpcWebClientChannel channel =
        GrpcWebClientChannel.xhr(Uri.parse('http://localhost:9000'));

    FiltersServiceClient clientStub = FiltersServiceClient(
      channel,
    );

    return clientStub.getFilters(Void());
  }
}

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: 9000 }
      filter_chains:
        - filters:
            - name: envoy.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: greeter_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_http1_bridge
                  - name: envoy.filters.http.grpc_web
                  - name: envoy.filters.http.cors
                  - name: envoy.filters.http.router
  clusters:
    - name: greeter_service
      connect_timeout: 0.25s
      type: logical_dns
      upstream_connection_options:
        tcp_keepalive:
          keepalive_time: 300
      lb_policy: round_robin
      load_assignment:
        cluster_name: cluster_0
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: host.docker.internal
                      port_value: 9001

Where port 9001 is where my gRPC service is running. I was able to verify the service is working properly by directly calling it from Kreya, and I received the correct response.

I am also able to successfully access the admin server from localhost:9901.

However, when I try calling the endpoint exposed by Envoy (9000) through either Kreya or client side, I get

gRPC Error (code: 14, codeName: UNAVAILABLE, message: upstream connect error or disconnect/reset before headers. reset reason: protocol error, details: [], rawResponse: null, trailers: {content-length: 0})

I am running the following commands to run the Dockerized Envoy:

docker build -t my-envoy:1.0 .

docker run -p 9000:9000 -p 9901:9901 my-envoy:1.0


Solution

  • After a lot of frustration and playing around, I finally figured it out. Looking at the documentation, it seems like the envoy.filters.httl.grpc_web filter can translate a request to both HTTP/2 and HTTP/3. I am assuming (though if someone more knowledgeable in this field can correct me, please do) that without specifying, Envoy does not know what protocol to translate to. As such, simply adding http2_protocol_options: {} to my cluster resolved the issue. I'm including the full cluster block below for anyone that may come across the same issue in the future.

    clusters:
        - name: greeter_service
          connect_timeout: 0.25s
          type: logical_dns
          lb_policy: round_robin
          http2_protocol_options: {}
          load_assignment:
            cluster_name: cluster_0
            endpoints:
              - lb_endpoints:
                  - endpoint:
                      address:
                        socket_address:
                          address: host.docker.internal
                          port_value: 9001
    

    For some reason, whenever I tried using the --networks=host flag in my docker run command, I was unable to access the admin portal, and so the solutions that I came across, and the tutorials for using Envoy to proxy for gRPC Web weren't very beneficial in resolving this issue. Hopefully this is helpful to someone facing the same issue.