-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tonic-web): implement grpc <-> grpc-web protocol translation (#455)
tonic-web enables tonic servers to handle requests from grpc-web clients directly, without the need of an external proxy. Co-authored-by: John Hernandez <[email protected]> Co-authored-by: zancas <[email protected]>
- Loading branch information
1 parent
352b0f5
commit c309063
Showing
24 changed files
with
2,445 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
node_modules | ||
/target | ||
binary | ||
text | ||
run.sh | ||
package-lock.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[workspace] | ||
members = [ | ||
"tonic-web", | ||
"interop", | ||
"tests/integration" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* | ||
!client/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "web-interop" | ||
version = "0.1.0" | ||
authors = ["Juan Alvarez <[email protected]>"] | ||
publish = false | ||
edition = "2018" | ||
|
||
[dependencies] | ||
interop = { path = "../../interop" } | ||
tonic = { path = "../../tonic" } | ||
tonic-web = { path = "../tonic-web" } | ||
tokio = { version = "1.0.1", features = ["rt-multi-thread", "macros"] } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
FROM node:12-stretch | ||
|
||
RUN apt-get install -y unzip | ||
|
||
WORKDIR /tmp | ||
|
||
RUN curl -sSL https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/\ | ||
protoc-3.14.0-linux-x86_64.zip -o protoc.zip && \ | ||
unzip -qq protoc.zip && \ | ||
cp ./bin/protoc /usr/local/bin/protoc | ||
|
||
RUN curl -sSL https://github.com/grpc/grpc-web/releases/download/1.2.1/\ | ||
protoc-gen-grpc-web-1.2.1-linux-x86_64 -o /usr/local/bin/protoc-gen-grpc-web && \ | ||
chmod +x /usr/local/bin/protoc-gen-grpc-web | ||
|
||
WORKDIR / | ||
|
||
COPY ./client ./ | ||
|
||
RUN echo "\nloglevel=error\n" >> $HOME/.npmrc && npm install && mkdir -p binary text | ||
|
||
RUN protoc -I=. ./test.proto\ | ||
--js_out=import_style=commonjs:./text\ | ||
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:./text | ||
|
||
RUN protoc -I=. ./test.proto\ | ||
--js_out=import_style=commonjs:./binary\ | ||
--grpc-web_out=import_style=commonjs,mode=grpcweb:./binary |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
## Running interop tests | ||
|
||
Start the server: | ||
|
||
```bash | ||
cd tonic-web/interop | ||
cargo run | ||
``` | ||
Build the client docker image: | ||
|
||
```bash | ||
cd tonic-web/interop | ||
docker build -t grpcweb-client . | ||
``` | ||
Run tests on linux: | ||
|
||
```bash | ||
docker run --network=host --rm grpcweb-client /test.sh | ||
``` | ||
Run tests on docker desktop: | ||
|
||
```bash | ||
docker run --rm grpcweb-client /test.sh host.docker.internal | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/** | ||
* | ||
* 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. | ||
* | ||
*/ | ||
|
||
// Adapted from https://github.com/grpc/grpc-web/tree/master/test/interop | ||
|
||
global.XMLHttpRequest = require("xhr2"); | ||
|
||
const parseArgs = require('minimist'); | ||
const argv = parseArgs(process.argv, { | ||
string: ['mode', 'host'] | ||
}); | ||
|
||
const SERVER_HOST = `http://${argv.host || "localhost"}:9999`; | ||
|
||
if (argv.mode === 'binary') { | ||
console.log('Testing tonic-web mode (binary)...'); | ||
} else { | ||
console.log('Testing tonic-web mode (text)...'); | ||
} | ||
console.log('Tonic server:', SERVER_HOST); | ||
|
||
const PROTO_PATH = argv.mode === 'binary' ? './binary' : './text'; | ||
|
||
const { | ||
Empty, | ||
SimpleRequest, | ||
StreamingOutputCallRequest, | ||
EchoStatus, | ||
Payload, | ||
ResponseParameters | ||
} = require(`${PROTO_PATH}/test_pb.js`); | ||
|
||
const {TestServiceClient} = require(`${PROTO_PATH}/test_grpc_web_pb.js`); | ||
|
||
const assert = require('assert'); | ||
const grpc = {}; | ||
grpc.web = require('grpc-web'); | ||
|
||
function multiDone(done, count) { | ||
return function () { | ||
count -= 1; | ||
if (count <= 0) { | ||
done(); | ||
} | ||
}; | ||
} | ||
|
||
function doEmptyUnary(done) { | ||
const testService = new TestServiceClient(SERVER_HOST, null, null); | ||
testService.emptyCall(new Empty(), null, (err, response) => { | ||
assert.ifError(err); | ||
assert(response instanceof Empty); | ||
done(); | ||
}); | ||
} | ||
|
||
function doLargeUnary(done) { | ||
const testService = new TestServiceClient(SERVER_HOST, null, null); | ||
const req = new SimpleRequest(); | ||
const size = 314159; | ||
|
||
const payload = new Payload(); | ||
payload.setBody('0'.repeat(271828)); | ||
|
||
req.setPayload(payload); | ||
req.setResponseSize(size); | ||
|
||
testService.unaryCall(req, null, (err, response) => { | ||
assert.ifError(err); | ||
assert.equal(response.getPayload().getBody().length, size); | ||
done(); | ||
}); | ||
} | ||
|
||
function doServerStreaming(done) { | ||
const testService = new TestServiceClient(SERVER_HOST, null, null); | ||
const sizes = [31415, 9, 2653, 58979]; | ||
|
||
const responseParams = sizes.map((size, idx) => { | ||
const param = new ResponseParameters(); | ||
param.setSize(size); | ||
param.setIntervalUs(idx * 10); | ||
return param; | ||
}); | ||
|
||
const req = new StreamingOutputCallRequest(); | ||
req.setResponseParametersList(responseParams); | ||
|
||
const stream = testService.streamingOutputCall(req); | ||
|
||
done = multiDone(done, sizes.length); | ||
let numCallbacks = 0; | ||
stream.on('data', (response) => { | ||
assert.equal(response.getPayload().getBody().length, sizes[numCallbacks]); | ||
numCallbacks++; | ||
done(); | ||
}); | ||
} | ||
|
||
function doCustomMetadata(done) { | ||
const testService = new TestServiceClient(SERVER_HOST, null, null); | ||
done = multiDone(done, 3); | ||
|
||
const req = new SimpleRequest(); | ||
const size = 314159; | ||
const ECHO_INITIAL_KEY = 'x-grpc-test-echo-initial'; | ||
const ECHO_INITIAL_VALUE = 'test_initial_metadata_value'; | ||
const ECHO_TRAILING_KEY = 'x-grpc-test-echo-trailing-bin'; | ||
const ECHO_TRAILING_VALUE = 0xababab; | ||
|
||
const payload = new Payload(); | ||
payload.setBody('0'.repeat(271828)); | ||
|
||
req.setPayload(payload); | ||
req.setResponseSize(size); | ||
|
||
const call = testService.unaryCall(req, { | ||
[ECHO_INITIAL_KEY]: ECHO_INITIAL_VALUE, | ||
[ECHO_TRAILING_KEY]: ECHO_TRAILING_VALUE | ||
}, (err, response) => { | ||
assert.ifError(err); | ||
assert.equal(response.getPayload().getBody().length, size); | ||
done(); | ||
}); | ||
|
||
call.on('metadata', (metadata) => { | ||
assert(ECHO_INITIAL_KEY in metadata); | ||
assert.equal(metadata[ECHO_INITIAL_KEY], ECHO_INITIAL_VALUE); | ||
done(); | ||
}); | ||
|
||
call.on('status', (status) => { | ||
assert('metadata' in status); | ||
assert(ECHO_TRAILING_KEY in status.metadata); | ||
assert.equal(status.metadata[ECHO_TRAILING_KEY], ECHO_TRAILING_VALUE); | ||
done(); | ||
}); | ||
} | ||
|
||
function doStatusCodeAndMessage(done) { | ||
const testService = new TestServiceClient(SERVER_HOST, null, null); | ||
const req = new SimpleRequest(); | ||
|
||
const TEST_STATUS_MESSAGE = 'test status message'; | ||
const echoStatus = new EchoStatus(); | ||
echoStatus.setCode(2); | ||
echoStatus.setMessage(TEST_STATUS_MESSAGE); | ||
|
||
req.setResponseStatus(echoStatus); | ||
|
||
testService.unaryCall(req, {}, (err, response) => { | ||
assert(err); | ||
assert('code' in err); | ||
assert('message' in err); | ||
assert.equal(err.code, 2); | ||
assert.equal(err.message, TEST_STATUS_MESSAGE); | ||
done(); | ||
}); | ||
} | ||
|
||
function doUnimplementedMethod(done) { | ||
const testService = new TestServiceClient(SERVER_HOST, null, null); | ||
testService.unimplementedCall(new Empty(), {}, (err, response) => { | ||
assert(err); | ||
assert('code' in err); | ||
assert.equal(err.code, 12); | ||
done(); | ||
}); | ||
} | ||
|
||
const testCases = { | ||
'empty_unary': {testFunc: doEmptyUnary}, | ||
'large_unary': {testFunc: doLargeUnary}, | ||
'server_streaming': { | ||
testFunc: doServerStreaming, | ||
skipBinaryMode: true | ||
}, | ||
'custom_metadata': {testFunc: doCustomMetadata}, | ||
'status_code_and_message': {testFunc: doStatusCodeAndMessage}, | ||
'unimplemented_method': {testFunc: doUnimplementedMethod} | ||
}; | ||
|
||
|
||
describe('tonic-web interop tests', function () { | ||
Object.keys(testCases).forEach((testCase) => { | ||
if (argv.mode === 'binary' && testCases[testCase].skipBinaryMode) return; | ||
it('should pass ' + testCase, testCases[testCase].testFunc); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "grpc-web-interop-test", | ||
"version": "0.1.0", | ||
"description": "gRPC-Web Interop Test Client", | ||
"license": "Apache-2.0", | ||
"private": true, | ||
"scripts": { | ||
"test": "mocha -b --timeout 500 ./interop_client.js" | ||
}, | ||
"dependencies": { | ||
"google-protobuf": "~3.14.0", | ||
"grpc-web": "~1.2.1" | ||
}, | ||
"devDependencies": { | ||
"minimist": "~1.2.5", | ||
"mocha": "~7.1.1", | ||
"xhr2": "~0.2.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
|
||
// Copyright 2015-2016 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. | ||
|
||
// Adapted from https://github.com/grpc/grpc-web/tree/master/src/proto/grpc/testing | ||
|
||
syntax = "proto3"; | ||
|
||
package grpc.testing; | ||
|
||
service TestService { | ||
rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty); | ||
rpc UnaryCall(SimpleRequest) returns (SimpleResponse); | ||
rpc StreamingOutputCall(StreamingOutputCallRequest) | ||
returns (stream StreamingOutputCallResponse); | ||
rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty); | ||
} | ||
|
||
message Empty {} | ||
|
||
message BoolValue { | ||
bool value = 1; | ||
} | ||
|
||
message Payload { | ||
bytes body = 2; | ||
} | ||
|
||
message EchoStatus { | ||
int32 code = 1; | ||
string message = 2; | ||
} | ||
|
||
message SimpleRequest { | ||
int32 response_size = 2; | ||
Payload payload = 3; | ||
EchoStatus response_status = 7; | ||
} | ||
|
||
message SimpleResponse { | ||
Payload payload = 1; | ||
} | ||
|
||
message ResponseParameters { | ||
int32 size = 1; | ||
int32 interval_us = 2; | ||
} | ||
|
||
message StreamingOutputCallRequest { | ||
repeated ResponseParameters response_parameters = 2; | ||
Payload payload = 3; | ||
EchoStatus response_status = 7; | ||
} | ||
|
||
message StreamingOutputCallResponse { | ||
Payload payload = 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/bin/bash | ||
|
||
set -e | ||
|
||
HOST=${1:-"localhost"} | ||
|
||
npm test -- --host="$HOST" | ||
npm test -- --mode=binary --host="$HOST" |
Oops, something went wrong.