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
|
image: grpcweb/binary-client
|
||||||
ports:
|
ports:
|
||||||
- "8081:8081"
|
- "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