diff --git a/README.md b/README.md index 8ad3129..33523c3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 1468c65..6238cbb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: ./ diff --git a/net/grpc/gateway/docker/grpcwebproxy/Dockerfile b/net/grpc/gateway/docker/grpcwebproxy/Dockerfile index f616458..6ab1201 100644 --- a/net/grpc/gateway/docker/grpcwebproxy/Dockerfile +++ b/net/grpc/gateway/docker/grpcwebproxy/Dockerfile @@ -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 \ diff --git a/net/grpc/gateway/docker/nginx/Dockerfile b/net/grpc/gateway/docker/nginx/Dockerfile index 1322517..65e9a40 100644 --- a/net/grpc/gateway/docker/nginx/Dockerfile +++ b/net/grpc/gateway/docker/nginx/Dockerfile @@ -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 diff --git a/net/grpc/gateway/docker/node_server/Dockerfile b/net/grpc/gateway/docker/node_server/Dockerfile new file mode 100644 index 0000000..8dfd506 --- /dev/null +++ b/net/grpc/gateway/docker/node_server/Dockerfile @@ -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"] diff --git a/net/grpc/gateway/examples/echo/README.md b/net/grpc/gateway/examples/echo/README.md index 1aefcb7..df60071 100644 --- a/net/grpc/gateway/examples/echo/README.md +++ b/net/grpc/gateway/examples/echo/README.md @@ -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 diff --git a/net/grpc/gateway/examples/echo/commonjs-example/client.js b/net/grpc/gateway/examples/echo/commonjs-example/client.js index 9a77bca..4bfa1cb 100644 --- a/net/grpc/gateway/examples/echo/commonjs-example/client.js +++ b/net/grpc/gateway/examples/echo/commonjs-example/client.js @@ -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, diff --git a/net/grpc/gateway/examples/echo/echo.proto b/net/grpc/gateway/examples/echo/echo.proto index 4dc1678..822d74e 100644 --- a/net/grpc/gateway/examples/echo/echo.proto +++ b/net/grpc/gateway/examples/echo/echo.proto @@ -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); } diff --git a/net/grpc/gateway/examples/echo/echo_service_impl.cc b/net/grpc/gateway/examples/echo/echo_service_impl.cc index 6c77b99..ab490b0 100644 --- a/net/grpc/gateway/examples/echo/echo_service_impl.cc +++ b/net/grpc/gateway/examples/echo/echo_service_impl.cc @@ -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* writer) { @@ -89,14 +82,3 @@ Status EchoServiceImpl::ServerStreamingEcho( } return Status::OK; } - -Status EchoServiceImpl::ServerStreamingEchoAbort( - ServerContext* context, const ServerStreamingEchoRequest* request, - ServerWriter* writer) { - CopyClientMetadataToResponse(context); - ServerStreamingEchoResponse response; - response.set_message(request->message()); - writer->Write(response); - return Status(grpc::StatusCode::ABORTED, - "Aborted from server side."); -} diff --git a/net/grpc/gateway/examples/echo/echo_service_impl.h b/net/grpc/gateway/examples/echo/echo_service_impl.h index 607cc24..dc8579e 100644 --- a/net/grpc/gateway/examples/echo/echo_service_impl.h +++ b/net/grpc/gateway/examples/echo/echo_service_impl.h @@ -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_ diff --git a/net/grpc/gateway/examples/echo/envoy.yaml b/net/grpc/gateway/examples/echo/envoy.yaml index 886ba5f..d4ef15c 100644 --- a/net/grpc/gateway/examples/echo/envoy.yaml +++ b/net/grpc/gateway/examples/echo/envoy.yaml @@ -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 }}] diff --git a/net/grpc/gateway/examples/echo/node-server/.gitignore b/net/grpc/gateway/examples/echo/node-server/.gitignore new file mode 100644 index 0000000..504afef --- /dev/null +++ b/net/grpc/gateway/examples/echo/node-server/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/net/grpc/gateway/examples/echo/node-server/package.json b/net/grpc/gateway/examples/echo/node-server/package.json new file mode 100644 index 0000000..0c1b50f --- /dev/null +++ b/net/grpc/gateway/examples/echo/node-server/package.json @@ -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" + } +} diff --git a/net/grpc/gateway/examples/echo/node-server/server.js b/net/grpc/gateway/examples/echo/node-server/server.js new file mode 100644 index 0000000..b987b04 --- /dev/null +++ b/net/grpc/gateway/examples/echo/node-server/server.js @@ -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; diff --git a/net/grpc/gateway/examples/echo/ts-example/tsconfig.json b/net/grpc/gateway/examples/echo/ts-example/tsconfig.json index 3ecd837..b197406 100644 --- a/net/grpc/gateway/examples/echo/ts-example/tsconfig.json +++ b/net/grpc/gateway/examples/echo/ts-example/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "module": "commonjs", "noImplicitAny": true, "allowJs": true, diff --git a/net/grpc/gateway/examples/echo/tutorial.md b/net/grpc/gateway/examples/echo/tutorial.md index 98d347e..6ec1b2c 100644 --- a/net/grpc/gateway/examples/echo/tutorial.md +++ b/net/grpc/gateway/examples/echo/tutorial.md @@ -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 diff --git a/packages/grpc-web/README.md b/packages/grpc-web/README.md index d415466..fb24e3c 100644 --- a/packages/grpc-web/README.md +++ b/packages/grpc-web/README.md @@ -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: diff --git a/scripts/kokoro.sh b/scripts/kokoro.sh index ea552e6..917f4d4 100755 --- a/scripts/kokoro.sh +++ b/scripts/kokoro.sh @@ -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