grpc-dart icon indicating copy to clipboard operation
grpc-dart copied to clipboard

Help: Invalid frame type: 1

Open MateusAmin opened this issue 3 years ago • 8 comments

Error: gRPC Error (code: 12, codeName: UNIMPLEMENTED, message: Invalid frame type: 1, details: null, rawResponse: null)
    at Object.throw_ [as throw] (http://localhost:40069/dart_sdk.js:5062:11)
    at web_streams._GrpcWebConversionSink.new.[_parseFrameType] (http://localhost:40069/packages/grpc/src/client/transport/web_streams.dart.lib.js:136:19)
    at web_streams._GrpcWebConversionSink.new.add (http://localhost:40069/packages/grpc/src/client/transport/web_streams.dart.lib.js:209:53)
    at _ConverterStreamEventSink.new.add (http://localhost:40069/dart_sdk.js:45045:29)
    at _SinkTransformerStreamSubscription.new.[_handleData] (http://localhost:40069/dart_sdk.js:36146:34)
    at _RootZone.runUnaryGuarded (http://localhost:40069/dart_sdk.js:37595:11)
    at _ControllerSubscription.new.[_sendData] (http://localhost:40069/dart_sdk.js:31475:22)
    at _DelayedData.new.perform (http://localhost:40069/dart_sdk.js:34780:28)
    at _StreamImplEvents.new.handleNext (http://localhost:40069/dart_sdk.js:34884:15)
    at async._AsyncCallbackEntry.new.callback (http://localhost:40069/dart_sdk.js:34645:16)
    at Object._microtaskLoop (http://localhost:40069/dart_sdk.js:37925:13)
    at _startMicrotaskLoop (http://localhost:40069/dart_sdk.js:37931:13)
    at http://localhost:40069/dart_sdk.js:33666:9

grpc: dependency: "direct main" description: name: grpc url: "https://pub.dartlang.org" source: hosted version: "3.0.0"

Repro steps

https://github.com/MateusAmin/grpc-dart-issues-506

  1. Goto messages and run ./grpc.sh
  2. Goto server and run ./run-server.sh and ./run-envoy.sh
  3. Goto client and run flutter run -d chrome

Expected result: gRPC unary call completes without expectation

Actual result: gRPC unary call completes successfully server side but client throws expectation parsing response (an empty message object).

Details

enovy config:

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
          access_log:
            - name: envoy.access_loggers.file
              config:
                path: "/dev/stdout"
          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_web
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.router
  clusters:
  - name: greeter_service
    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: 50051

envy boot:

#! /bin/sh

sudo docker run -it -v "$(pwd)"/envoy.yaml:/etc/envoy/envoy.yaml:ro \
    --network=host envoyproxy/envoy:v1.15.0 --use-dynamic-base-id  --config-path '/etc/envoy/envoy.yaml'

dart grpc server:

#! /bin/sh

shellcheck ./*.sh

cd ../messages || exit
    ./../messages/grpc.sh
cd - || exit

dart bin/server.dart

message script

#! /bin/sh

shellcheck ./*.sh

# mac os specific
# brew install protobuf

dart pub global activate protoc_plugin
export PATH="$PATH:$HOME/.pub-cache/bin"
protoc --dart_out=grpc:lib/src/generated -Iprotos protos/messages.proto

MateusAmin avatar Jul 18 '21 01:07 MateusAmin

https://github.com/grpc/grpc-dart/blob/c982597faea6079712182b663779bf3098d25578/lib/src/client/transport/web_streams.dart#L61

Updated envoy to latest 17 and matched grpc-web echo example config. Updated to dart grpc master. Same error.

MateusAmin avatar Jul 18 '21 11:07 MateusAmin

HAR: localhost.zip

HAR without _initiator:

{
  "log": {
    "version": "1.2",
    "creator": {
      "name": "WebInspector",
      "version": "537.36"
    },
    "pages": [],
    "entries": [
      {
        "_initiator": {},
        "_priority": "High",
        "_resourceType": "xhr",
        "cache": {},
        "connection": "150085",
        "request": {
          "method": "POST",
          "url": "http://localhost:8080/messages.Greeter/ResetPassword",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "Host",
              "value": "localhost:8080"
            },
            {
              "name": "Connection",
              "value": "keep-alive"
            },
            {
              "name": "Content-Length",
              "value": "28"
            },
            {
              "name": "sec-ch-ua",
              "value": "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\""
            },
            {
              "name": "X-User-Agent",
              "value": "grpc-web-dart/0.1"
            },
            {
              "name": "X-Grpc-Web",
              "value": "1"
            },
            {
              "name": "sec-ch-ua-mobile",
              "value": "?0"
            },
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36"
            },
            {
              "name": "Content-Type",
              "value": "application/grpc-web+proto"
            },
            {
              "name": "Accept",
              "value": "*/*"
            },
            {
              "name": "Origin",
              "value": "http://localhost:34893"
            },
            {
              "name": "Sec-Fetch-Site",
              "value": "same-site"
            },
            {
              "name": "Sec-Fetch-Mode",
              "value": "cors"
            },
            {
              "name": "Sec-Fetch-Dest",
              "value": "empty"
            },
            {
              "name": "Referer",
              "value": "http://localhost:34893/"
            },
            {
              "name": "Accept-Encoding",
              "value": "gzip, deflate, br"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.9"
            }
          ],
          "queryString": [],
          "cookies": [],
          "headersSize": 641,
          "bodySize": 28,
          "postData": {
            "mimeType": "application/grpc-web+proto",
            "text": "\u0000\u0000\u0000\u0000\u0017\n\[email protected]"
          }
        },
        "response": {
          "status": 200,
          "statusText": "OK",
          "httpVersion": "HTTP/1.1",
          "headers": [
            {
              "name": "content-type",
              "value": "application/grpc-web+proto"
            },
            {
              "name": "grpc-encoding",
              "value": "identity"
            },
            {
              "name": "x-envoy-upstream-service-time",
              "value": "1223"
            },
            {
              "name": "access-control-allow-origin",
              "value": "http://localhost:34893"
            },
            {
              "name": "access-control-expose-headers",
              "value": "custom-header-1,grpc-status,grpc-message"
            },
            {
              "name": "date",
              "value": "Sun, 18 Jul 2021 12:54:58 GMT"
            },
            {
              "name": "server",
              "value": "envoy"
            },
            {
              "name": "transfer-encoding",
              "value": "chunked"
            }
          ],
          "cookies": [],
          "content": {
            "size": 27,
            "mimeType": "application/grpc-web+proto",
            "compression": -16,
            "text": "AQAAAAIIAYAAAAAPZ3JwYy1zdGF0dXM6MA0K",
            "encoding": "base64"
          },
          "redirectURL": "",
          "headersSize": 329,
          "bodySize": 43,
          "_transferSize": 372,
          "_error": null
        },
        "serverIPAddress": "127.0.0.1",
        "startedDateTime": "2021-07-18T12:54:57.572Z",
        "time": 1226.0509999947772,
        "timings": {
          "blocked": 0.6939999983739108,
          "dns": 0.00899999999999998,
          "ssl": -1,
          "connect": 0.235,
          "send": 0.08400000000000002,
          "wait": 1223.828999997614,
          "receive": 1.1999999987892807,
          "_blocked_queueing": 0.5539999983739108
        }
      }
    ]
  }
}

MateusAmin avatar Jul 18 '21 13:07 MateusAmin

So, I am looking at: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md

I can see that it should be:

  • 10000000b - trailers without compression
  • 10000001b - trailers with compression
  • 00000000b - data

with the most significant bit last

MateusAmin avatar Jul 18 '21 13:07 MateusAmin

Added minimal example to replicate. Only need docker, dart, and flutter. 3 steps:

https://github.com/MateusAmin/grpc-dart-issues-506

MateusAmin avatar Jul 18 '21 13:07 MateusAmin

Looks like I "fixed" the issue by removing the indentity codec from the server constructor.

  final server = Server(
    [GreeterService()],
    const <Interceptor>[],
    CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),
  );
  final server = Server(
    [GreeterService()],
    const <Interceptor>[],
    CodecRegistry(codecs: const [GzipCodec(),]),
  );

MateusAmin avatar Jul 18 '21 13:07 MateusAmin

https://grpc.github.io/grpc/core/md_doc_compression.html

I take it the GzipCodec() + IdentityCodec() purpose is to be used to prevent CRIME/BEAST attacks?

MateusAmin avatar Jul 18 '21 14:07 MateusAmin

@MateusAmin I have the same problem. A very ugly workaround is to change https://github.com/grpc/grpc-dart/blob/master/lib/src/client/transport/web_streams.dart#L57:L65

  int _parseFrameType(List<int> chunkData) {
    final frameType = chunkData[_chunkOffset];
    _chunkOffset++;
    const frameTypeDataWebWorkaround = 0x01;
    if (frameType != frameTypeData &&
        frameType != frameTypeTrailers &&
        frameType != frameTypeDataWebWorkaround) {
      throw GrpcError.unimplemented('Invalid frame type: ${frameType}');
    }
    _state = _GrpcWebParseState.Length;
    if (frameType == frameTypeDataWebWorkaround) {
      return frameTypeData;
    }
    return frameType;
  }

tomk9 avatar Apr 21 '22 14:04 tomk9

Looks like I "fixed" the issue by removing the indentity codec from the server constructor.

  final server = Server(
    [GreeterService()],
    const <Interceptor>[],
    CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),
  );
  final server = Server(
    [GreeterService()],
    const <Interceptor>[],
    CodecRegistry(codecs: const [GzipCodec(),]),
  );

This solution works for me.

tomk9 avatar Apr 21 '22 16:04 tomk9