mirror of https://github.com/grpc/grpc-web.git
Add interop spec and interop tests
This commit is contained in:
parent
0bf05fd26a
commit
ea88b10600
|
|
@ -95,3 +95,12 @@ services:
|
|||
image: grpcweb/binary-client
|
||||
ports:
|
||||
- "8081:8081"
|
||||
interop:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/interop/Dockerfile
|
||||
depends_on:
|
||||
- prereqs
|
||||
image: grpcweb/interop
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
gRPC-Web Interop Tests
|
||||
======================
|
||||
|
||||
This document describes the set of tests any gRPC-Web clients or proxies need
|
||||
to implement. The proto definition for the messages and RPCs we are using for
|
||||
the tests can be found
|
||||
[here](src/proto/grpc/testing/test.proto).
|
||||
|
||||
The canonical set of interop tests was defined in the main
|
||||
[grpc/grpc repo](https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md).
|
||||
|
||||
Here in gRPC-Web, we will only implement a subset of tests that's relevant to
|
||||
gRPC-Web. For example, we will not be implementing any tests involving
|
||||
client-streaming or bidi-streaming for now. On the other hand, there are
|
||||
gRPC-Web specific tests that we will add here.
|
||||
|
||||
```
|
||||
gRPC-Web Client <--> Proxy <--> gRPC Service
|
||||
```
|
||||
|
||||
The idea is that we should be able to swap out any of the 3 components above
|
||||
and all the interop tests should still pass.
|
||||
|
||||
This repository will provide a canonical implementation of the interop test
|
||||
suite using the Javascript client, Envoy and a gRPC service implemented in
|
||||
Node.
|
||||
|
||||
For any new gRPC-Web client implementation, you need to swap out the JS
|
||||
client and make sure all tests still pass.
|
||||
|
||||
For any in-process proxies implementation, you need to swap out the proxy
|
||||
and the service as a unit and make sure the standard JS client will still
|
||||
pass the tests.
|
||||
|
||||
|
||||
List of Tests
|
||||
-------------
|
||||
|
||||
| Test Name | grpc-web-text Mode | grpc-web Binary mode |
|
||||
| --------- |:------------------:|:--------------------:|
|
||||
| empty_unary | ✓ | ✓ |
|
||||
| cacheable_unary | TBD | TBD |
|
||||
| large_unary | ✓ | ✓ |
|
||||
| client_compressed_unary | TBD | TBD |
|
||||
| server_compressed_unary | TBD | TBD |
|
||||
| client_streaming | ✗ | ✗ |
|
||||
| client_compressed_streaming | ✗ | ✗ |
|
||||
| server_streaming | ✓ | ✗ |
|
||||
| server_compressed_streaming | TBD | ✗ |
|
||||
| ping_pong | ✗ | ✗ |
|
||||
| empty_stream | ✗ | ✗ |
|
||||
| compute_engine_creds | TBD | TBD |
|
||||
| jwt_token_creds | TBD | TBD |
|
||||
| oauth2_auth_token | TBD | TBD |
|
||||
| per_rpc_creds | TBD | TBD |
|
||||
| google_default_credentials | TBD | TBD |
|
||||
| compute_engine_channel_credentials | TBD | TBD |
|
||||
| custom_metadata | ✓ | ✓ |
|
||||
| status_code_and_message | ✓ | ✓ |
|
||||
| special_status_message | ✓ | ✓ |
|
||||
| unimplemented_method | ✓ | ✓ |
|
||||
| unimplemented_service | ✓ | ✓ |
|
||||
| cancel_after_begin | ✗ | ✗ |
|
||||
| cancel_after_first_response | ✗ | ✗ |
|
||||
| timeout_on_sleeping_server | ✗ | ✗ |
|
||||
|
||||
|
||||
gRPC-Web specific considerations
|
||||
--------------------------------
|
||||
|
||||
### Text vs Binary mode
|
||||
|
||||
As mentioned in the table above, client needs to be tested in both the text
|
||||
format `application/grpc-web-text` and the binary mode
|
||||
`application/grpc-web+proto`. The latter we don't need to test any streaming
|
||||
methods.
|
||||
|
||||
### CORS and other web specific scenarios
|
||||
|
||||
We may add specific tests to account for web-related scenarios like CORS
|
||||
handling, etc. Mostly these are to test the connection between the browser
|
||||
client and the proxy.
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# 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/prereqs
|
||||
|
||||
WORKDIR /github/grpc-web/test/interop
|
||||
|
||||
COPY ./test/interop .
|
||||
|
||||
RUN protoc -I=../.. src/proto/grpc/testing/test.proto \
|
||||
src/proto/grpc/testing/empty.proto src/proto/grpc/testing/messages.proto \
|
||||
--js_out=import_style=commonjs:. \
|
||||
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
|
||||
|
||||
RUN npm install && \
|
||||
npm link grpc-web && \
|
||||
npx webpack && \
|
||||
cp index.html /var/www/html && \
|
||||
cp dist/main.js /var/www/html/dist
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
EXPOSE 8081
|
||||
CMD ["python", "-m", "SimpleHTTPServer", "8081"]
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package grpc.testing;
|
||||
|
||||
// An empty message that you can re-use to avoid defining duplicated empty
|
||||
// messages in your project. A typical example is to use it as argument or the
|
||||
// return value of a service API. For instance:
|
||||
//
|
||||
// service Foo {
|
||||
// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
|
||||
// };
|
||||
//
|
||||
message Empty {}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
|
||||
// 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.
|
||||
|
||||
// Message definitions to be used by integration test service definitions.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package grpc.testing;
|
||||
|
||||
// TODO(dgq): Go back to using well-known types once
|
||||
// https://github.com/grpc/grpc/issues/6980 has been fixed.
|
||||
// import "google/protobuf/wrappers.proto";
|
||||
message BoolValue {
|
||||
// The bool value.
|
||||
bool value = 1;
|
||||
}
|
||||
|
||||
// The type of payload that should be returned.
|
||||
enum PayloadType {
|
||||
// Compressable text format.
|
||||
COMPRESSABLE = 0;
|
||||
}
|
||||
|
||||
// A block of data, to simply increase gRPC message size.
|
||||
message Payload {
|
||||
// The type of data in body.
|
||||
PayloadType type = 1;
|
||||
// Primary contents of payload.
|
||||
bytes body = 2;
|
||||
}
|
||||
|
||||
// A protobuf representation for grpc status. This is used by test
|
||||
// clients to specify a status that the server should attempt to return.
|
||||
message EchoStatus {
|
||||
int32 code = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
// The type of route that a client took to reach a server w.r.t. gRPCLB.
|
||||
// The server must fill in "fallback" if it detects that the RPC reached
|
||||
// the server via the "gRPCLB fallback" path, and "backend" if it detects
|
||||
// that the RPC reached the server via "gRPCLB backend" path (i.e. if it got
|
||||
// the address of this server from the gRPCLB server BalanceLoad RPC). Exactly
|
||||
// how this detection is done is context and server dependent.
|
||||
enum GrpclbRouteType {
|
||||
// Server didn't detect the route that a client took to reach it.
|
||||
GRPCLB_ROUTE_TYPE_UNKNOWN = 0;
|
||||
// Indicates that a client reached a server via gRPCLB fallback.
|
||||
GRPCLB_ROUTE_TYPE_FALLBACK = 1;
|
||||
// Indicates that a client reached a server as a gRPCLB-given backend.
|
||||
GRPCLB_ROUTE_TYPE_BACKEND = 2;
|
||||
}
|
||||
|
||||
// Unary request.
|
||||
message SimpleRequest {
|
||||
// Desired payload type in the response from the server.
|
||||
// If response_type is RANDOM, server randomly chooses one from other formats.
|
||||
PayloadType response_type = 1;
|
||||
|
||||
// Desired payload size in the response from the server.
|
||||
int32 response_size = 2;
|
||||
|
||||
// Optional input payload sent along with the request.
|
||||
Payload payload = 3;
|
||||
|
||||
// Whether SimpleResponse should include username.
|
||||
bool fill_username = 4;
|
||||
|
||||
// Whether SimpleResponse should include OAuth scope.
|
||||
bool fill_oauth_scope = 5;
|
||||
|
||||
// Whether to request the server to compress the response. This field is
|
||||
// "nullable" in order to interoperate seamlessly with clients not able to
|
||||
// implement the full compression tests by introspecting the call to verify
|
||||
// the response's compression status.
|
||||
BoolValue response_compressed = 6;
|
||||
|
||||
// Whether server should return a given status
|
||||
EchoStatus response_status = 7;
|
||||
|
||||
// Whether the server should expect this request to be compressed.
|
||||
BoolValue expect_compressed = 8;
|
||||
|
||||
// Whether SimpleResponse should include server_id.
|
||||
bool fill_server_id = 9;
|
||||
|
||||
// Whether SimpleResponse should include grpclb_route_type.
|
||||
bool fill_grpclb_route_type = 10;
|
||||
}
|
||||
|
||||
// Unary response, as configured by the request.
|
||||
message SimpleResponse {
|
||||
// Payload to increase message size.
|
||||
Payload payload = 1;
|
||||
// The user the request came from, for verifying authentication was
|
||||
// successful when the client expected it.
|
||||
string username = 2;
|
||||
// OAuth scope.
|
||||
string oauth_scope = 3;
|
||||
|
||||
// Server ID. This must be unique among different server instances,
|
||||
// but the same across all RPC's made to a particular server instance.
|
||||
string server_id = 4;
|
||||
// gRPCLB Path.
|
||||
GrpclbRouteType grpclb_route_type = 5;
|
||||
|
||||
// Server hostname.
|
||||
string hostname = 6;
|
||||
}
|
||||
|
||||
// Client-streaming request.
|
||||
message StreamingInputCallRequest {
|
||||
// Optional input payload sent along with the request.
|
||||
Payload payload = 1;
|
||||
|
||||
// Whether the server should expect this request to be compressed. This field
|
||||
// is "nullable" in order to interoperate seamlessly with servers not able to
|
||||
// implement the full compression tests by introspecting the call to verify
|
||||
// the request's compression status.
|
||||
BoolValue expect_compressed = 2;
|
||||
|
||||
// Not expecting any payload from the response.
|
||||
}
|
||||
|
||||
// Client-streaming response.
|
||||
message StreamingInputCallResponse {
|
||||
// Aggregated size of payloads received from the client.
|
||||
int32 aggregated_payload_size = 1;
|
||||
}
|
||||
|
||||
// Configuration for a particular response.
|
||||
message ResponseParameters {
|
||||
// Desired payload sizes in responses from the server.
|
||||
int32 size = 1;
|
||||
|
||||
// Desired interval between consecutive responses in the response stream in
|
||||
// microseconds.
|
||||
int32 interval_us = 2;
|
||||
|
||||
// Whether to request the server to compress the response. This field is
|
||||
// "nullable" in order to interoperate seamlessly with clients not able to
|
||||
// implement the full compression tests by introspecting the call to verify
|
||||
// the response's compression status.
|
||||
BoolValue compressed = 3;
|
||||
}
|
||||
|
||||
// Server-streaming request.
|
||||
message StreamingOutputCallRequest {
|
||||
// Desired payload type in the response from the server.
|
||||
// If response_type is RANDOM, the payload from each response in the stream
|
||||
// might be of different types. This is to simulate a mixed type of payload
|
||||
// stream.
|
||||
PayloadType response_type = 1;
|
||||
|
||||
// Configuration for each expected response message.
|
||||
repeated ResponseParameters response_parameters = 2;
|
||||
|
||||
// Optional input payload sent along with the request.
|
||||
Payload payload = 3;
|
||||
|
||||
// Whether server should return a given status
|
||||
EchoStatus response_status = 7;
|
||||
}
|
||||
|
||||
// Server-streaming response, as configured by the request and parameters.
|
||||
message StreamingOutputCallResponse {
|
||||
// Payload to increase response size.
|
||||
Payload payload = 1;
|
||||
}
|
||||
|
||||
// For reconnect interop test only.
|
||||
// Client tells server what reconnection parameters it used.
|
||||
message ReconnectParams {
|
||||
int32 max_reconnect_backoff_ms = 1;
|
||||
}
|
||||
|
||||
// For reconnect interop test only.
|
||||
// Server tells client whether its reconnects are following the spec and the
|
||||
// reconnect backoffs it saw.
|
||||
message ReconnectInfo {
|
||||
bool passed = 1;
|
||||
repeated int32 backoff_ms = 2;
|
||||
}
|
||||
|
||||
message LoadBalancerStatsRequest {
|
||||
// Request stats for the next num_rpcs sent by client.
|
||||
int32 num_rpcs = 1;
|
||||
// If num_rpcs have not completed within timeout_sec, return partial results.
|
||||
int32 timeout_sec = 2;
|
||||
}
|
||||
|
||||
message LoadBalancerStatsResponse {
|
||||
// The number of completed RPCs for each peer.
|
||||
map<string, int32> rpcs_by_peer = 1;
|
||||
// The number of RPCs that failed to record a remote peer.
|
||||
int32 num_failures = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
|
||||
// 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.
|
||||
|
||||
// An integration test service that covers all the method signature permutations
|
||||
// of unary/streaming requests/responses.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
import "src/proto/grpc/testing/empty.proto";
|
||||
import "src/proto/grpc/testing/messages.proto";
|
||||
|
||||
package grpc.testing;
|
||||
|
||||
// A simple service to test the various types of RPCs and experiment with
|
||||
// performance with various types of payload.
|
||||
service TestService {
|
||||
// One empty request followed by one empty response.
|
||||
rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
||||
|
||||
// One request followed by one response.
|
||||
rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
|
||||
|
||||
// One request followed by one response. Response has cache control
|
||||
// headers set such that a caching HTTP proxy (such as GFE) can
|
||||
// satisfy subsequent requests.
|
||||
rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse);
|
||||
|
||||
// One request followed by a sequence of responses (streamed download).
|
||||
// The server returns the payload with client desired type and sizes.
|
||||
rpc StreamingOutputCall(StreamingOutputCallRequest)
|
||||
returns (stream StreamingOutputCallResponse);
|
||||
|
||||
// A sequence of requests followed by one response (streamed upload).
|
||||
// The server returns the aggregated size of client payload as the result.
|
||||
rpc StreamingInputCall(stream StreamingInputCallRequest)
|
||||
returns (StreamingInputCallResponse);
|
||||
|
||||
// A sequence of requests with each request served by the server immediately.
|
||||
// As one request could lead to multiple responses, this interface
|
||||
// demonstrates the idea of full duplexing.
|
||||
rpc FullDuplexCall(stream StreamingOutputCallRequest)
|
||||
returns (stream StreamingOutputCallResponse);
|
||||
|
||||
// A sequence of requests followed by a sequence of responses.
|
||||
// The server buffers all the client requests and then serves them in order. A
|
||||
// stream of responses are returned to the client when the server starts with
|
||||
// first request.
|
||||
rpc HalfDuplexCall(stream StreamingOutputCallRequest)
|
||||
returns (stream StreamingOutputCallResponse);
|
||||
|
||||
// The test server will not implement this method. It will be used
|
||||
// to test the behavior when clients call unimplemented methods.
|
||||
rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
||||
}
|
||||
|
||||
// A simple service NOT implemented at servers so clients can test for
|
||||
// that case.
|
||||
service UnimplementedService {
|
||||
// A call that no server should implement
|
||||
rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
||||
}
|
||||
|
||||
// A service used to control reconnect server.
|
||||
service ReconnectService {
|
||||
rpc Start(grpc.testing.ReconnectParams) returns (grpc.testing.Empty);
|
||||
rpc Stop(grpc.testing.Empty) returns (grpc.testing.ReconnectInfo);
|
||||
}
|
||||
|
||||
// A service used to obtain stats for verifying LB behavior.
|
||||
service LoadBalancerStatsService {
|
||||
// Gets the backend distribution for RPCs sent by a test client.
|
||||
rpc GetClientStats(LoadBalancerStatsRequest)
|
||||
returns (LoadBalancerStatsResponse) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
gRPC-Web Interop Tests
|
||||
======================
|
||||
|
||||
See the
|
||||
[main doc](https://github.com/grpc/grpc-web/blob/master/interop-test-descriptions.md)
|
||||
for details about gRPC interop tests in general and the list of test cases.
|
||||
|
||||
|
||||
Run interop tests
|
||||
-----------------
|
||||
|
||||
### Run the Node interop server
|
||||
|
||||
An interop server implemented in Node is hosted in the `grpc/grpc-node` repo.
|
||||
There might be a bit of set up you need to do before running the command below.
|
||||
|
||||
```sh
|
||||
$ cd grpc-node/test
|
||||
$ node --require ./fixtures/native_native interop/interop_server.js --port=7074
|
||||
```
|
||||
|
||||
|
||||
### Run the Envoy proxy
|
||||
|
||||
An `envoy.yaml` file is provided in this directory to direct traffic for these
|
||||
tests.
|
||||
|
||||
```sh
|
||||
$ cd grpc-web
|
||||
$ docker run -it --rm -v $(pwd)/test/interop/envoy.yaml:/etc/envoy/envoy.yaml:ro \
|
||||
--network=host -p 8080:8080 envoyproxy/envoy:latest
|
||||
```
|
||||
|
||||
|
||||
### Run the gRPC-Web browser client
|
||||
|
||||
|
||||
```sh
|
||||
$ cd grpc-web
|
||||
$ docker-compose -f advanced.yml build common prereqs interop
|
||||
$ docker-compose -f advanced.yml up interop
|
||||
```
|
||||
|
||||
Open up the browser and go to `http://localhost:8081/index.html` and open up
|
||||
the console.
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const {Empty} = require('./src/proto/grpc/testing/empty_pb.js');
|
||||
const {SimpleRequest,
|
||||
StreamingOutputCallRequest,
|
||||
EchoStatus,
|
||||
Payload,
|
||||
ResponseParameters} =
|
||||
require('./src/proto/grpc/testing/messages_pb.js');
|
||||
const {TestServiceClient} =
|
||||
require('./src/proto/grpc/testing/test_grpc_web_pb.js');
|
||||
const grpc = {};
|
||||
grpc.web = require('grpc-web');
|
||||
|
||||
var testService = new TestServiceClient(
|
||||
'http://'+window.location.hostname+':8080', null, null);
|
||||
|
||||
function doEmptyUnary() {
|
||||
return new Promise((resolve, reject) => {
|
||||
testService.emptyCall(new Empty(), null, (err, response) => {
|
||||
if (err) {
|
||||
reject('EmptyUnary failed: Received unexpected error "'+
|
||||
err.message+'"');
|
||||
return;
|
||||
} else {
|
||||
if (!(response instanceof Empty)) {
|
||||
reject('EmptyUnary failed: Response not of Empty type');
|
||||
return;
|
||||
}
|
||||
resolve('EmptyUnary: passed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doLargeUnary() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var req = new SimpleRequest();
|
||||
var size = 314159;
|
||||
|
||||
var payload = new Payload();
|
||||
payload.setBody('0'.repeat(271828));
|
||||
|
||||
req.setPayload(payload);
|
||||
req.setResponseSize(size);
|
||||
|
||||
testService.unaryCall(req, null, (err, response) => {
|
||||
if (err) {
|
||||
reject('LargeUnary failed: Received unexpected error "'+
|
||||
err.message+'"');
|
||||
return;
|
||||
} else {
|
||||
if (response.getPayload().getBody().length != size) {
|
||||
rejecet('LargeUnary failed: Received incorrect size payload');
|
||||
return;
|
||||
}
|
||||
resolve('LargeUnary: passed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doServerStreaming() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var sizes = [31415, 9, 2653, 58979];
|
||||
|
||||
var responseParams = sizes.map((size, idx) => {
|
||||
var param = new ResponseParameters();
|
||||
param.setSize(size);
|
||||
param.setIntervalUs(idx * 10);
|
||||
return param;
|
||||
});
|
||||
|
||||
var req = new StreamingOutputCallRequest();
|
||||
req.setResponseParametersList(responseParams);
|
||||
|
||||
var stream = testService.streamingOutputCall(req);
|
||||
|
||||
var numCallbacks = 0;
|
||||
stream.on('data', (response) => {
|
||||
if (response.getPayload().getBody().length != sizes[numCallbacks]) {
|
||||
reject('ServerStreaming failed: Received incorrect size payload');
|
||||
return;
|
||||
}
|
||||
numCallbacks++;
|
||||
});
|
||||
// TODO(stanleycheung): is there a better way to do this?
|
||||
setTimeout(() => {
|
||||
if (numCallbacks != sizes.length) {
|
||||
reject('ServerStreaming failed: data callback called '+
|
||||
numCallbacks+' times. Should be '+sizes.length);
|
||||
return;
|
||||
}
|
||||
resolve('ServerStreaming: passed');
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function doCustomMetadata() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var 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;
|
||||
|
||||
var payload = new Payload();
|
||||
payload.setBody('0'.repeat(271828));
|
||||
|
||||
req.setPayload(payload);
|
||||
req.setResponseSize(size);
|
||||
|
||||
var call = testService.unaryCall(req, {
|
||||
[ECHO_INITIAL_KEY]: ECHO_INITIAL_VALUE,
|
||||
[ECHO_TRAILING_KEY]: ECHO_TRAILING_VALUE
|
||||
}, (err, response) => {
|
||||
if (err) {
|
||||
reject('CustomMetadata failed: Received unexpected error "'+
|
||||
err.message+'"');
|
||||
return;
|
||||
} else {
|
||||
if (response.getPayload().getBody().length != size) {
|
||||
rejecet('CustomMetadata failed: Received incorrect size payload');
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
var metadataCallbackVerified = false;
|
||||
var statusCallbackVerified = false;
|
||||
call.on('metadata', (metadata) => {
|
||||
if (!(ECHO_INITIAL_KEY in metadata)) {
|
||||
reject('CustomMetadata failed: initial metadata does not '+
|
||||
'contain '+ECHO_INITIAL_KEY);
|
||||
} else if (metadata[ECHO_INITIAL_KEY] != ECHO_INITIAL_VALUE) {
|
||||
reject('CustomMetadata failed: initial metadata value for '+
|
||||
ECHO_INITIAL_KEY+' is incorrect');
|
||||
} else {
|
||||
metadataCallbackVerified = true;
|
||||
}
|
||||
});
|
||||
call.on('status', (status) => {
|
||||
if (!('metadata' in status)) {
|
||||
reject('CustomMetadata failed: status callback does not '+
|
||||
'contain metadata');
|
||||
} else if (!(ECHO_TRAILING_KEY in status.metadata)) {
|
||||
reject('CustomMetadata failed: trailing metadata does not '+
|
||||
'contain '+ECHO_TRAILING_KEY);
|
||||
} else if (status.metadata[ECHO_TRAILING_KEY] != ECHO_TRAILING_VALUE) {
|
||||
reject('CustomMetadata failed: trailing metadata value for '+
|
||||
ECHO_TRAILING_KEY+' is incorrect');
|
||||
} else {
|
||||
statusCallbackVerified = true;
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (!metadataCallbackVerified || !statusCallbackVerified) {
|
||||
reject('CustomMetadata failed: some callback failed to verify');
|
||||
}
|
||||
resolve('CustomMetadta passed');
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function doStatusCodeAndMessage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var 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) => {
|
||||
if (err) {
|
||||
if (!('code' in err)) {
|
||||
reject('StatusCodeAndMessage failed: status callback does not '+
|
||||
'contain code');
|
||||
} else if (!('message' in err)) {
|
||||
reject('StatusCodeAndMessage failed: status callback does not '+
|
||||
'contain message');
|
||||
} else if (err.code != 2) {
|
||||
reject('StatusCodeAndMessage failed: status code is not 2, is '+
|
||||
err.code);
|
||||
} else if (err.message != TEST_STATUS_MESSAGE) {
|
||||
reject('StatusCodeAndMessage failed: status mesage is not '+
|
||||
TEST_STATUS_MESSAGE+', is '+err.message);
|
||||
} else {
|
||||
resolve('StatusCodeAndMessage passed');
|
||||
}
|
||||
} else {
|
||||
reject('StatusCodeAndMessage failed: should not have received a '+
|
||||
'proper response');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doUnimplementedMethod() {
|
||||
return new Promise((resolve, reject) => {
|
||||
testService.unimplementedCall(new Empty(), {}, (err, response) => {
|
||||
if (err) {
|
||||
if (!('code' in err)) {
|
||||
reject('UnimplementedMethod failed: status callback does not '+
|
||||
'contain code');
|
||||
} else if (!('message' in err)) {
|
||||
reject('UnimplementedMethod failed: status callback does not '+
|
||||
'contain message');
|
||||
} else if (err.code != 12) {
|
||||
reject('UnimplementedMethod failed: status code is not 12'+
|
||||
'(UNIMPLEMENTED), is '+ err.code);
|
||||
} else {
|
||||
resolve('UnimplementedMethod passed');
|
||||
}
|
||||
} else {
|
||||
reject('UnimplementedMethod failed: should not have received a '+
|
||||
'proper respoonse');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var testCases = [doEmptyUnary, doLargeUnary, doServerStreaming,
|
||||
doCustomMetadata, doStatusCodeAndMessage,
|
||||
doUnimplementedMethod];
|
||||
|
||||
testCases.reduce((promiseChain, currentTask) => {
|
||||
return promiseChain.then(() => {
|
||||
return currentTask().then(console.log);
|
||||
}).catch(console.error);
|
||||
}, Promise.resolve());
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
admin:
|
||||
access_log_path: /tmp/admin_access.log
|
||||
address:
|
||||
socket_address: { address: 0.0.0.0, port_value: 9901 }
|
||||
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: listener_0
|
||||
address:
|
||||
socket_address: { address: 0.0.0.0, port_value: 8080 }
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
config:
|
||||
codec_type: auto
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains: ["*"]
|
||||
routes:
|
||||
- match: { prefix: "/" }
|
||||
route:
|
||||
cluster: interop_service
|
||||
max_grpc_timeout: 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,x-grpc-test-echo-initial,x-grpc-test-echo-trailing-bin,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
|
||||
max_age: "1728000"
|
||||
expose_headers: x-grpc-test-echo-initial,x-grpc-test-echo-trailing-bin,grpc-status,grpc-message
|
||||
http_filters:
|
||||
- name: envoy.grpc_web
|
||||
- name: envoy.cors
|
||||
- name: envoy.router
|
||||
clusters:
|
||||
- name: interop_service
|
||||
connect_timeout: 0.25s
|
||||
type: logical_dns
|
||||
http2_protocol_options: {}
|
||||
lb_policy: round_robin
|
||||
hosts: [{ socket_address: { address: localhost, port_value: 7074 }}]
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<!-- 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. -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Interop Test</title>
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script src="./dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row" id="first">
|
||||
<p>Please open up the console to see the test results.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "grpc-web-interop-test",
|
||||
"version": "0.1.0",
|
||||
"description": "gRPC-Web Interop Test Client",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"google-protobuf": "^3.6.1",
|
||||
"grpc-web": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^16.2.2",
|
||||
"webpack": "^4.16.5",
|
||||
"webpack-cli": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
mode: "production",
|
||||
entry: "./client.js",
|
||||
};
|
||||
Loading…
Reference in New Issue