From f8fbbe32f9f8e36a91c106fe4248d24810599355 Mon Sep 17 00:00:00 2001 From: Eryu Xia Date: Sun, 14 May 2023 02:57:11 -0700 Subject: [PATCH] Update Debian (and other deps) and remove Java In-process Proxy (#1335) --- README.md | 2 + docker-compose.yml | 5 - .../docker/java_interop_server/Dockerfile | 41 -- .../docker/node_interop_server/Dockerfile | 2 +- net/grpc/gateway/docker/prereqs/Dockerfile | 6 +- .../echo/commonjs-example/package.json | 4 +- .../examples/echo/ts-example/package.json | 4 +- net/grpc/gateway/examples/echo/tutorial.md | 4 +- .../grpc-web-java/greeter-service/README.md | 14 - .../grpc-web-java/greeter-service/pom.xml | 120 ---- .../examples/greeter/GreeterService.java | 31 - .../greeter/StartServiceAndGrpcwebProxy.java | 35 -- .../java/grpcweb/examples/greeter/Util.java | 36 -- .../src/main/proto/greeter.proto | 45 -- .../examples/greeter/GreeterClientTest.java | 108 ---- .../interop-test-service/README.md | 6 - .../interop-test-service/pom.xml | 136 ----- .../grpcweb/examples/InteropTestService.java | 546 ------------------ .../examples/StartServiceAndGrpcwebProxy.java | 44 -- .../src/main/java/grpcweb/examples/Util.java | 22 - .../src/main/proto/grpc/testing/empty.proto | 34 -- .../main/proto/grpc/testing/messages.proto | 199 ------- .../src/main/proto/grpc/testing/test.proto | 94 --- .../gateway/examples/helloworld/README.md | 4 +- .../gateway/examples/helloworld/package.json | 4 +- .../grpc-web/docker/jsunit-test/Dockerfile | 4 +- .../grpc-web/scripts/generate_test_files.sh | 2 +- packages/grpc-web/scripts/run_jsunit_tests.sh | 3 +- scripts/run_interop_tests.sh | 23 +- src/connector/README.md | 67 --- src/connector/pom.xml | 79 --- .../src/main/java/io/grpcweb/CorsFilter.java | 74 --- .../src/main/java/io/grpcweb/DebugInfo.java | 61 -- .../java/io/grpcweb/GrpcPortNumRelay.java | 23 - .../grpcweb/GrpcServiceConnectionManager.java | 47 -- .../io/grpcweb/GrpcWebClientInterceptor.java | 82 --- .../java/io/grpcweb/GrpcWebGuiceModule.java | 41 -- .../io/grpcweb/GrpcWebTrafficServlet.java | 38 -- .../JettyWebserverForGrpcwebTraffic.java | 53 -- .../main/java/io/grpcweb/MessageDeframer.java | 126 ---- .../main/java/io/grpcweb/MessageFramer.java | 43 -- .../main/java/io/grpcweb/MessageHandler.java | 86 --- .../main/java/io/grpcweb/MetadataUtil.java | 87 --- .../main/java/io/grpcweb/RequestHandler.java | 177 ------ .../main/java/io/grpcweb/SendResponse.java | 122 ---- .../java/io/grpcweb/MessageDeframerTest.java | 134 ----- .../java/io/grpcweb/MessageFramerTest.java | 34 -- test/interop/package.json | 4 +- 48 files changed, 25 insertions(+), 2931 deletions(-) delete mode 100644 net/grpc/gateway/docker/java_interop_server/Dockerfile delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/README.md delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/pom.xml delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/GreeterService.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/StartServiceAndGrpcwebProxy.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/Util.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/proto/greeter.proto delete mode 100644 net/grpc/gateway/examples/grpc-web-java/greeter-service/src/test/java/grpcweb/examples/greeter/GreeterClientTest.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/README.md delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/pom.xml delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/InteropTestService.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/StartServiceAndGrpcwebProxy.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/Util.java delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/empty.proto delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/messages.proto delete mode 100644 net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/test.proto delete mode 100644 src/connector/README.md delete mode 100644 src/connector/pom.xml delete mode 100644 src/connector/src/main/java/io/grpcweb/CorsFilter.java delete mode 100644 src/connector/src/main/java/io/grpcweb/DebugInfo.java delete mode 100644 src/connector/src/main/java/io/grpcweb/GrpcPortNumRelay.java delete mode 100644 src/connector/src/main/java/io/grpcweb/GrpcServiceConnectionManager.java delete mode 100644 src/connector/src/main/java/io/grpcweb/GrpcWebClientInterceptor.java delete mode 100644 src/connector/src/main/java/io/grpcweb/GrpcWebGuiceModule.java delete mode 100644 src/connector/src/main/java/io/grpcweb/GrpcWebTrafficServlet.java delete mode 100644 src/connector/src/main/java/io/grpcweb/JettyWebserverForGrpcwebTraffic.java delete mode 100644 src/connector/src/main/java/io/grpcweb/MessageDeframer.java delete mode 100644 src/connector/src/main/java/io/grpcweb/MessageFramer.java delete mode 100644 src/connector/src/main/java/io/grpcweb/MessageHandler.java delete mode 100644 src/connector/src/main/java/io/grpcweb/MetadataUtil.java delete mode 100644 src/connector/src/main/java/io/grpcweb/RequestHandler.java delete mode 100644 src/connector/src/main/java/io/grpcweb/SendResponse.java delete mode 100644 src/connector/src/test/java/io/grpcweb/MessageDeframerTest.java delete mode 100644 src/connector/src/test/java/io/grpcweb/MessageFramerTest.java diff --git a/README.md b/README.md index 770c6596e..a2c8c8e40 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,8 @@ Multiple proxies support the gRPC-web protocol. 3. Apache [APISIX](https://apisix.apache.org/) has also added grpc-web support, and more details can be found [here](https://apisix.apache.org/blog/2022/01/25/apisix-grpc-web-integration/). +4. [Nginx](https://www.nginx.com/) has a grpc-web module ([doc](https://nginx.org/en/docs/http/ngx_http_grpc_module.html), [announcement](https://www.nginx.com/blog/nginx-1-13-10-grpc/))), and seems to work with simple configs, according to user [feedback](https://github.com/grpc/grpc-web/discussions/1322). + ### Web Frameworks with gRPC-Web support - [Armeria (JVM)](https://armeria.dev/docs/server-grpc/#grpc-web) - [Tonic (Rust)](https://docs.rs/tonic-web/latest/tonic_web/) diff --git a/docker-compose.yml b/docker-compose.yml index ef36d26e5..b4cca7fca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -93,11 +93,6 @@ services: image: grpcweb/interop-client ports: - "8081:8081" - java-interop-server: - build: - context: ./ - dockerfile: ./net/grpc/gateway/docker/java_interop_server/Dockerfile - image: grpcweb/java-interop-server protoc-plugin: build: context: ./ diff --git a/net/grpc/gateway/docker/java_interop_server/Dockerfile b/net/grpc/gateway/docker/java_interop_server/Dockerfile deleted file mode 100644 index 658093802..000000000 --- a/net/grpc/gateway/docker/java_interop_server/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -# 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 maven:3.6.1-jdk-8 - -RUN apt-get -qq update && apt-get -qq 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.4.2/\ -protoc-gen-grpc-web-1.4.2-linux-x86_64 -o /usr/local/bin/protoc-gen-grpc-web && \ - chmod +x /usr/local/bin/protoc-gen-grpc-web - -WORKDIR /var/www/html/dist - -WORKDIR /github/grpc-web - -RUN git clone https://github.com/grpc/grpc-web . && \ - cd src/connector && \ - mvn install --no-transfer-progress && \ - cd ../../net/grpc/gateway/examples/grpc-web-java/interop-test-service && \ - mvn package --no-transfer-progress - -ENTRYPOINT ["java", "-cp", "net/grpc/gateway/examples/grpc-web-java/interop-test-service/target/interop-test-0.1-jar-with-dependencies.jar", "grpcweb.examples.StartServiceAndGrpcwebProxy"] diff --git a/net/grpc/gateway/docker/node_interop_server/Dockerfile b/net/grpc/gateway/docker/node_interop_server/Dockerfile index 83d1d7244..d5d85fa97 100644 --- a/net/grpc/gateway/docker/node_interop_server/Dockerfile +++ b/net/grpc/gateway/docker/node_interop_server/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM node:12.22.6-stretch +FROM node:20.0.0-bullseye WORKDIR /github/grpc-node diff --git a/net/grpc/gateway/docker/prereqs/Dockerfile b/net/grpc/gateway/docker/prereqs/Dockerfile index b2bcd6fff..a95ab0e99 100644 --- a/net/grpc/gateway/docker/prereqs/Dockerfile +++ b/net/grpc/gateway/docker/prereqs/Dockerfile @@ -18,8 +18,8 @@ ###################################### # Stage 1: Fetch binaries ###################################### -# node:... Docker image is based on buildpack-deps:stretch -FROM buildpack-deps:stretch AS prepare +# node:... Docker image is based on buildpack-deps:bullseye (11) +FROM buildpack-deps:bullseye AS prepare ARG BUILDIFIER_VERSION=1.0.0 ARG PROTOBUF_VERSION=3.19.4 @@ -49,7 +49,7 @@ RUN ./scripts/init_submodules.sh ###################################### # Stage 2: Copy source files and build ###################################### -FROM node:12.22.6-stretch AS copy-and-build +FROM node:20.0.0-bullseye AS copy-and-build ARG MAKEFLAGS=-j8 ARG BAZEL_VERSION=4.1.0 diff --git a/net/grpc/gateway/examples/echo/commonjs-example/package.json b/net/grpc/gateway/examples/echo/commonjs-example/package.json index 47db098d0..12752e34f 100644 --- a/net/grpc/gateway/examples/echo/commonjs-example/package.json +++ b/net/grpc/gateway/examples/echo/commonjs-example/package.json @@ -8,7 +8,7 @@ "grpc-web": "~1.4.2" }, "devDependencies": { - "webpack": "~4.43.0", - "webpack-cli": "~3.3.11" + "webpack": "~5.82.1", + "webpack-cli": "~5.1.1" } } diff --git a/net/grpc/gateway/examples/echo/ts-example/package.json b/net/grpc/gateway/examples/echo/ts-example/package.json index 71f125f64..da6c03d01 100644 --- a/net/grpc/gateway/examples/echo/ts-example/package.json +++ b/net/grpc/gateway/examples/echo/ts-example/package.json @@ -8,7 +8,7 @@ "jquery": "~3.5.1", "mock-xmlhttprequest": "~2.0.0", "typescript": "latest", - "webpack": "~4.43.0", - "webpack-cli": "~3.3.11" + "webpack": "~5.82.1", + "webpack-cli": "~5.1.1" } } diff --git a/net/grpc/gateway/examples/echo/tutorial.md b/net/grpc/gateway/examples/echo/tutorial.md index 68e93d254..d65b43c79 100644 --- a/net/grpc/gateway/examples/echo/tutorial.md +++ b/net/grpc/gateway/examples/echo/tutorial.md @@ -172,8 +172,8 @@ You will need a `package.json` file "grpc-web": "~1.4.2" }, "devDependencies": { - "webpack": "~4.43.0", - "webpack-cli": "~3.3.11" + "webpack": "~5.82.1", + "webpack-cli": "~5.1.1" } } ``` diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/README.md b/net/grpc/gateway/examples/grpc-web-java/greeter-service/README.md deleted file mode 100644 index 0381b4fc6..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/README.md +++ /dev/null @@ -1,14 +0,0 @@ -This dir contains code to exercise the [java grpc-web in-process -proxy](../../../../../../src/connector) with a simple -[service](src/main/proto/greeter.proto). - -To run this example, run this command -``` -mvn test -``` - -This example does the following: -1. Implement a [service](src/main/proto/greeter.proto) -2. Start the service on a **grpc-port**. Service is now ready to accept grpc requests on **grpc-port** -3. Start the grpc-web in-process proxy on another port **grpc-web-port** -4. Service is now ready to accept grpc-web requests on the **grpc-web-port** diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/pom.xml b/net/grpc/gateway/examples/grpc-web-java/greeter-service/pom.xml deleted file mode 100644 index 7e507d5c7..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/pom.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - 4.0.0 - - grpcweb.examples - grpcweb-java-examples-greeter - 0.1 - jar - - - UTF-8 - 1.7 - 1.7 - 1.30.0 - 3.12.0 - 7080 - 8080 - - - - - io.grpcweb - grpcweb-java - 0.1-SNAPSHOT - - - org.apache.httpcomponents - fluent-hc - 4.5.12 - - - org.apache.tomcat - annotations-api - 6.0.53 - provided - - - - io.grpc - grpc-netty-shaded - ${grpc.version} - runtime - - - - junit - junit - 4.13.2 - test - - - - - - - kr.motd.maven - os-maven-plugin - 1.6.2 - - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - 0.6.1 - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - grpc-java - io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} - - - - - compile - compile-custom - - - - - - org.codehaus.mojo - properties-maven-plugin - 1.0.0 - - - generate-resources - - write-project-properties - - - ${project.build.outputDirectory}/my.properties - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 1.4.1 - - - enforce - - enforce - - - - - - - - - - - - diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/GreeterService.java b/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/GreeterService.java deleted file mode 100644 index 8bb7d5d61..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/GreeterService.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 The 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. - */ - -package grpcweb.examples.greeter; - -import grpcweb.examples.greeter.GreeterOuterClass.HelloReply; -import grpcweb.examples.greeter.GreeterOuterClass.HelloRequest; -import io.grpc.stub.StreamObserver; - -public class GreeterService extends GreeterGrpc.GreeterImplBase { - @Override - public void sayHello(HelloRequest req, StreamObserver responseObserver) { - System.out.println("Greeter Service responding in sayhello() method"); - HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/StartServiceAndGrpcwebProxy.java b/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/StartServiceAndGrpcwebProxy.java deleted file mode 100644 index 19e4eebcb..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/StartServiceAndGrpcwebProxy.java +++ /dev/null @@ -1,35 +0,0 @@ -package grpcweb.examples.greeter; - -import io.grpc.BindableService; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import io.grpcweb.GrpcPortNumRelay; -import io.grpcweb.JettyWebserverForGrpcwebTraffic; - -/** - * This class starts the service on a port (property: grpc-port) - * and starts the grpc-web proxy on a different port (property: grpc-web-port). - */ -class StartServiceAndGrpcwebProxy { - private void startGrpcService(int port) throws Exception { - Server grpcServer = ServerBuilder.forPort(port) - .addService((BindableService) new GreeterService()) - .build(); - grpcServer.start(); - System.out.println("**** started gRPC Service on port# " + port); - } - - void start() throws Exception { - int grpcPort = Util.getGrpcServicePortNum(); - int grpcWebPort = Util.getGrpcwebServicePortNum(); - - // Start the Grpc service on grpc-port - startGrpcService(grpcPort); - - // Start the grpc-web proxy on grpc-web-port. - (new JettyWebserverForGrpcwebTraffic(grpcWebPort)).start(); - - // grpc-web proxy needs to know the grpc-port# so it could connect to the grpc service. - GrpcPortNumRelay.setGrpcPortNum(grpcPort); - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/Util.java b/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/Util.java deleted file mode 100644 index 4aacae496..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/Util.java +++ /dev/null @@ -1,36 +0,0 @@ -package grpcweb.examples.greeter; - -import grpcweb.examples.greeter.GreeterOuterClass.HelloRequest; -import java.io.IOException; -import java.util.Properties; - -class Util { - static byte[] packageReqObjIntoGrpcwebProtocol(HelloRequest req) { - byte[] reqObjBytes = req.toByteArray(); - int len = reqObjBytes.length; - byte[] packagedBytes = new byte[5 + len]; - packagedBytes[0] = (byte) 0x00; - packagedBytes[1] = (byte) ((len >> 24) & 0xff); - packagedBytes[2] = (byte) ((len >> 16) & 0xff); - packagedBytes[3] = (byte) ((len >> 8) & 0xff); - packagedBytes[4] = (byte) ((len >> 0) & 0xff); - System.arraycopy(reqObjBytes, 0, packagedBytes, 5, len); - return packagedBytes; - } - - private static int getIntPropertyValue(String s) throws IOException { - java.io.InputStream inputStream = - Thread.currentThread().getContextClassLoader().getResourceAsStream("my.properties"); - java.util.Properties properties = new Properties(); - properties.load(inputStream); - return Integer.parseInt(properties.getProperty(s)); - } - - static int getGrpcServicePortNum() throws IOException { - return getIntPropertyValue("grpc-port"); - } - - static int getGrpcwebServicePortNum() throws IOException { - return getIntPropertyValue("grpc-web-port"); - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/proto/greeter.proto b/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/proto/greeter.proto deleted file mode 100644 index b0e9cbcb7..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/proto/greeter.proto +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 The 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. - -// ======================================= -// -// DO NOT EDIT -// this is copy of -// https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/ -// examples/helloworld/helloworld.proto -// -// TODO: can the original be directly used without making copy here -// ======================================= - -syntax = "proto3"; - -option java_package = "grpcweb.examples.greeter"; - -package grpcweb.examples.greeter; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; -} diff --git a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/test/java/grpcweb/examples/greeter/GreeterClientTest.java b/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/test/java/grpcweb/examples/greeter/GreeterClientTest.java deleted file mode 100644 index d0c32e297..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/greeter-service/src/test/java/grpcweb/examples/greeter/GreeterClientTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020 The 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. - */ - -// ================================================ -// -// This is for testing only. Just to demonstrate a simple -// Java client to send grpc-web request and receive response -// and validate the response. -// -// ================================================ - -package grpcweb.examples.greeter; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.google.protobuf.InvalidProtocolBufferException; -import grpcweb.examples.greeter.GreeterOuterClass.HelloReply; -import grpcweb.examples.greeter.GreeterOuterClass.HelloRequest; -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.nio.ByteBuffer; -import java.util.logging.Logger; -import org.apache.http.HttpVersion; -import org.apache.http.client.fluent.Request; -import org.apache.http.entity.ContentType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** - * A simple client that requests a greeting from the {@link GreeterService}. - */ -@RunWith(JUnit4.class) -public class GreeterClientTest { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - - private void validateResponse(byte[] response) throws InvalidProtocolBufferException { - LOG.info("Response is: " + new String(response)); - - // validate the 1st byte - assertEquals((byte)0x00, response[0]); - - int len = ByteBuffer.wrap(response, 1, 4).getInt(); - assertEquals(11, len); - - // copy len bytes into a byte array, which can be xlated into HelloResponse - byte[] protoBytes = new byte[len]; - System.arraycopy(response, 5, protoBytes, 0, len); - HelloReply reply = HelloReply.parseFrom(protoBytes); - assertEquals("Hello foo", reply.getMessage()); - - // if there is more data in the response, it should be a trailer. - int offset = len + 5; - int trailerBlockLen = response.length - offset; - if (trailerBlockLen == 0) { - // don't have any more bytes. we are done - return; - } - assertEquals((byte)0x80, response[offset]); - offset++; - int trailerLen = ByteBuffer.wrap(response, offset, 4).getInt(); - assertTrue(trailerLen > 0); - - byte[] trailer = new byte[trailerLen]; - System.arraycopy(response, offset+4, trailer, 0, trailerLen); - String trailerStr = new String(trailer); - // LOG.info("received trailer: " + trailerStr); - assertTrue(trailerStr.startsWith("grpc-status:0")); - } - - private byte[] sendGrpcWebReqAndReceiveResponse() throws IOException { - // create a HelloRequest obj - HelloRequest reqObj = HelloRequest.newBuilder().setName("foo").build(); - byte[] packagedBytes = Util.packageReqObjIntoGrpcwebProtocol(reqObj); - - // send request to the grpc-web server - ContentType contentType = ContentType.create("application/grpc-web"); - int grpcWebPort = Util.getGrpcwebServicePortNum(); - return Request.Post("http://localhost:" + grpcWebPort + - "/grpcweb.examples.greeter.Greeter/SayHello") - .useExpectContinue() - .version(HttpVersion.HTTP_1_1) - .bodyByteArray(packagedBytes, contentType) - .execute().returnContent().asBytes(); - } - - @Test - public void testGreeterClient() throws Exception { - new StartServiceAndGrpcwebProxy().start(); - byte[] response = sendGrpcWebReqAndReceiveResponse(); - validateResponse(response); - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/README.md b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/README.md deleted file mode 100644 index 78d2ed715..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/README.md +++ /dev/null @@ -1,6 +0,0 @@ -This dir contains code to run -[interop-tests](../../../../../../test/interop/README.md) -on the [java grpc-web in-process proxy](../../../../../../src/connector) - -See [details](../../../../../../src/connector/README.md) -on how to run the interop-tests using code in this dir. diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/pom.xml b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/pom.xml deleted file mode 100644 index 1044f370c..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/pom.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - 4.0.0 - - grpcweb.examples - interop-test - 0.1 - jar - - - UTF-8 - 1.7 - 1.7 - 1.33.0 - 3.12.0 - 7080 - 8080 - - - - - io.grpc - grpc-core - ${grpc.version} - - - io.grpcweb - grpcweb-java - 0.1-SNAPSHOT - - - org.apache.tomcat - annotations-api - 6.0.53 - provided - - - - io.grpc - grpc-netty-shaded - ${grpc.version} - runtime - - - - - - - kr.motd.maven - os-maven-plugin - 1.6.2 - - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - 0.6.1 - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - grpc-java - io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} - - - - - compile - compile-custom - - - - - - org.codehaus.mojo - properties-maven-plugin - 1.0.0 - - - generate-resources - - write-project-properties - - - ${project.build.outputDirectory}/my.properties - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.3.0 - - - package - - single - - - - - grpcweb.examples.StartServiceAndGrpcwebProxy - - - - jar-with-dependencies - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 1.4.1 - - - enforce - - enforce - - - - - - - - - - - - diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/InteropTestService.java b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/InteropTestService.java deleted file mode 100644 index 4f25b353d..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/InteropTestService.java +++ /dev/null @@ -1,546 +0,0 @@ -/* - * Copyright 2020 The 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. - */ - -// ******************* DO NOT EDIT -// This is copy of the following: -// github.com/grpc/grpc-java/blob/master/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java - -package grpcweb.examples; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Queues; -import com.google.protobuf.ByteString; -import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; -import io.grpc.Metadata; -import io.grpc.ServerCall; -import io.grpc.ServerCallHandler; -import io.grpc.ServerInterceptor; -import io.grpc.Status; -import io.grpc.internal.LogExceptionRunnable; -import io.grpc.protobuf.lite.ProtoLiteUtils; -import io.grpc.stub.ServerCallStreamObserver; -import io.grpc.stub.StreamObserver; -import grpc.testing.EmptyProtos; -import grpc.testing.Messages; -import grpc.testing.Messages.Payload; -import grpc.testing.Messages.ResponseParameters; -import grpc.testing.Messages.SimpleRequest; -import grpc.testing.Messages.SimpleResponse; -import grpc.testing.Messages.StreamingInputCallRequest; -import grpc.testing.Messages.StreamingInputCallResponse; -import grpc.testing.Messages.StreamingOutputCallRequest; -import grpc.testing.Messages.StreamingOutputCallResponse; -import grpc.testing.TestServiceGrpc; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Queue; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import javax.annotation.concurrent.GuardedBy; - -/** - * Implementation of the business logic for the TestService. Uses an executor to schedule chunks - * sent in response streams. - */ -public class InteropTestService extends TestServiceGrpc.TestServiceImplBase { - private final Random random = new Random(); - - private final ScheduledExecutorService executor; - private final ByteString compressableBuffer; - - private static final Metadata.Key METADATA_KEY = - Metadata.Key.of( - "grpc.testing.SimpleContext" + Metadata.BINARY_HEADER_SUFFIX, - ProtoLiteUtils.metadataMarshaller(Messages.SimpleContext.getDefaultInstance())); - private static final Metadata.Key ECHO_INITIAL_METADATA_KEY - = Metadata.Key.of("x-grpc-test-echo-initial", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key ECHO_TRAILING_METADATA_KEY - = Metadata.Key.of("x-grpc-test-echo-trailing-bin", Metadata.BINARY_BYTE_MARSHALLER); - - - /** - * Constructs a controller using the given executor for scheduling response stream chunks. - */ - public InteropTestService(ScheduledExecutorService executor) { - this.executor = executor; - this.compressableBuffer = ByteString.copyFrom(new byte[1024]); - } - - @Override - public void emptyCall(EmptyProtos.Empty request, - StreamObserver responseObserver) { - responseObserver.onNext(EmptyProtos.Empty.getDefaultInstance()); - responseObserver.onCompleted(); - } - - /** - * Immediately responds with a payload of the type and size specified in the request. - */ - @Override - public void unaryCall(SimpleRequest req, StreamObserver responseObserver) { - ServerCallStreamObserver obs = - (ServerCallStreamObserver) responseObserver; - SimpleResponse.Builder responseBuilder = SimpleResponse.newBuilder(); - try { - if (req.hasResponseCompressed() && req.getResponseCompressed().getValue()) { - obs.setCompression("gzip"); - } else { - obs.setCompression("identity"); - } - } catch (IllegalArgumentException e) { - System.out.println("sending error"); - obs.onError(Status.UNIMPLEMENTED - .withDescription("compression not supported.") - .withCause(e) - .asRuntimeException()); - return; - } - - if (req.getResponseSize() != 0) { - // For consistency with the c++ TestServiceImpl, use a random offset for unary calls. - // TODO(wonderfly): whether or not this is a good approach needs further discussion. - int offset = random.nextInt(compressableBuffer.size()); - ByteString payload = generatePayload(compressableBuffer, offset, req.getResponseSize()); - responseBuilder.setPayload( - Payload.newBuilder() - .setBody(payload)); - } - - if (req.hasResponseStatus()) { - obs.onError(Status.fromCodeValue(req.getResponseStatus().getCode()) - .withDescription(req.getResponseStatus().getMessage()) - .asRuntimeException()); - return; - } - - responseObserver.onNext(responseBuilder.build()); - responseObserver.onCompleted(); - } - - /** - * Given a request that specifies chunk size and interval between responses, creates and schedules - * the response stream. - */ - @Override - public void streamingOutputCall(StreamingOutputCallRequest request, - StreamObserver responseObserver) { - // Create and start the response dispatcher. - new ResponseDispatcher(responseObserver).enqueue(toChunkQueue(request)).completeInput(); - } - - /** - * Waits until we have received all of the request messages and then returns the aggregate payload - * size for all of the received requests. - */ - @Override - public StreamObserver streamingInputCall( - final StreamObserver responseObserver) { - return new StreamObserver() { - private int totalPayloadSize; - - @Override - public void onNext(StreamingInputCallRequest message) { - totalPayloadSize += message.getPayload().getBody().size(); - } - - @Override - public void onCompleted() { - responseObserver.onNext(StreamingInputCallResponse.newBuilder() - .setAggregatedPayloadSize(totalPayloadSize).build()); - responseObserver.onCompleted(); - } - - @Override - public void onError(Throwable cause) { - responseObserver.onError(cause); - } - }; - } - - /** - * True bi-directional streaming. Processes requests as they come in. Begins streaming results - * immediately. - */ - @Override - public StreamObserver fullDuplexCall( - final StreamObserver responseObserver) { - final ResponseDispatcher dispatcher = new ResponseDispatcher(responseObserver); - return new StreamObserver() { - @Override - public void onNext(StreamingOutputCallRequest request) { - if (request.hasResponseStatus()) { - dispatcher.cancel(); - dispatcher.onError(Status.fromCodeValue(request.getResponseStatus().getCode()) - .withDescription(request.getResponseStatus().getMessage()) - .asRuntimeException()); - return; - } - dispatcher.enqueue(toChunkQueue(request)); - } - - @Override - public void onCompleted() { - if (!dispatcher.isCancelled()) { - // Tell the dispatcher that all input has been received. - dispatcher.completeInput(); - } - } - - @Override - public void onError(Throwable cause) { - dispatcher.onError(cause); - } - }; - } - - /** - * Similar to {@link #fullDuplexCall}, except that it waits for all streaming requests to be - * received before starting the streaming responses. - */ - @Override - public StreamObserver halfDuplexCall( - final StreamObserver responseObserver) { - final ResponseDispatcher dispatcher = new ResponseDispatcher(responseObserver); - final Queue chunks = new ArrayDeque<>(); - return new StreamObserver() { - @Override - public void onNext(StreamingOutputCallRequest request) { - chunks.addAll(toChunkQueue(request)); - } - - @Override - public void onCompleted() { - // Dispatch all of the chunks in one shot. - dispatcher.enqueue(chunks).completeInput(); - } - - @Override - public void onError(Throwable cause) { - dispatcher.onError(cause); - } - }; - } - - /** - * Schedules the dispatch of a queue of chunks. Whenever chunks are added or input is completed, - * the next response chunk is scheduled for delivery to the client. When no more chunks are - * available, the stream is half-closed. - */ - private class ResponseDispatcher { - private final Chunk completionChunk = new Chunk(0, 0, 0); - private final Queue chunks; - private final StreamObserver responseStream; - private boolean scheduled; - @GuardedBy("this") private boolean cancelled; - private Throwable failure; - private Runnable dispatchTask = new Runnable() { - @Override - @SuppressWarnings("CatchAndPrintStackTrace") - public void run() { - try { - - // Dispatch the current chunk to the client. - try { - dispatchChunk(); - } catch (RuntimeException e) { - // Indicate that nothing is scheduled and re-throw. - synchronized (ResponseDispatcher.this) { - scheduled = false; - } - throw e; - } - - // Schedule the next chunk if there is one. - synchronized (ResponseDispatcher.this) { - // Indicate that nothing is scheduled. - scheduled = false; - scheduleNextChunk(); - } - } catch (Throwable t) { - t.printStackTrace(); - } - } - }; - - /** - * The {@link StreamObserver} will be used to send the queue of response chunks. Since calls to - * {@link StreamObserver} must be synchronized across threads, no further calls should be made - * directly on {@code responseStream} after it is provided to the {@link ResponseDispatcher}. - */ - public ResponseDispatcher(StreamObserver responseStream) { - this.chunks = Queues.newLinkedBlockingQueue(); - this.responseStream = responseStream; - } - - /** - * Adds the given chunks to the response stream and schedules the next chunk to be delivered if - * needed. - */ - public synchronized ResponseDispatcher enqueue(Queue moreChunks) { - assertNotFailed(); - chunks.addAll(moreChunks); - scheduleNextChunk(); - return this; - } - - /** - * Indicates that the input is completed and the currently enqueued response chunks are all that - * remain to be scheduled for dispatch to the client. - */ - public ResponseDispatcher completeInput() { - assertNotFailed(); - chunks.add(completionChunk); - scheduleNextChunk(); - return this; - } - - /** - * Allows the service to cancel the remaining responses. - */ - public synchronized void cancel() { - Preconditions.checkState(!cancelled, "Dispatcher already cancelled"); - chunks.clear(); - cancelled = true; - } - - public synchronized boolean isCancelled() { - return cancelled; - } - - private synchronized void onError(Throwable cause) { - responseStream.onError(cause); - } - - /** - * Dispatches the current response chunk to the client. This is only called by the executor. At - * any time, a given dispatch task should only be registered with the executor once. - */ - private synchronized void dispatchChunk() { - if (cancelled) { - return; - } - try { - // Pop off the next chunk and send it to the client. - Chunk chunk = chunks.remove(); - if (chunk == completionChunk) { - responseStream.onCompleted(); - } else { - responseStream.onNext(chunk.toResponse()); - } - } catch (Throwable e) { - failure = e; - if (Status.fromThrowable(e).getCode() == Status.CANCELLED.getCode()) { - // Stream was cancelled by client, responseStream.onError() might be called already or - // will be called soon by inbounding StreamObserver. - chunks.clear(); - } else { - responseStream.onError(e); - } - } - } - - /** - * Schedules the next response chunk to be dispatched. If all input has been received and there - * are no more chunks in the queue, the stream is closed. - */ - private void scheduleNextChunk() { - synchronized (this) { - if (scheduled) { - // Dispatch task is already scheduled. - return; - } - - // Schedule the next response chunk if there is one. - Chunk nextChunk = chunks.peek(); - if (nextChunk != null) { - scheduled = true; - // TODO(ejona): cancel future if RPC is cancelled - Future unused = executor.schedule(new LogExceptionRunnable(dispatchTask), - nextChunk.delayMicroseconds, TimeUnit.MICROSECONDS); - return; - } - } - } - - private void assertNotFailed() { - if (failure != null) { - throw new IllegalStateException("Stream already failed", failure); - } - } - } - - /** - * Breaks down the request and creates a queue of response chunks for the given request. - */ - public Queue toChunkQueue(StreamingOutputCallRequest request) { - Queue chunkQueue = new ArrayDeque<>(); - int offset = 0; - for (ResponseParameters params : request.getResponseParametersList()) { - chunkQueue.add(new Chunk(params.getIntervalUs(), offset, params.getSize())); - - // Increment the offset past this chunk. Buffer need to be circular. - offset = (offset + params.getSize()) % compressableBuffer.size(); - } - return chunkQueue; - } - - /** - * A single chunk of a response stream. Contains delivery information for the dispatcher and can - * be converted to a streaming response proto. A chunk just references it's payload in the - * {@link #compressableBuffer} array. The payload isn't actually created until {@link - * #toResponse()} is called. - */ - private class Chunk { - private final int delayMicroseconds; - private final int offset; - private final int length; - - public Chunk(int delayMicroseconds, int offset, int length) { - this.delayMicroseconds = delayMicroseconds; - this.offset = offset; - this.length = length; - } - - /** - * Convert this chunk into a streaming response proto. - */ - private StreamingOutputCallResponse toResponse() { - StreamingOutputCallResponse.Builder responseBuilder = - StreamingOutputCallResponse.newBuilder(); - ByteString payload = generatePayload(compressableBuffer, offset, length); - responseBuilder.setPayload( - Payload.newBuilder() - .setBody(payload)); - return responseBuilder.build(); - } - } - - /** - * Generates a payload of desired type and size. Reads compressableBuffer or - * uncompressableBuffer as a circular buffer. - */ - private ByteString generatePayload(ByteString dataBuffer, int offset, int size) { - ByteString payload = ByteString.EMPTY; - // This offset would never pass the array boundary. - int begin = offset; - int end = 0; - int bytesLeft = size; - while (bytesLeft > 0) { - end = Math.min(begin + bytesLeft, dataBuffer.size()); - // ByteString.substring returns the substring from begin, inclusive, to end, exclusive. - payload = payload.concat(dataBuffer.substring(begin, end)); - bytesLeft -= (end - begin); - begin = end % dataBuffer.size(); - } - return payload; - } - - /** Returns interceptors necessary for full service implementation. */ - public static List interceptors() { - return Arrays.asList( - echoRequestHeadersInterceptor(METADATA_KEY), - echoRequestMetadataInHeaders(ECHO_INITIAL_METADATA_KEY), - echoRequestMetadataInTrailers(ECHO_TRAILING_METADATA_KEY)); - } - - /** - * Echo the request headers from a client into response headers and trailers. Useful for - * testing end-to-end metadata propagation. - */ - private static ServerInterceptor echoRequestHeadersInterceptor(final Metadata.Key... keys) { - final Set> keySet = new HashSet<>(Arrays.asList(keys)); - return new ServerInterceptor() { - @Override - public ServerCall.Listener interceptCall( - ServerCall call, - final Metadata requestHeaders, - ServerCallHandler next) { - return next.startCall(new SimpleForwardingServerCall(call) { - @Override - public void sendHeaders(Metadata responseHeaders) { - responseHeaders.merge(requestHeaders, keySet); - super.sendHeaders(responseHeaders); - } - - @Override - public void close(Status status, Metadata trailers) { - trailers.merge(requestHeaders, keySet); - super.close(status, trailers); - } - }, requestHeaders); - } - }; - } - - /** - * Echoes request headers with the specified key(s) from a client into response headers only. - */ - private static ServerInterceptor echoRequestMetadataInHeaders(final Metadata.Key... keys) { - final Set> keySet = new HashSet<>(Arrays.asList(keys)); - return new ServerInterceptor() { - @Override - public ServerCall.Listener interceptCall( - ServerCall call, - final Metadata requestHeaders, - ServerCallHandler next) { - return next.startCall(new SimpleForwardingServerCall(call) { - @Override - public void sendHeaders(Metadata responseHeaders) { - responseHeaders.merge(requestHeaders, keySet); - super.sendHeaders(responseHeaders); - } - - @Override - public void close(Status status, Metadata trailers) { - super.close(status, trailers); - } - }, requestHeaders); - } - }; - } - - /** - * Echoes request headers with the specified key(s) from a client into response trailers only. - */ - private static ServerInterceptor echoRequestMetadataInTrailers(final Metadata.Key... keys) { - final Set> keySet = new HashSet<>(Arrays.asList(keys)); - return new ServerInterceptor() { - @Override - public ServerCall.Listener interceptCall( - ServerCall call, - final Metadata requestHeaders, - ServerCallHandler next) { - return next.startCall(new SimpleForwardingServerCall(call) { - @Override - public void sendHeaders(Metadata responseHeaders) { - super.sendHeaders(responseHeaders); - } - - @Override - public void close(Status status, Metadata trailers) { - trailers.merge(requestHeaders, keySet); - super.close(status, trailers); - } - }, requestHeaders); - } - }; - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/StartServiceAndGrpcwebProxy.java b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/StartServiceAndGrpcwebProxy.java deleted file mode 100644 index 06911a658..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/StartServiceAndGrpcwebProxy.java +++ /dev/null @@ -1,44 +0,0 @@ -package grpcweb.examples; - -import io.grpc.BindableService; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import io.grpc.ServerInterceptors; -import io.grpcweb.GrpcPortNumRelay; -import io.grpcweb.JettyWebserverForGrpcwebTraffic; -import java.util.concurrent.Executors; - -/** - * This class starts the service on a port (property: grpc-port) - * and starts the grpc-web proxy on a different port (property: grpc-web-port). - */ -class StartServiceAndGrpcwebProxy { - private void startGrpcService(int port) throws Exception { - Server grpcServer = ServerBuilder.forPort(port) - .addService( - ServerInterceptors.intercept( - (BindableService) new InteropTestService(Executors.newSingleThreadScheduledExecutor()), - InteropTestService.interceptors())) - .build(); - grpcServer.start(); - System.out.println("**** started gRPC Service on port# " + port); - } - - void start() throws Exception { - int grpcPort = Util.getGrpcServicePortNum(); - int grpcWebPort = Util.getGrpcwebServicePortNum(); - - // Start the Grpc service on grpc-port - startGrpcService(grpcPort); - - // Start the grpc-web proxy on grpc-web-port. - (new JettyWebserverForGrpcwebTraffic(grpcWebPort)).start(); - - // grpc-web proxy needs to know the grpc-port# so it could connect to the grpc service. - GrpcPortNumRelay.setGrpcPortNum(grpcPort); - } - - public static void main(String[] args) throws Exception { - new StartServiceAndGrpcwebProxy().start(); - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/Util.java b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/Util.java deleted file mode 100644 index d760d71a5..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/java/grpcweb/examples/Util.java +++ /dev/null @@ -1,22 +0,0 @@ -package grpcweb.examples; - -import java.io.IOException; -import java.util.Properties; - -class Util { - private static int getIntPropertyValue(String s) throws IOException { - java.io.InputStream inputStream = - Thread.currentThread().getContextClassLoader().getResourceAsStream("my.properties"); - java.util.Properties properties = new Properties(); - properties.load(inputStream); - return Integer.parseInt(properties.getProperty(s)); - } - - static int getGrpcServicePortNum() throws IOException { - return getIntPropertyValue("grpc-port"); - } - - static int getGrpcwebServicePortNum() throws IOException { - return getIntPropertyValue("grpc-web-port"); - } -} diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/empty.proto b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/empty.proto deleted file mode 100644 index c7584c88b..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/empty.proto +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2015 The 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. - -// ******************* DO NOT EDIT -// This is copy of the following: -// github.com/grpc/grpc-java/blob/master/interop-testing/src/main/proto/grpc/testing/empty.proto - -syntax = "proto2"; - -package grpc.testing; - -option java_package = "grpc.testing"; -option java_outer_classname = "EmptyProtos"; - -// 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 {} diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/messages.proto b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/messages.proto deleted file mode 100644 index 9fb45a6da..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/messages.proto +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2015 The 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. - -// ******************* DO NOT EDIT -// This is copy of the following: -// github.com/grpc/grpc-java/blob/master/interop-testing/src/main/proto/grpc/testing/messages.proto - -syntax = "proto3"; - -package grpc.testing; - -option java_package = "grpc.testing"; - -// TODO(jihuncho): Use well-known types once all languages are synced. -message BoolValue { - // The bool value. - bool value = 1; -} - -// A block of data, to simply increase gRPC message size. -message Payload { - reserved 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 { - reserved 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; -} - -// 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; -} - -message SimpleContext { - string value = 1; -} - -// 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 { - reserved 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 rpcs_by_peer = 1; - // The number of RPCs that failed to record a remote peer. - int32 num_failures = 2; -} diff --git a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/test.proto b/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/test.proto deleted file mode 100644 index 26347d54d..000000000 --- a/net/grpc/gateway/examples/grpc-web-java/interop-test-service/src/main/proto/grpc/testing/test.proto +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2015 The 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. -// -// -// ******************* DO NOT EDIT -// -// This is copy of the following: -// github.com/grpc/grpc-java/blob/master/interop-testing/src/main/proto/grpc/testing/test.proto -// -// ********************* - -syntax = "proto3"; - -import "grpc/testing/empty.proto"; -import "grpc/testing/messages.proto"; - -package grpc.testing; - -option java_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.Empty) 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) {} -} diff --git a/net/grpc/gateway/examples/helloworld/README.md b/net/grpc/gateway/examples/helloworld/README.md index e149a4b17..3bf95c354 100644 --- a/net/grpc/gateway/examples/helloworld/README.md +++ b/net/grpc/gateway/examples/helloworld/README.md @@ -210,8 +210,8 @@ the `client.js` files. "google-protobuf": "~3.14.0", "grpc-web": "~1.4.2", "lodash": "~4.17.0", - "webpack": "~4.43.0", - "webpack-cli": "~3.3.11" + "webpack": "~5.82.1", + "webpack-cli": "~5.1.1" } } ``` diff --git a/net/grpc/gateway/examples/helloworld/package.json b/net/grpc/gateway/examples/helloworld/package.json index 377ef61d7..a98db24a9 100644 --- a/net/grpc/gateway/examples/helloworld/package.json +++ b/net/grpc/gateway/examples/helloworld/package.json @@ -10,7 +10,7 @@ "google-protobuf": "~3.14.0", "grpc-web": "~1.4.2", "lodash": "~4.17.0", - "webpack": "~4.43.0", - "webpack-cli": "~3.3.11" + "webpack": "~5.82.1", + "webpack-cli": "~5.1.1" } } diff --git a/packages/grpc-web/docker/jsunit-test/Dockerfile b/packages/grpc-web/docker/jsunit-test/Dockerfile index a82706ce2..29519f022 100644 --- a/packages/grpc-web/docker/jsunit-test/Dockerfile +++ b/packages/grpc-web/docker/jsunit-test/Dockerfile @@ -1,7 +1,7 @@ FROM selenium/standalone-chrome:93.0.4577.63 -# Matching the node version used in the node:12.22.6-stretch image. -ARG NODE_VERSION=12.22.6 +# Matching the node version used in the node:20.0.0-bullseye image. +ARG NODE_VERSION=20.0.0 USER root diff --git a/packages/grpc-web/scripts/generate_test_files.sh b/packages/grpc-web/scripts/generate_test_files.sh index 74215086b..cd1989091 100755 --- a/packages/grpc-web/scripts/generate_test_files.sh +++ b/packages/grpc-web/scripts/generate_test_files.sh @@ -32,7 +32,7 @@ cd "$REPO_DIR" mkdir -p "$GEN_DIR" echo "Generating dependency file..." -$(npm bin)/closure-make-deps \ +npx closure-make-deps \ --closure-path="node_modules/google-closure-library/closure/goog" \ --file="node_modules/google-closure-library/closure/goog/deps.js" \ --root="$JAVASCRIPT_DIR" \ diff --git a/packages/grpc-web/scripts/run_jsunit_tests.sh b/packages/grpc-web/scripts/run_jsunit_tests.sh index e9d2178fe..a3a5a055c 100755 --- a/packages/grpc-web/scripts/run_jsunit_tests.sh +++ b/packages/grpc-web/scripts/run_jsunit_tests.sh @@ -27,7 +27,6 @@ set -e cd "$(dirname $(dirname "$0"))" -NPM_BIN_PATH=$(npm bin) PROTRACTOR_BIN_PATH="./node_modules/protractor/bin" function cleanup () { @@ -37,7 +36,7 @@ function cleanup () { # Start the local webserver. echo "Starting local HTP Server..." -$NPM_BIN_PATH/gulp serve & +npx gulp serve & serverPid=$! echo "Local HTTP Server started with PID $serverPid." diff --git a/scripts/run_interop_tests.sh b/scripts/run_interop_tests.sh index 255dbbcdc..5c344cf7c 100755 --- a/scripts/run_interop_tests.sh +++ b/scripts/run_interop_tests.sh @@ -25,7 +25,6 @@ REPO_DIR=$(realpath "${SCRIPT_DIR}/..") # Set up cd "${REPO_DIR}" - # These programs need to be already installed progs=(docker docker-compose npm) for p in "${progs[@]}" @@ -38,18 +37,17 @@ function cleanup () { echo "Killing lingering Docker servers..." docker rm -f "$pid1" docker rm -f "$pid2" - docker rm -f "$pid3" } trap cleanup EXIT # Build all relevant docker images. They should all build successfully. -docker-compose build prereqs node-interop-server java-interop-server +docker-compose build prereqs node-interop-server ########################################################## # Run interop tests (against Envoy) ########################################################## -echo -e "\n[Running] Interop test #1 - against Envoy" +echo -e "\n[Running] Interop test (Envoy)" pid1=$(docker run -d \ -v "$(pwd)"/test/interop/envoy.yaml:/etc/envoy/envoy.yaml:ro \ --network=host envoyproxy/envoy:v1.22.0) @@ -58,19 +56,4 @@ pid2=$(docker run -d --network=host grpcweb/node-interop-server) run_tests docker rm -f "$pid1" -docker rm -f "$pid2" - - -########################################################## -# Run interop tests (against grpc-web Java connector code) -########################################################## -echo -e "\n[Running] Interop test #2 - against Java interop server" -pid3=$(docker run -d --network=host grpcweb/java-interop-server) - -run_tests - -docker rm -f "$pid3" - -# Clean up -git clean -f -d -x -echo 'Completed' +docker rm -f "$pid2" \ No newline at end of file diff --git a/src/connector/README.md b/src/connector/README.md deleted file mode 100644 index d2762a443..000000000 --- a/src/connector/README.md +++ /dev/null @@ -1,67 +0,0 @@ -(On Hold) Java gRPC-web in-process proxy -==================================================== - -### _(Development on the Java in-process proxy is On Hold. No active development or bug fixes is being done at this moment.)_ - -### Background & Motivation -This project enables gRPC-web support in a Java Service that currently -serves only gRPC clients but not equipped to handle gRPC-web clients. -This project provides a java jar file that can be added to the Java Service -when it is deployed. There are minimal changes to be made to the Java Service -before linking the jar file provided by this project and re-deploying the -Java Service. - -### How to use it in your Java Service -Here are the steps needed to use this project to add gRPC-web client serving -capability to your existing Java Service (that only serves gRPC clients but not -gRPC-web clients) - -Examine the code in the following -[dir](../../net/grpc/gateway/examples/grpc-web-java/greeter-service) - -- A Java Service specified by -[this proto](../../net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/proto/greeter.proto) is implemented -[here](../../net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/GreeterService.java) - -- [This](../../net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/StartServiceAndGrpcwebProxy.java) -code starts the above gRPC-Service and a gRPC-web in-process proxy -provided by this project. - -- Since you will already have implemented code to start your gRPC-Service, -add code to start gRPC-web in-process proxy, as demonstrated by -the above. - -- The only critical piece of info needed by the gRPC-web in-process proxy is the port -to listen on, for gRPC-web requests. -[This file](../../net/grpc/gateway/examples/grpc-web-java/greeter-service/src/main/java/grpcweb/examples/greeter/Util.java) - externalizes grpc-web port# thru a static member. - -Rebuild your Service with the Java jar file provided by this project and re-deploy. - -Voila! Your service can now service gRPC and gRPC-web clients! - -### How to run the [Interop tests](https://github.com/grpc/grpc-web/blob/master/test/interop/README.md) with this code - -- Install "grpc-web java" jar locally -```shell script -$ cd src/connector -$ mvn install -``` -- Bring up a Test Service with this code as "grpc-web in-process proxy" -```shell script - $ cd net/grpc/gateway/examples/grpc-web-java/interop-test-service - $ mvn package - $ java -jar target/interop-test-0.1-jar-with-dependencies.jar -``` -- Run the interop tests from browser -```shell script -$ cd test/interop -$ docker-compose up interop-client -``` -Open browser at http://localhost:8081/index.html and -check the console for messages like the following: -```shell script -EmptyUnary: passed -LargeUnary: passed -etc.. -``` diff --git a/src/connector/pom.xml b/src/connector/pom.xml deleted file mode 100644 index 661e43b7a..000000000 --- a/src/connector/pom.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - 4.0.0 - - io.grpcweb - grpcweb-java - 0.1-SNAPSHOT - Java gRPC-web in-process proxy - - - UTF-8 - 1.7 - 1.7 - 1.33.0 - - - - - - io.grpc - grpc-bom - ${grpc.version} - pom - import - - - - - - - org.eclipse.jetty - jetty-server - 10.0.14 - - - org.eclipse.jetty - jetty-servlet - 9.4.40.v20210413 - - - commons-io - commons-io - 2.8.0 - - - org.apache.commons - commons-lang3 - 3.12.0 - - - io.grpc - grpc-protobuf - - - io.grpc - grpc-stub - - - com.google.inject - guice - 4.2.3 - - - - junit - junit - 4.13.2 - test - - - com.google.truth - truth - 1.1.2 - test - - - diff --git a/src/connector/src/main/java/io/grpcweb/CorsFilter.java b/src/connector/src/main/java/io/grpcweb/CorsFilter.java deleted file mode 100644 index f408f1e01..000000000 --- a/src/connector/src/main/java/io/grpcweb/CorsFilter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ - -package io.grpcweb; - -import java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; -import com.google.common.net.HttpHeaders; - -public class CorsFilter implements Filter { - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - this.handle((HttpServletRequest)request, (HttpServletResponse)response); - chain.doFilter(request, response); - } - - private void handle(HttpServletRequest req, HttpServletResponse resp) - throws IOException, ServletException { - resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, - StringUtils.joinWith(",", - "user-agent", - "cache-control", - "content-type", - "content-transfer-encoding", - "grpc-timeout", - "keep-alive", - "x-accept-content-transfer-encoding", - "x-accept-response-streaming", - "x-grpc-test-echo-initial", - "x-grpc-test-echo-trailing-bin", - "x-grpc-web", - "x-user-agent")); - resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, - req.getHeader("Origin")); - resp.setHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, - "content-type,x-grpc-web"); - resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, - "OPTIONS,GET,POST,HEAD"); - resp.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, - StringUtils.joinWith(",", - "x-grpc-test-echo-initial", - "x-grpc-test-echo-trailing-bin", - "grpc-status", - "grpc-message")); - } - - @Override - public void init(FilterConfig arg0) throws ServletException {} - - @Override - public void destroy() {} -} diff --git a/src/connector/src/main/java/io/grpcweb/DebugInfo.java b/src/connector/src/main/java/io/grpcweb/DebugInfo.java deleted file mode 100644 index b9dc6875e..000000000 --- a/src/connector/src/main/java/io/grpcweb/DebugInfo.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ - -package io.grpcweb; - -import java.lang.invoke.MethodHandles; -import java.util.Enumeration; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.servlet.http.HttpServletRequest; - -class DebugInfo { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - - static void printRequest(HttpServletRequest req) { - if (!LOG.isLoggable(Level.FINE)) return; - - StringBuilder sb = new StringBuilder(); - Enumeration headerNames = req.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - Enumeration headers = req.getHeaders(headerName); - while (headers.hasMoreElements()) { - String headerValue = headers.nextElement(); - sb.append("\n\t" + headerName + " = " + headerValue); - } - } - sb.append("\n\t ContextPath: ").append(req.getContextPath()); - sb.append("\n\t LocalAddr: ").append(req.getLocalAddr()); - sb.append("\n\t LocalName: ").append(req.getLocalName()); - sb.append("\n\t LocalPort: ").append(req.getLocalPort()); - sb.append("\n\t PathInfo: ").append(req.getPathInfo()); - sb.append("\n\t PathTranslated: ").append(req.getPathTranslated()); - sb.append("\n\t Protocol: ").append(req.getProtocol()); - sb.append("\n\t RemoteAddr: ").append(req.getRemoteAddr()); - sb.append("\n\t RemoteHost: ").append(req.getRemoteHost()); - sb.append("\n\t RemotePort: ").append(req.getRemotePort()); - sb.append("\n\t RequestURI: ").append(req.getRequestURI()); - sb.append("\n\t RequestURL: ").append(req.getRequestURL()); - sb.append("\n\t Scheme: ").append(req.getScheme()); - sb.append("\n\t ServerName: ").append(req.getServerName()); - sb.append("\n\t ServerPort: ").append(req.getServerPort()); - sb.append("\n\t ServletPath: ").append(req.getServletPath()); - sb.append("\n\t Method: ").append(req.getMethod()); - LOG.fine(sb.toString()); - } -} diff --git a/src/connector/src/main/java/io/grpcweb/GrpcPortNumRelay.java b/src/connector/src/main/java/io/grpcweb/GrpcPortNumRelay.java deleted file mode 100644 index f0e0d8083..000000000 --- a/src/connector/src/main/java/io/grpcweb/GrpcPortNumRelay.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -public class GrpcPortNumRelay { - public static void setGrpcPortNum(int i) { - // TODO This class & method names are wrong - involves more than just setting the portnum - GrpcWebGuiceModule.init(i); - } -} diff --git a/src/connector/src/main/java/io/grpcweb/GrpcServiceConnectionManager.java b/src/connector/src/main/java/io/grpcweb/GrpcServiceConnectionManager.java deleted file mode 100644 index fc29180d4..000000000 --- a/src/connector/src/main/java/io/grpcweb/GrpcServiceConnectionManager.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ - -package io.grpcweb; - -import com.google.inject.Singleton; -import io.grpc.Channel; -import io.grpc.ClientInterceptors; -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; -import java.lang.invoke.MethodHandles; -import java.util.logging.Logger; - -/** - * TODO: Manage the connection pool to talk to the grpc-service - */ -@Singleton -class GrpcServiceConnectionManager { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - private final ManagedChannel mChannel; - - GrpcServiceConnectionManager(int grpcPortNum) { - // TODO: Manage a connection pool. - mChannel = ManagedChannelBuilder.forAddress("localhost", grpcPortNum) - .usePlaintext() - .build(); - LOG.info("**** connection channel initiated"); - } - - Channel getChannelWithClientInterceptor(GrpcWebClientInterceptor interceptor) { - return ClientInterceptors.intercept(mChannel, interceptor); - } -} diff --git a/src/connector/src/main/java/io/grpcweb/GrpcWebClientInterceptor.java b/src/connector/src/main/java/io/grpcweb/GrpcWebClientInterceptor.java deleted file mode 100644 index bd724ca21..000000000 --- a/src/connector/src/main/java/io/grpcweb/GrpcWebClientInterceptor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ - -package io.grpcweb; - -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientCall.Listener; -import io.grpc.ClientInterceptor; -import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; -import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import java.util.concurrent.CountDownLatch; -import javax.servlet.http.HttpServletResponse; - -class GrpcWebClientInterceptor implements ClientInterceptor { - - private final CountDownLatch mLatch; - private final HttpServletResponse mResp; - private final SendResponse mSendResponse; - - GrpcWebClientInterceptor(HttpServletResponse resp, CountDownLatch latch, SendResponse send) { - mLatch = latch; - mResp = resp; - mSendResponse = send; - } - - @Override - public ClientCall interceptCall(MethodDescriptor method, - CallOptions callOptions, Channel channel) { - return new SimpleForwardingClientCall(channel.newCall(method, callOptions)) { - @Override - public void start(Listener responseListener, Metadata headers) { - super.start(new MetadataResponseListener(responseListener), headers); - } - }; - } - - class MetadataResponseListener extends SimpleForwardingClientCallListener { - private boolean headersSent = false; - - MetadataResponseListener(Listener responseListener) { - super(responseListener); - } - - @Override - public void onHeaders(Metadata h) { - mSendResponse.writeHeaders(h); - headersSent = true; - } - - @Override - public void onClose(Status s, Metadata t) { - if (!headersSent) { - // seems, sometimes onHeaders() is not called before this method is called! - // so far, they are the error cases. let onError() method in ClientListener - // handle this call. Could ignore this. - // TODO is this correct? what if onError() never gets called? - } else { - mSendResponse.writeTrailer(s, t); - mLatch.countDown(); - } - super.onClose(s, t); - } - } -} diff --git a/src/connector/src/main/java/io/grpcweb/GrpcWebGuiceModule.java b/src/connector/src/main/java/io/grpcweb/GrpcWebGuiceModule.java deleted file mode 100644 index 1f76401fe..000000000 --- a/src/connector/src/main/java/io/grpcweb/GrpcWebGuiceModule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import com.google.inject.AbstractModule; -import com.google.inject.Guice; -import com.google.inject.Injector; - -class GrpcWebGuiceModule extends AbstractModule { - private static Injector sInjector; - private static int sGrpcPortNum; - - // This method should be called only once. - static void init(int i) { - sGrpcPortNum = i; - sInjector = Guice.createInjector(new GrpcWebGuiceModule()); - } - - static Injector getInjector() { - return sInjector; - } - - @Override - protected void configure() { - bind(GrpcServiceConnectionManager.class) - .toInstance(new GrpcServiceConnectionManager(sGrpcPortNum)); - } -} diff --git a/src/connector/src/main/java/io/grpcweb/GrpcWebTrafficServlet.java b/src/connector/src/main/java/io/grpcweb/GrpcWebTrafficServlet.java deleted file mode 100644 index fd1d92405..000000000 --- a/src/connector/src/main/java/io/grpcweb/GrpcWebTrafficServlet.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * The main class that handles all the grpc-web traffic. - */ -public class GrpcWebTrafficServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) { - RequestHandler reqHandler = GrpcWebGuiceModule.getInjector().getInstance(RequestHandler.class); - reqHandler.handle(request, response); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) { - doGet(request, response); - } -} diff --git a/src/connector/src/main/java/io/grpcweb/JettyWebserverForGrpcwebTraffic.java b/src/connector/src/main/java/io/grpcweb/JettyWebserverForGrpcwebTraffic.java deleted file mode 100644 index 990b5f397..000000000 --- a/src/connector/src/main/java/io/grpcweb/JettyWebserverForGrpcwebTraffic.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import java.lang.invoke.MethodHandles; -import java.util.EnumSet; -import java.util.logging.Logger; -import javax.servlet.DispatcherType; -import org.eclipse.jetty.servlet.ServletHandler; - -public class JettyWebserverForGrpcwebTraffic { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - - private final int mGrpcwebPort; - - public JettyWebserverForGrpcwebTraffic(int grpcwebPort) { - mGrpcwebPort = grpcwebPort; - } - - public org.eclipse.jetty.server.Server start() { - // Start a jetty server to listen on the grpc-web port# - org.eclipse.jetty.server.Server jServer = new org.eclipse.jetty.server.Server(mGrpcwebPort); - ServletHandler handler = new ServletHandler(); - jServer.setHandler(handler); - handler.addFilterWithMapping(CorsFilter.class, "/*", - EnumSet.of(DispatcherType.REQUEST)); - handler.addServletWithMapping(GrpcWebTrafficServlet.class, "/*"); - try { - jServer.start(); - } catch (Exception e) { - LOG.warning("Jetty Server couldn't be started. " + e.getLocalizedMessage()); - e.printStackTrace(); - return null; - } - LOG.info("**** started gRPC-web Service on port# " + mGrpcwebPort); - jServer.setStopAtShutdown(true); - return jServer; - } -} diff --git a/src/connector/src/main/java/io/grpcweb/MessageDeframer.java b/src/connector/src/main/java/io/grpcweb/MessageDeframer.java deleted file mode 100644 index 86cd511ce..000000000 --- a/src/connector/src/main/java/io/grpcweb/MessageDeframer.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import io.grpcweb.MessageHandler.ContentType; -import java.io.IOException; -import java.io.InputStream; -import java.lang.invoke.MethodHandles; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.logging.Logger; -import org.apache.commons.io.IOUtils; - -/** - * Reads frames from the input bytes and returns a single message. - */ -class MessageDeframer { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - - static final byte DATA_BYTE = (byte) 0x00; - - // TODO: fix this code to be able to handle upto 4GB input size. - private int mLength = 0; - private int mReadSoFar = 0; - - private ArrayList mFrames = new ArrayList<>(); - private byte[] mMsg = null; - private int mNumFrames; - - byte[] getMessageBytes() { return mMsg;} - int getLength() { return mLength;} - int getNumberOfFrames() {return mNumFrames;} - - /** Reads the bytes from the given InputStream and populates bytes in {@link #mMsg} - */ - boolean processInput(InputStream in, MessageHandler.ContentType contentType) { - byte[] inBytes; - try { - InputStream inStream = (contentType == ContentType.GRPC_WEB_TEXT) - ? Base64.getDecoder().wrap(in) - : in; - inBytes = IOUtils.toByteArray(inStream); - } catch (IOException e) { - e.printStackTrace(); - LOG.warning("invalid input"); - return false; - } - if (inBytes.length < 5) { - LOG.fine("invalid input. Expected minimum of 5 bytes"); - return false; - } - - while (getNextFrameBytes(inBytes)) {} - mNumFrames = mFrames.size(); - - // common case is only one frame. - if (mNumFrames == 1) { - mMsg = mFrames.get(0); - } else { - // concatenate all frames into one byte array - // TODO: this is inefficient. - mMsg = new byte[mLength]; - int offset = 0; - for (byte[] f : mFrames) { - System.arraycopy(f, 0, mMsg, offset, f.length); - offset += f.length; - } - mFrames = null; - } - return true; - } - - /** returns true if the next frame is a DATA frame */ - private boolean getNextFrameBytes(byte[] inBytes) { - // Firstbyte should be 0x00 (for this to be a DATA frame) - int firstByteValue = inBytes[mReadSoFar] | DATA_BYTE; - if (firstByteValue != 0) { - LOG.fine("done with DATA bytes"); - return false; - } - - // Next 4 bytes = length of the bytes array starting after the 4 bytes. - int offset = mReadSoFar + 1; - int len = ByteBuffer.wrap(inBytes, offset, 4).getInt(); - - // Empty message is special case. - // TODO: Can this is special handling be removed? - if (len == 0) { - mFrames.add(new byte[0]); - return false; - } - - // Make sure we have enough bytes in the inputstream - int expectedNumBytes = len + 5 + mReadSoFar; - if (inBytes.length < expectedNumBytes) { - LOG.warning(String.format("input doesn't have enough bytes. expected: %d, found %d", - expectedNumBytes, inBytes.length)); - return false; - } - - // Read "len" bytes into message - mLength += len; - offset += 4; - byte[] inputBytes = Arrays.copyOfRange(inBytes, offset, len + offset); - mFrames.add(inputBytes); - mReadSoFar += (len + 5); - // we have more frames to process, if there are bytes unprocessed - return inBytes.length > mReadSoFar; - } -} diff --git a/src/connector/src/main/java/io/grpcweb/MessageFramer.java b/src/connector/src/main/java/io/grpcweb/MessageFramer.java deleted file mode 100644 index 88e3f1fc1..000000000 --- a/src/connector/src/main/java/io/grpcweb/MessageFramer.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -/** - * Creates frames from the input bytes. - */ -class MessageFramer { - enum Type { - DATA ((byte) 0x00), - TRAILER ((byte) 0x80); - - public final byte value; - Type(byte b) { - value = b; - } - } - - // TODO: handle more than single frame; i.e., input byte array size > (2GB - 1) - byte[] getPrefix(byte[] in, Type type) { - int len = in.length; - return new byte[] { - type.value, - (byte) ((len >> 24) & 0xff), - (byte) ((len >> 16) & 0xff), - (byte) ((len >> 8) & 0xff), - (byte) ((len >> 0) & 0xff), - }; - } -} diff --git a/src/connector/src/main/java/io/grpcweb/MessageHandler.java b/src/connector/src/main/java/io/grpcweb/MessageHandler.java deleted file mode 100644 index 4d97e3019..000000000 --- a/src/connector/src/main/java/io/grpcweb/MessageHandler.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import com.google.common.annotations.VisibleForTesting; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; - -class MessageHandler { - @VisibleForTesting - enum ContentType { - GRPC_WEB_BINARY, - GRPC_WEB_TEXT; - } - - private static Map GRPC_GCP_CONTENT_TYPES = - new HashMap() {{ - put("application/grpc-web", ContentType.GRPC_WEB_BINARY); - put("application/grpc-web+proto", ContentType.GRPC_WEB_BINARY); - put("application/grpc-web-text", ContentType.GRPC_WEB_TEXT); - put("application/grpc-web-text+proto", ContentType.GRPC_WEB_TEXT); - }}; - - /** - * Validate the content-type - */ - ContentType validateContentType(HttpServletRequest req) throws IllegalArgumentException { - String contentType = req.getContentType(); - if (contentType == null || !GRPC_GCP_CONTENT_TYPES.containsKey(contentType)) { - throw new IllegalArgumentException("This content type is not used for grpc-web: " - + contentType); - } - return getContentType(contentType); - } - - static ContentType getContentType(String type) { - return GRPC_GCP_CONTENT_TYPES.get(type); - } - - /** - * Find the input arg protobuf class for the given rpc-method. - * Convert the given bytes to the input protobuf. return that. - */ - Object getInputProtobufObj(Method rpcMethod, byte[] in) { - Class[] inputArgs = rpcMethod.getParameterTypes(); - Class inputArgClass = inputArgs[0]; - - // use the inputArg classtype to create a protobuf object - Method parseFromObj; - try { - parseFromObj = inputArgClass.getMethod("parseFrom", byte[].class); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("Couldn't find method in 'parseFrom' in " - + inputArgClass.getName()); - } - - Object inputObj; - try { - inputObj = parseFromObj.invoke(null, in); - } catch (InvocationTargetException | IllegalAccessException e) { - throw new IllegalArgumentException(e); - } - - if (inputObj == null || !inputArgClass.isInstance(inputObj)) { - throw new IllegalArgumentException( - "Input obj is **not** instance of the correct input class type"); - } - return inputObj; - } -} diff --git a/src/connector/src/main/java/io/grpcweb/MetadataUtil.java b/src/connector/src/main/java/io/grpcweb/MetadataUtil.java deleted file mode 100644 index fda478c37..000000000 --- a/src/connector/src/main/java/io/grpcweb/MetadataUtil.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import io.grpc.Metadata; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; - -class MetadataUtil { - private static final String BINARY_HEADER_SUFFIX = "-bin"; - private static final String GRPC_HEADER_PREFIX = "x-grpc-"; - private static final List EXCLUDED = Arrays.asList("x-grpc-web", "content-type", - "grpc-accept-encoding", "grpc-encoding"); - - static Metadata getHtpHeaders(HttpServletRequest req) { - Metadata httpHeaders = new Metadata(); - Enumeration headerNames = req.getHeaderNames(); - if (headerNames == null) { - return httpHeaders; - } - // copy all headers "x-grpc-*" into Metadata - // TODO: do we need to copy all "x-*" headers instead? - while (headerNames.hasMoreElements()) { - String headerName = headerNames.nextElement(); - if (EXCLUDED.contains(headerName.toLowerCase())) { - continue; - } - if (headerName.toLowerCase().startsWith(GRPC_HEADER_PREFIX)) { - // Get all the values of this header. - Enumeration values = req.getHeaders(headerName); - if (values != null) { - // Java enumerations have klunky API. lets convert to a list. - // this will be a short list usually. - List list = Collections.list(values); - for (String s : list) { - if (headerName.toLowerCase().endsWith(BINARY_HEADER_SUFFIX)) { - // Binary header - httpHeaders.put( - Metadata.Key.of(headerName, Metadata.BINARY_BYTE_MARSHALLER), s.getBytes()); - } else { - // String header - httpHeaders.put( - Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER), s); - } - } - } - } - } - return httpHeaders; - } - - static Map getHttpHeadersFromMetadata(Metadata trailer) { - Map map = new HashMap<>(); - for (String key : trailer.keys()) { - if (EXCLUDED.contains(key.toLowerCase())) { - continue; - } - if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { - // TODO allow any object type here - byte[] value = trailer.get(Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER)); - map.put(key, new String(value)); - } else { - String value = trailer.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)); - map.put(key, value); - } - } - return map; - } -} diff --git a/src/connector/src/main/java/io/grpcweb/RequestHandler.java b/src/connector/src/main/java/io/grpcweb/RequestHandler.java deleted file mode 100644 index 3ac14c3eb..000000000 --- a/src/connector/src/main/java/io/grpcweb/RequestHandler.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import com.google.inject.Inject; -import io.grpc.Channel; -import io.grpc.Metadata; -import io.grpc.Status; -import io.grpc.stub.MetadataUtils; -import io.grpc.stub.StreamObserver; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; -import javax.servlet.ServletInputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -class RequestHandler { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - - private final MessageHandler mMessageHandler; - private final GrpcServiceConnectionManager mGrpcServiceConnectionManager; - - @Inject - RequestHandler(GrpcServiceConnectionManager g, MessageHandler m) { - mMessageHandler = m; - mGrpcServiceConnectionManager = g; - } - - public void handle(final HttpServletRequest req, final HttpServletResponse resp) { - DebugInfo.printRequest(req); - MessageHandler.ContentType contentType = mMessageHandler.validateContentType(req); - SendResponse sendResponse = new SendResponse(req, resp); - - try { - // From the request, get the rpc-method name and class name and then get their corresponding - // concrete objects. - Pair classAndMethodNames = getClassAndMethod(req); - String className = classAndMethodNames.getLeft(); - String methodName = classAndMethodNames.getRight(); - Class cls = getClassObject(className); - if (cls == null) { - LOG.info("incorrect classname in the request: " + className); - // incorrect classname specified in the request. - sendResponse.returnUnimplementedStatusCode(); - return; - } - - // Create a ClientInterceptor object - CountDownLatch latch = new CountDownLatch(1); - GrpcWebClientInterceptor interceptor = - new GrpcWebClientInterceptor(resp, latch, sendResponse); - Channel channel = mGrpcServiceConnectionManager.getChannelWithClientInterceptor(interceptor); - - // get the stub for the rpc call and the method to be called within the stub - io.grpc.stub.AbstractStub asyncStub = getRpcStub(channel, cls, "newStub"); - Metadata headers = MetadataUtil.getHtpHeaders(req); - if (!headers.keys().isEmpty()) { - asyncStub = MetadataUtils.attachHeaders(asyncStub, headers); - } - Method asyncStubCall = getRpcMethod(asyncStub, methodName); - // Get the input object bytes - ServletInputStream in = req.getInputStream(); - MessageDeframer deframer = new MessageDeframer(); - Object inObj = null; - if (deframer.processInput(in, contentType)) { - inObj = mMessageHandler.getInputProtobufObj(asyncStubCall, deframer.getMessageBytes()); - } - - // Invoke the rpc call - asyncStubCall.invoke(asyncStub, inObj, - new GrpcCallResponseReceiver(sendResponse, latch)); - if (!latch.await(500, TimeUnit.MILLISECONDS)) { - LOG.warning("grpc call took too long!"); - } - } catch (Exception e) { - LOG.info("Exception occurred: " + e.getMessage()); - resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); - } - } - - private Pair getClassAndMethod(HttpServletRequest req) - throws IllegalArgumentException { - String pathInfo = req.getPathInfo(); - // pathInfo starts with "/". ignore that first char. - String[] rpcClassAndMethodTokens = pathInfo.substring(1).split("/"); - if (rpcClassAndMethodTokens.length != 2) { - throw new IllegalArgumentException("incorrect pathinfo: " + pathInfo); - } - - String rpcClassName = rpcClassAndMethodTokens[0]; - String rpcMethodNameRecvd = rpcClassAndMethodTokens[1]; - String rpcMethodName = rpcMethodNameRecvd.substring(0, 1).toLowerCase() - + rpcMethodNameRecvd.substring(1); - return new ImmutablePair<>(rpcClassName, rpcMethodName); - } - - private Class getClassObject(String className) { - Class rpcClass = null; - try { - rpcClass = Class.forName(className + "Grpc"); - } catch (ClassNotFoundException e) { - LOG.info("no such class " + className); - } - return rpcClass; - } - - private io.grpc.stub.AbstractStub getRpcStub(Channel ch, Class cls, String stubName) { - try { - Method m = cls.getDeclaredMethod(stubName, io.grpc.Channel.class); - return (io.grpc.stub.AbstractStub) m.invoke(null, ch); - } catch (Exception e) { - LOG.warning("Error when fetching " + stubName + " for: " + cls.getName()); - throw new IllegalArgumentException(e); - } - } - - /** - * Find the matching method in the stub class. - */ - private Method getRpcMethod(Object stub, String rpcMethodName) { - for (Method m : stub.getClass().getMethods()) { - if (m.getName().equals(rpcMethodName)) { - return m; - } - } - throw new IllegalArgumentException("Couldn't find rpcmethod: " + rpcMethodName); - } - - private static class GrpcCallResponseReceiver implements StreamObserver { - private final SendResponse sendResponse; - private final CountDownLatch latch; - - GrpcCallResponseReceiver(SendResponse s, CountDownLatch c) { - sendResponse = s; - latch = c; - } - - @Override - public void onNext(java.lang.Object resp) { - // TODO verify that the resp object is of Class instance returnedCls. - byte[] outB = ((com.google.protobuf.GeneratedMessageV3)resp).toByteArray(); - sendResponse.writeResponse(outB); - } - - @Override - public void onError(Throwable t) { - Status s = Status.fromThrowable(t); - sendResponse.writeError(s); - latch.countDown(); - } - - @Override - public void onCompleted() { - sendResponse.writeStatusTrailer(Status.OK); - latch.countDown(); - } - } -} diff --git a/src/connector/src/main/java/io/grpcweb/SendResponse.java b/src/connector/src/main/java/io/grpcweb/SendResponse.java deleted file mode 100644 index d25ab899e..000000000 --- a/src/connector/src/main/java/io/grpcweb/SendResponse.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020 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 - * - * 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. - */ -package io.grpcweb; - -import io.grpc.Metadata; -import io.grpc.Status; -import io.grpcweb.MessageHandler.ContentType; -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.util.Map; -import java.util.logging.Logger; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Base64; - -class SendResponse { - private static final Logger LOG = - Logger.getLogger(MethodHandles.lookup().lookupClass().getName()); - - private final String mContentType; - private final HttpServletResponse mResp; - private boolean isFinalResponseSent = false; - - SendResponse(HttpServletRequest req, HttpServletResponse resp) { - mContentType = req.getContentType(); - mResp = resp; - } - - synchronized void writeHeaders(Metadata headers) { - if (isFinalResponseSent) return; - mResp.setContentType(mContentType); - mResp.setHeader("transfer-encoding", "chunked"); - mResp.setHeader("trailer", "grpc-status,grpc-message"); - if (headers == null) return; - Map ht = MetadataUtil.getHttpHeadersFromMetadata(headers); - StringBuffer sb = new StringBuffer(); - for (String key : ht.keySet()) { - mResp.setHeader(key, ht.get(key)); - } - } - - synchronized void returnUnimplementedStatusCode() { - if (isFinalResponseSent) return; - writeHeaders(null); - writeStatusTrailer(Status.UNIMPLEMENTED); - isFinalResponseSent = true; - } - - synchronized void writeError(Status s) { - if (isFinalResponseSent) return; - writeHeaders(null); - writeStatusTrailer(s); - isFinalResponseSent = true; - } - - synchronized void writeStatusTrailer(Status status) { - writeTrailer(status, null); - } - - synchronized void writeTrailer(Status status, Metadata trailer) { - if (isFinalResponseSent) return; - StringBuffer sb = new StringBuffer(); - if (trailer != null) { - Map ht = MetadataUtil.getHttpHeadersFromMetadata(trailer); - for (String key : ht.keySet()) { - sb.append(String.format("%s:%s\r\n", key, ht.get(key))); - } - } - sb.append(String.format("grpc-status:%d\r\n", status.getCode().value())); - if (status.getDescription() != null && !status.getDescription().isEmpty()) { - sb.append(String.format("grpc-message:%s\r\n", status.getDescription())); - } - LOG.fine("writing trailer: " + sb.toString()); - writeResponse(sb.toString().getBytes(), MessageFramer.Type.TRAILER); - writeOk(); - } - - synchronized void writeResponse(byte[] out) { - writeResponse(out, MessageFramer.Type.DATA); - } - - private void writeResponse(byte[] out, MessageFramer.Type type) { - if (isFinalResponseSent) return; - try { - // PUNT multiple frames not handled - byte[] prefix = new MessageFramer().getPrefix(out, type); - ServletOutputStream oStream = mResp.getOutputStream(); - // binary encode if it is "text" content type - if (MessageHandler.getContentType(mContentType) == ContentType.GRPC_WEB_TEXT) { - byte[] concated = new byte[out.length + 5]; - System.arraycopy(prefix, 0, concated, 0, 5); - System.arraycopy(out, 0, concated, 5, out.length); - oStream.write(Base64.getEncoder().encode(concated)); - } else { - oStream.write(prefix); - oStream.write(out); - } - } catch (IOException e) { - // TODO what to do here? - LOG.warning("can't write?"); - } - } - - private void writeOk() { - mResp.setStatus(HttpServletResponse.SC_OK); - isFinalResponseSent = true; - } -} diff --git a/src/connector/src/test/java/io/grpcweb/MessageDeframerTest.java b/src/connector/src/test/java/io/grpcweb/MessageDeframerTest.java deleted file mode 100644 index ec2e1c3f0..000000000 --- a/src/connector/src/test/java/io/grpcweb/MessageDeframerTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package io.grpcweb; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import io.grpcweb.MessageHandler.ContentType; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Base64; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class MessageDeframerTest { - - private MessageDeframer testInstance; - - @Before - public void setUp() { - testInstance = new MessageDeframer(); - assertNotNull(testInstance); - } - - private void singleFrameTest(boolean encode) throws IOException { - String source = "This is the source of my input stream"; - byte[] str = stringToFrame(source, encode); - InputStream in = new ByteArrayInputStream(str); - assertTrue(testInstance.processInput(in, getContentType(encode))); - byte[] result = testInstance.getMessageBytes(); - assertTrue(source.equals(new String(result))); - assertEquals(source.length(), testInstance.getLength()); - assertEquals(1, testInstance.getNumberOfFrames()); - } - - @Test - public void testProcessInput_Singleframe() throws IOException { - singleFrameTest(false); - } - - @Test - public void testProcessInput_Singleframe_Base64Encoded() throws IOException { - singleFrameTest(true); - } - - private void manyFramesTest(boolean encode) throws IOException { - ArrayList inputSrcs = new ArrayList<>(); - // Create 10 frames - int numFrames = 10; - for (int i = 0; i < numFrames; i++) { - inputSrcs.add("this is string# " + i); - } - ByteArrayOutputStream combined = new ByteArrayOutputStream(); - String concatenatedInputSrc = ""; - // create 10 frames first and then Base64 Encode the whole string. - for (String s : inputSrcs) { - combined.write(stringToFrame(s, false)); - concatenatedInputSrc += s; - } - byte[] combinedBytesOrig = combined.toByteArray(); - byte[] combinedBytes = encode ? Base64.getEncoder().encode(combinedBytesOrig) - : combinedBytesOrig; - if (!encode) { - assertEquals(concatenatedInputSrc.length() + inputSrcs.size() * 5, - combinedBytes.length); - } - InputStream in = new ByteArrayInputStream(combinedBytes); - assertTrue(testInstance.processInput(in, getContentType(encode))); - byte[] result = testInstance.getMessageBytes(); - assertTrue(concatenatedInputSrc.equals(new String(result))); - assertEquals(concatenatedInputSrc.length(), testInstance.getLength()); - assertEquals(10, testInstance.getNumberOfFrames()); - } - - @Test - public void testProcessInput_Manyframes() throws IOException { - manyFramesTest(false); - } - - @Test - public void testProcessInput_Manyframes_Base64Encoded() throws IOException { - manyFramesTest(true); - } - - public void emptyFrameTest(boolean encode) throws IOException { - // Empty frame is a valid frame. - byte[] str = stringToFrame("", false); - InputStream in = new ByteArrayInputStream(str); - assertTrue(testInstance.processInput(in, getContentType(encode))); - assertEquals(0, testInstance.getMessageBytes().length); - assertEquals(1, testInstance.getNumberOfFrames()); - } - - @Test - public void testProcessInput_EmptyDataframeInInput() throws IOException { - emptyFrameTest(false); - } - - public void noDataTest(boolean encode) throws IOException { - // Input has no data frames at all - InputStream in = new ByteArrayInputStream(new byte[0]); - assertFalse(testInstance.processInput(in, getContentType(encode))); - } - - @Test - public void testProcessInput_NoDataframesInInput() throws IOException { - noDataTest(false); - } - - @Test - public void testProcessInput_NoDataframesInInput_Base64Encoded() throws IOException { - noDataTest(true); - } - - private byte[] stringToFrame(String source, boolean encode) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - output.write(MessageDeframer.DATA_BYTE); - output.write(ByteBuffer.allocate(4).putInt(source.length()).array()); - output.write(source.getBytes()); - byte[] outB = output.toByteArray(); - return (encode) ? Base64.getEncoder().encode(outB) : outB; - } - - private MessageHandler.ContentType getContentType(boolean encode) { - return encode ? ContentType.GRPC_WEB_TEXT : ContentType.GRPC_WEB_BINARY; - } -} diff --git a/src/connector/src/test/java/io/grpcweb/MessageFramerTest.java b/src/connector/src/test/java/io/grpcweb/MessageFramerTest.java deleted file mode 100644 index 1dabb2fbd..000000000 --- a/src/connector/src/test/java/io/grpcweb/MessageFramerTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.grpcweb; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.io.IOException; -import java.nio.ByteBuffer; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class MessageFramerTest { - - private MessageFramer testInstance; - - @Before - public void setUp() { - testInstance = new MessageFramer(); - assertNotNull(testInstance); - } - - @Test - public void testProcessInput_Singleframe() throws IOException { - String source = "This is the source of my input stream"; - byte[] bytes = source.getBytes(); - byte[] prefix = testInstance.getPrefix(bytes, MessageFramer.Type.DATA); - assertEquals(5, prefix.length); - int len = ByteBuffer.wrap(prefix, 1, 4).getInt(); - assertEquals(source.length(), len); - } - // PUNT add more tests: Empty frame, zero frames -} diff --git a/test/interop/package.json b/test/interop/package.json index 318f30931..0738d5116 100644 --- a/test/interop/package.json +++ b/test/interop/package.json @@ -13,8 +13,8 @@ "devDependencies": { "minimist": "~1.2.5", "mocha": "~7.1.1", - "webpack": "~4.43.0", - "webpack-cli": "~3.3.11", + "webpack": "~5.82.1", + "webpack-cli": "~5.1.1", "xhr2": "~0.2.0" } }