This guide is intended to help you get started with gRPC-Web with a simple Hello World example. For more information about the gRPC-Web project as a whole, please visit the main repo.
All the code for this example can be found in this current directory.
$ cd net/grpc/gateway/examples/helloworld
First, let's define a gRPC service using
protocol buffers. Put this
in the helloworld.proto
file. Here we define a request message, a response
message, and a service with one RPC method: SayHello
.
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Then, we need to implement the gRPC Service. In this example, we will use
NodeJS. Put this in a server.js
file. Here, we receive the client request,
and we can access the message field via call.request.name
. Then we construct
a nice response and send it back to the client via callback(null, response)
.
var PROTO_PATH = __dirname + '/helloworld.proto';
var assert = require('assert');
var grpc = require('@grpc/grpc-js');
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, callback) {
callback(null, {
message: 'Hello! ' + call.request.name
});
}
function getServer() {
var server = new grpc.Server();
server.addService(helloworld.Greeter.service, {
sayHello: doSayHello,
});
return server;
}
if (require.main === module) {
var server = getServer();
server.bindAsync(
'0.0.0.0:9090', grpc.ServerCredentials.createInsecure(), (err, port) => {
assert.ifError(err);
server.start();
});
}
exports.getServer = getServer;
Next up, we need to configure the Envoy proxy to forward the browser's gRPC-Web
requests to the backend. Put this in an envoy.yaml
file. Here we configure
Envoy to listen at port :8080
, and forward any gRPC-Web requests to a
cluster at port :9090
.
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.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_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: greeter_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: 9090
NOTE: As per this issue: if you are running Docker on Mac/Windows, change the last
address: 0.0.0.0
to... socket_address: address: host.docker.internalor if your version of Docker on Mac older then v18.03.0, change it to:
... socket_address: address: docker.for.mac.localhost
Now, we are ready to write some client code! Put this in a client.js
file.
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();
request.setName('World');
client.sayHello(request, {}, (err, response) => {
console.log(response.getMessage());
});
The classes HelloRequest
, HelloReply
and GreeterClient
we import here are
generated for you by the protoc
generator utility (which we will cover in the
next section) from the helloworld.proto
file we defined earlier.
Then we instantiate a GreeterClient
instance, set the field in the
HelloRequest
protobuf object, and we can make a gRPC call via
client.sayHello()
, just like how we defined in the helloworld.proto
file.
You will need a package.json
file. This is needed for both the server.js
and
the client.js
files.
{
"name": "grpc-web-simple-example",
"version": "0.1.0",
"description": "gRPC-Web simple example",
"main": "server.js",
"devDependencies": {
"@grpc/grpc-js": "~1.0.5",
"@grpc/proto-loader": "~0.5.4",
"async": "~1.5.2",
"google-protobuf": "~3.14.0",
"grpc-web": "~1.3.0",
"lodash": "~4.17.0",
"webpack": "~4.43.0",
"webpack-cli": "~3.3.11"
}
}
And finally a simple index.html
file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>gRPC-Web Example</title>
<script src="./dist/main.js"></script>
</head>
<body>
<p>Open up the developer console and see the logs for the output.</p>
</body>
</html>
The ./dist/main.js
file will be generated by webpack
(which will be covered
in the next section).
And that's it! We have all the code ready. Let's run the example!
To generate the protobuf messages and client service stub class from your
.proto
definitions, we need:
- the
protoc
binary, and - the
protoc-gen-grpc-web
plugin.
You can download the
protoc-gen-grpc-web
protoc plugin from our release page.If you don't already have
protoc
installed, you will have to download it first from here.Make sure they are both executable and are discoverable from your PATH.
For example, in MacOS, you can do:
$ sudo mv ~/Downloads/protoc-gen-grpc-web-1.3.0-darwin-x86_64 \ /usr/local/bin/protoc-gen-grpc-web $ sudo chmod +x /usr/local/bin/protoc-gen-grpc-web
When you have both protoc
and protoc-gen-grpc-web
installed, you can now
run this command:
$ protoc -I=. helloworld.proto \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
After the command runs successfully, you should now see two new files generated in the current directory:
helloworld_pb.js
: this contains theHelloRequest
andHelloReply
classeshelloworld_grpc_web_pb.js
: this contains theGreeterClient
class
These are also the 2 files that our client.js
file imported earlier in the
example.
Next, we need to compile the client side JavaScript code into something that can be consumed by the browser.
$ npm install
$ npx webpack client.js
Here we use webpack
and give it an entry point client.js
. You can also use
browserify
or other similar tools. This will resolve all the require()
statements and produce a ./dist/main.js
file that can be embedded in our
index.html
file.
We are ready to run the Hello World example. The following set of commands will run the 3 processes all in the background.
- Run the NodeJS gRPC Service. This listens at port
:9090
.
$ node server.js &
- Run the Envoy proxy. The
envoy.yaml
file configures Envoy to listen to browser requests at port:8080
, and forward them to port:9090
(see above).
$ docker run -d -v "$(pwd)"/envoy.yaml:/etc/envoy/envoy.yaml:ro \
--network=host envoyproxy/envoy:v1.20.0
NOTE: As per this issue: if you are running Docker on Mac/Windows, remove the
--network=host
option:$ docker run -d -v "$(pwd)"/envoy.yaml:/etc/envoy/envoy.yaml:ro \ -p 8080:8080 -p 9901:9901 envoyproxy/envoy:v1.20.0
- Run the simple Web Server. This hosts the static file
index.html
anddist/main.js
we generated earlier.
$ python3 -m http.server 8081 &
When these are all ready, you can open a browser tab and navigate to
localhost:8081
Open up the developer console and you should see the following printed out:
Hello! World