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/web
where 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.