Search code examples
node.jsstreamingbuffergrpcgrpc-web

How gRPC-web handles bytes data from server-side streaming?


I want to transmit a sample video file from backend grpc service to the browser using grpc-web, I did some tweaks based on official hello world tutorial. Btw, nothing changed in the envoy configuration. The video file is split into 17 chunks, I can receive 17 messages in browser, however there is nothing inside, what should do so I can get the data?

protobuf definition:

syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (stream HelloReply);
}

message HelloRequest {}

message HelloReply {
  bytes message = 1;
}

server.js:

var PROTO_PATH = __dirname + '/helloworld.proto';

var grpc = require('grpc');
var fs = require('fs');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
     longs: String,
     enums: String,
     defaults: true,
     oneofs: true
    });
var protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
var helloworld = protoDescriptor.helloworld;

function doSayHello(call) {
  let count = 0;
  let videoDataStream = fs.createReadStream('./sample.mp4');
  videoDataStream.on('data',function(chunk){
      console.log(chunk);
      console.log(++count);
      call.write({videoStream: chunk});
//      call.write(chunk);
  }).on('end',function(){
      call.end();
  });
}

function getServer() {
  var server = new grpc.Server();
  server.addService(helloworld.Greeter.service, {
    sayHello: doSayHello,
  });
  return server;
}

if (require.main === module) {
  var server = getServer();
  server.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure());
  server.start();
}

exports.getServer = getServer;

client.js:

const {HelloRequest, HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');

var client = new GreeterClient('http://localhost:8080');

var request = new HelloRequest();

client.sayHello(request).on('data', function(chunk){
  //console.log(chunk.getMessage());
  console.log(chunk);
});

Anyway, in case there is problem with proxy, below 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.http_connection_manager
        config:
          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.grpc_web
          - name: envoy.cors
          - name: envoy.router
  clusters:
  - name: greeter_service
    connect_timeout: 0.25s
    type: logical_dns
    http2_protocol_options: {}
    lb_policy: round_robin
    hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]

the bytes logged on server side: enter image description here

and below console output in browser: enter image description here


Solution

  • Have you tried testing with a gRPC (rather than gRPC Web) client to eliminate the possibility that the proxy is the problem?

    I'm not super familiar with the Node.JS implementation but...

    Should it not be server.addProtoService(...)?

    Also the message streamed by the server is:

    message HelloReply {
      bytes message = 1;
    }
    

    But you:

    call.write({videoStream: chunk});
    

    Should it not be:

    call.write({message: chunk});