Add Node gRPC Server to Example

This commit is contained in:
Stanley Cheung 2018-10-02 01:41:07 -07:00 committed by Stanley Cheung
parent 20930ce66f
commit 3f4cc9e1dc
18 changed files with 179 additions and 101 deletions

View File

@ -23,8 +23,8 @@ Try gRPC-Web and run a quick Echo example from the browser!
From the repo root directory:
```sh
$ docker-compose pull prereqs common echo-server envoy commonjs-client
$ docker-compose up -d echo-server envoy commonjs-client
$ docker-compose pull prereqs common node-server envoy commonjs-client
$ docker-compose up -d node-server envoy commonjs-client
```
Open a browser tab, and go to:
@ -52,6 +52,14 @@ You can compile the `protoc-gen-grpc-web` protoc plugin from this repo:
$ sudo make install-plugin
```
If you don't already have `protoc` installed, you may have to do this first:
```sh
$ ./scripts/init_submodules.sh
$ cd third_party/grpc/third_party/protobuf
$ ./autogen.sh && ./configure && make -j8 && sudo make install
```
## Client Configuration Options
@ -138,10 +146,10 @@ service EchoService {
Next you need to have a gRPC server that implements the service interface and a
gateway proxy that allows the client to connect to the server. Our example
builds a simple C++ gRPC backend server and the Envoy proxy.
builds a simple Node gRPC backend server and the Envoy proxy.
For the Echo service: see the
[service implementations](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/echo_service_impl.cc).
[service implementations](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/node-server/server.js).
For the Envoy proxy: see the
[config yaml file](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/envoy.yaml).
@ -239,20 +247,20 @@ Multiple proxies supports the gRPC-Web protocol. Currently, the default proxy
is [Envoy](https://www.envoyproxy.io), which supports gRPC-Web out of the box.
```sh
$ docker-compose up -d echo-server envoy commonjs-client
$ docker-compose up -d node-server envoy commonjs-client
```
An alternative is to build Nginx that comes with this repository.
```sh
$ docker-compose up -d echo-server nginx commonjs-client
$ docker-compose up -d node-server nginx commonjs-client
```
You can also try this
[gRPC-Web Go Proxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy).
```sh
$ docker-compose up -d echo-server grpcwebproxy binary-client
$ docker-compose up -d node-server grpcwebproxy binary-client
```
## Acknowledgement

View File

@ -21,6 +21,15 @@ services:
image: grpcweb/echo-server
ports:
- "9090:9090"
node-server:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/node_server/Dockerfile
depends_on:
- common
image: grpcweb/node-server
ports:
- "9090:9090"
envoy:
build:
context: ./
@ -29,7 +38,7 @@ services:
ports:
- "8080:8080"
links:
- echo-server
- node-server
nginx:
build:
context: ./
@ -40,7 +49,7 @@ services:
ports:
- "8080:8080"
links:
- echo-server
- node-server
grpcwebproxy:
build:
context: ./
@ -49,7 +58,7 @@ services:
ports:
- "8080:8080"
links:
- echo-server
- node-server
commonjs-client:
build:
context: ./

View File

@ -32,7 +32,7 @@ ADD ./etc/localhost.crt /etc
ADD ./etc/localhost.key /etc
ENTRYPOINT [ "/bin/sh", "-c", "exec /go/bin/grpcwebproxy \
--backend_addr=echo-server:9090 \
--backend_addr=node-server:9090 \
--server_bind_address=0.0.0.0 \
--server_http_debug_port=8080 \
--run_http_server=true \

View File

@ -15,7 +15,7 @@
FROM grpcweb/common
RUN cd /github/grpc-web-base/net/grpc/gateway/examples/echo && \
sed -i 's/localhost:9090/echo-server:9090/g' nginx.conf
sed -i 's/localhost:9090/node-server:9090/g' nginx.conf
RUN cd /github/grpc-web-base && \
make standalone-proxy

View File

@ -0,0 +1,21 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM grpcweb/common
RUN cd /github/grpc-web/net/grpc/gateway/examples/echo/node-server && \
npm install
EXPOSE 9090
CMD ["node", "/github/grpc-web/net/grpc/gateway/examples/echo/node-server/server.js"]

View File

@ -5,7 +5,7 @@ example. The example has 3 key components:
- Front-end JS client
- Envoy proxy
- gRPC backend server (written in C++)
- gRPC backend server (written in Node)
## Before you start
@ -32,13 +32,13 @@ $ docker build -t grpcweb/prereqs \
## Run the gRPC Backend server
This compiles the gRPC backend server, written in C++, and listens on port
This compiles the gRPC backend server, written in Node, and listens on port
9090.
```sh
$ docker build -t grpcweb/echo-server \
-f net/grpc/gateway/docker/echo_server/Dockerfile .
$ docker run -d -p 9090:9090 --name echo-server grpcweb/echo-server
$ docker build -t grpcweb/node-server \
-f net/grpc/gateway/docker/node_server/Dockerfile .
$ docker run -d -p 9090:9090 --name node-server grpcweb/node-server
```
## Run the Envoy proxy
@ -49,7 +49,7 @@ requests will be forwarded to port 9090.
```sh
$ docker build -t grpcweb/envoy \
-f net/grpc/gateway/docker/envoy/Dockerfile .
$ docker run -d -p 8080:8080 --link echo-server:echo-server grpcweb/envoy
$ docker run -d -p 8080:8080 --link node-server:node-server grpcweb/envoy
```
## Serve static JS/HTML contents

View File

@ -23,7 +23,7 @@ const {EchoApp} = require('../echoapp.js');
const grpc = {};
grpc.web = require('grpc-web');
var echoService = new EchoServiceClient('http://localhost:8080', null, null);
var echoService = new EchoServiceClient('http://'+window.location.hostname+':8080', null, null);
var echoApp = new EchoApp(
echoService,

View File

@ -16,16 +16,12 @@ syntax = "proto3";
package grpc.gateway.testing;
message Empty {
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
int32 message_count = 2;
}
// Request type for server side streaming echo.
@ -48,18 +44,6 @@ message ServerStreamingEchoResponse {
string message = 1;
}
// Request type for client side streaming echo.
message ClientStreamingEchoRequest {
// A special value "" indicates that there's no further messages.
string message = 1;
}
// Response type for client side streaming echo.
message ClientStreamingEchoResponse {
// Total number of client messages that have been received.
int32 message_count = 1;
}
// A simple echo service.
service EchoService {
// One request followed by one response
@ -70,34 +54,8 @@ service EchoService {
rpc EchoAbort(EchoRequest) returns (EchoResponse) {
};
// One empty request, ZERO processing, followed by one empty response
// (minimum effort to do message serialization).
rpc NoOp(Empty) returns (Empty);
// One request followed by a sequence of responses (streamed download).
// The server will return the same client message repeatedly.
rpc ServerStreamingEcho(ServerStreamingEchoRequest)
returns (stream ServerStreamingEchoResponse);
// One request followed by a sequence of responses (streamed download).
// The server abort directly.
rpc ServerStreamingEchoAbort(ServerStreamingEchoRequest)
returns (stream ServerStreamingEchoResponse) {
}
// A sequence of requests followed by one response (streamed upload).
// The server returns the total number of messages as the result.
rpc ClientStreamingEcho(stream ClientStreamingEchoRequest)
returns (ClientStreamingEchoResponse);
// A sequence of requests with each message echoed by the server immediately.
// The server returns the same client messages in order.
// E.g. this is how the speech API works.
rpc FullDuplexEcho(stream EchoRequest) returns (stream EchoResponse);
// A sequence of requests followed by a sequence of responses.
// The server buffers all the client messages and then returns the same
// client messages one by one after the client half-closes the stream.
// This is how an image recognition API may work.
rpc HalfDuplexEcho(stream EchoRequest) returns (stream EchoResponse);
}

View File

@ -30,7 +30,6 @@ using grpc::Status;
using grpc::gateway::testing::EchoRequest;
using grpc::gateway::testing::EchoResponse;
using grpc::gateway::testing::EchoService;
using grpc::gateway::testing::Empty;
using grpc::gateway::testing::ServerStreamingEchoRequest;
using grpc::gateway::testing::ServerStreamingEchoResponse;
@ -68,12 +67,6 @@ Status EchoServiceImpl::EchoAbort(ServerContext* context,
"Aborted from server side.");
}
Status EchoServiceImpl::NoOp(ServerContext* context, const Empty* request,
Empty* response) {
CopyClientMetadataToResponse(context);
return Status::OK;
}
Status EchoServiceImpl::ServerStreamingEcho(
ServerContext* context, const ServerStreamingEchoRequest* request,
ServerWriter<ServerStreamingEchoResponse>* writer) {
@ -89,14 +82,3 @@ Status EchoServiceImpl::ServerStreamingEcho(
}
return Status::OK;
}
Status EchoServiceImpl::ServerStreamingEchoAbort(
ServerContext* context, const ServerStreamingEchoRequest* request,
ServerWriter<ServerStreamingEchoResponse>* writer) {
CopyClientMetadataToResponse(context);
ServerStreamingEchoResponse response;
response.set_message(request->message());
writer->Write(response);
return Status(grpc::StatusCode::ABORTED,
"Aborted from server side.");
}

View File

@ -40,20 +40,11 @@ class EchoServiceImpl final :
grpc::ServerContext* context,
const grpc::gateway::testing::EchoRequest* request,
grpc::gateway::testing::EchoResponse* response) override;
grpc::Status NoOp(
grpc::ServerContext* context,
const grpc::gateway::testing::Empty* request,
grpc::gateway::testing::Empty* response) override;
grpc::Status ServerStreamingEcho(
grpc::ServerContext* context,
const grpc::gateway::testing::ServerStreamingEchoRequest* request,
grpc::ServerWriter<
grpc::gateway::testing::ServerStreamingEchoResponse>* writer) override;
grpc::Status ServerStreamingEchoAbort(
grpc::ServerContext* context,
const grpc::gateway::testing::ServerStreamingEchoRequest* request,
grpc::ServerWriter<
grpc::gateway::testing::ServerStreamingEchoResponse>* writer) override;
};
#endif // NET_GRPC_GATEWAY_EXAMPLES_ECHO_ECHO_SERVICE_IMPL_H_

View File

@ -40,4 +40,4 @@ static_resources:
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
hosts: [{ socket_address: { address: echo-server, port_value: 9090 }}]
hosts: [{ socket_address: { address: node-server, port_value: 9090 }}]

View File

@ -0,0 +1,2 @@
node_modules/
package-lock.json

View File

@ -0,0 +1,11 @@
{
"name": "grpc-web-node-server-example",
"version": "0.1.0",
"dependencies": {
"@grpc/proto-loader": "^0.3.0",
"async": "^1.5.2",
"google-protobuf": "^3.6.0",
"grpc": "^1.15.0",
"lodash": "^4.6.1"
}
}

View File

@ -0,0 +1,99 @@
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var PROTO_PATH = __dirname + '/../echo.proto';
var async = require('async');
var _ = require('lodash');
var grpc = require('grpc');
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 echo = protoDescriptor.grpc.gateway.testing;
function copyMetadata(call) {
var metadata = call.metadata.getMap();
var response_metadata = new grpc.Metadata();
for (var key in metadata) {
response_metadata.set(key, metadata[key]);
}
return response_metadata;
}
function doEcho(call, callback) {
callback(null, {
message: call.request.message
}, copyMetadata(call));
}
function doEchoAbort(call, callback) {
callback({
code: grpc.status.ABORTED,
message: 'Aborted from server side.'
});
}
function doServerStreamingEcho(call) {
var senders = [];
function sender(message, interval) {
return (callback) => {
call.write({
message: message
});
_.delay(callback, interval);
};
}
for (var i = 0; i < call.request.message_count; i++) {
senders[i] = sender(call.request.message, call.request.message_interval);
}
async.series(senders, () => {
call.end(copyMetadata(call));
});
}
/**
* Get a new server with the handler functions in this file bound to the methods
* it serves.
* @return {Server} The new server object
*/
function getServer() {
var server = new grpc.Server();
server.addProtoService(echo.EchoService.service, {
echo: doEcho,
echoAbort: doEchoAbort,
serverStreamingEcho: doServerStreamingEcho,
});
return server;
}
if (require.main === module) {
// If this is run as a script, start a server on an unused port
var echoServer = getServer();
echoServer.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure());
echoServer.start();
}
exports.getServer = getServer;

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"module": "commonjs",
"noImplicitAny": true,
"allowJs": true,

View File

@ -26,19 +26,16 @@ service EchoService {
## Implement gRPC Backend Server
Next, we implement our EchoService interface using C++ in the backend gRPC
Next, we implement our EchoService interface using Node in the backend gRPC
`EchoServer`. This will handle requests from clients. See the file
[`echo_server.cc`](echo_server.cc) for details.
[`node-server/server.js`](./node-server/server.js) for details.
You can implement the server in any language supported by gRPC. Please see
the [gRPC website][] for more details.
```cpp
Status EchoServiceImpl::Echo(ServerContext* context,
const EchoRequest* request,
EchoResponse* response) {
response->set_message(request->message());
return Status::OK;
```js
function doEcho(call, callback) {
callback(null, {message: call.request.message});
}
```
@ -80,7 +77,7 @@ this:
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
hosts: [{ socket_address: { address: echo-server, port_value: 9090 }}]
hosts: [{ socket_address: { address: node-server, port_value: 9090 }}]
```
You may also need to add some CORS setup to make sure the browser can request

View File

@ -76,7 +76,7 @@ Here's a quick way to get started!
```sh
$ git clone https://github.com/grpc/grpc-web
$ cd grpc-web
$ docker-compose up echo-server envoy commonjs-client
$ docker-compose up node-server envoy commonjs-client
```
Open a browser tab, and go to:

View File

@ -44,7 +44,7 @@ cd packages/grpc-web && \
# Bring up the Echo server and the Envoy proxy (in background).
# The 'sleep' seems necessary for the docker containers to be fully up
# and listening before we test the with curl requests
docker-compose up -d echo-server envoy && sleep 5;
docker-compose up -d node-server envoy && sleep 5;
# Run a curl request and verify the output
source ./scripts/test-proxy.sh