gRPC + Dart, Service + Client, shall we write? Part 5: Web

Hi, I’m Andrey, I work as a Flutter developer at Finam.

After the release of the 4th part, where we connected the mobile Flutter application to the Umka service, I received many questions from readers who tried to launch the Web version of the application and it did not work in the browser.

So will a Flutter gRPC service run on the Web?

TLDR: Yes, but you won’t be able to “stream” from the client side, and everything else will work. This requires dance with a tambourine convert service requests and responses into a format understandable for the browser. Can be used Envoy as a Web proxy, which out of the box supports incoming / outgoing gRPC requests.

Below I will show you how to do this. I want to note that Google is working on the development of gRPC for the Web and over time the need for a “middleman” may disappear.

Configuration for Envoy proxy

Let’s put umka_envoy.yaml file to the project of our service. The configuration looks like this:

static_resources:
  listeners:
  - name: umka_listener
    address:
      socket_address: { address: 0.0.0.0, port_value: 8888 }
    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: umka_route
            virtual_hosts:
            - name: umka_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: umka_service
                  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
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.router
  clusters:
  - name: umka_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: 0.0.0.0
                    port_value: 5555

Briefly, the essence is as follows: by the Umka client application running in the browser, we will send requests and receive responses to the Envoy server by interacting with it through the port 8888… The proxy, in turn, will redirect these requests converted to gRPC calls to the port 5555, on which we launched our service in the previous four parts. The cluster in our example will consist of one node (local computer), on which both Umka sevice and Envoy will run.

At Envoy good documentation and if you wish, you can get acquainted with this wonderful product in more detail.

Refinement of the Flutter application

Depending on whether the Web version of the Flutter application is running or the mobile version, the client channel for remote gRPC calls needs to be built in different ways.

By default, the channel is created like this:

ClientChannel buildChannel({
    required String host,
    int port = 443,
    bool secure = true,
  }) {
    return ClientChannel(host,
        port: port,
        options: ChannelOptions(
            credentials: secure
                ? ChannelCredentials.secure()
                : ChannelCredentials.insecure()));
}

In the case of launching on the Web like this:

ClientChannel buildChannel({
    required String host,
    int port = 443,
  }) {
    return GrpcWebClientChannel.xhr(Uri.parse('$host:$port'));
}

I wrote a little utility build_grpc_channel, which is exactly what “does”.

Let’s add it to dependencies of our application:

dependencies:
  ...
  build_grpc_channel:
  ...

Let’s slightly change the class code UmkaService:

const host="http://127.0.0.1";

int get port => kIsWeb ? 8888 : 5555;

class UmkaService {
  late final UmkaClient stub;

  UmkaService() {
    stub = UmkaClient(buildGrpcChannel(host: host, port: port, secure: false));
  }
  ...
}

We build the channel using the method buildGrpcChannel(host: host, port: port, secure: false) from utility build_grpc_channel… When launched on the Web, we pass port 8888, on which Envoy will listen for requests.

These are all the changes you need to make for the application to work in the browser.

Launch

The car must be installed Envoy

Installation example on a Mac: brew install envoy

From the directory where the config file is located umka_envoy.yaml run the command to start the proxy server:

envoy --config-path umka_envoy.yaml or envoy -c umka_envoy.yaml

Let’s build a project for working on the Web and go to its directory:

flutter build web && cd build/web

Let’s start the local server from this directory build/webwhere the file is located index.html, for example php:

php -S localhost:8080

Or using Python:

python -m http.server 8080

Now you can open the address in the browser localhost:8080 and check the operation of the application.

A spoon of tar…

We see that the tabs Quiz and Tutorial work exactly the same as the mobile version of the application, but here is a tab Exam does not work. This happens because the code for the exam involves the use of data flow from the “client” to the service

rpc takeExam(stream Answer) returns(Evaluation) {},

and this, at the moment, is not supported in gRPC Web.

The stream works in the direction from the service to the “client”. We can see this by the normal operation of the tab. Tutorialwhere the call is used:

rpc getTutorial(Student) returns (stream AnsweredQuestion) {}

Thanks to everyone who followed this series of articles or read later. Hope it was helpful.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *