Skip to content

Commit 706646f

Browse files
niloc132dapengzhang0voidzcy
authored
servlet: Implement gRPC server as a Servlet (#8596)
Full end to end implementation of gRPC server as a Servlet including tests and examples Co-authored-by: Penn (Dapeng) Zhang <[email protected]> Co-authored-by: Chengyuan Zhang <[email protected]>
1 parent 44847bf commit 706646f

29 files changed

+3387
-5
lines changed

RELEASING.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ $ VERSION_FILES=(
3333
examples/example-jwt-auth/pom.xml
3434
examples/example-hostname/build.gradle
3535
examples/example-hostname/pom.xml
36+
examples/example-servlet/build.gradle
3637
examples/example-tls/build.gradle
3738
examples/example-tls/pom.xml
3839
examples/example-xds/build.gradle

all/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ def subprojects = [
1919
project(':grpc-protobuf-lite'),
2020
project(':grpc-rls'),
2121
project(':grpc-services'),
22+
project(':grpc-servlet'),
23+
project(':grpc-servlet-jakarta'),
2224
project(':grpc-stub'),
2325
project(':grpc-testing'),
2426
project(':grpc-xds'),

examples/example-servlet/README.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Hello World Example using Servlets
2+
3+
This example uses Java Servlets instead of Netty for the gRPC server. This example requires `grpc-java`
4+
and `protoc-gen-grpc-java` to already be built. You are strongly encouraged to check out a git release
5+
tag, since these builds will already be available.
6+
7+
```bash
8+
git checkout v<major>.<minor>.<patch>
9+
```
10+
Otherwise, you must follow [COMPILING](../../COMPILING.md).
11+
12+
To build the example,
13+
14+
1. **[Install gRPC Java library SNAPSHOT locally, including code generation plugin](../../COMPILING.md) (Only need this step for non-released versions, e.g. master HEAD).**
15+
16+
2. In this directory, build the war file
17+
```bash
18+
$ ../gradlew war
19+
```
20+
21+
To run this, deploy the war, now found in `build/libs/example-servlet.war` to your choice of servlet
22+
container. Note that this container must support the Servlet 4.0 spec, for this particular example must
23+
use `javax.servlet` packages instead of the more modern `jakarta.servlet`, though there is a `grpc-servlet-jakarta`
24+
artifact that can be used for Jakarta support. Be sure to enable http/2 support in the servlet container,
25+
or clients will not be able to connect.
26+
27+
To test that this is working properly, build the HelloWorldClient example and direct it to connect to your
28+
http/2 server. From the parent directory:
29+
30+
1. Build the executables:
31+
```bash
32+
$ ../gradlew installDist
33+
```
34+
2. Run the client app, specifying the name to say hello to and the server's address:
35+
```bash
36+
$ ./build/install/examples/bin/hello-world-client World localhost:8080
37+
```

examples/example-servlet/build.gradle

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
plugins {
2+
// ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions
3+
id 'com.google.protobuf' version '0.8.17'
4+
// Generate IntelliJ IDEA's .idea & .iml project files
5+
id 'idea'
6+
id 'war'
7+
}
8+
9+
repositories {
10+
maven { // The google mirror is less flaky than mavenCentral()
11+
url "https://maven-central.storage-download.googleapis.com/maven2/" }
12+
mavenLocal()
13+
}
14+
15+
sourceCompatibility = 1.8
16+
targetCompatibility = 1.8
17+
18+
def grpcVersion = '1.53.0-SNAPSHOT' // CURRENT_GRPC_VERSION
19+
def protocVersion = '3.21.7'
20+
21+
dependencies {
22+
implementation "io.grpc:grpc-protobuf:${grpcVersion}",
23+
"io.grpc:grpc-servlet:${grpcVersion}",
24+
"io.grpc:grpc-stub:${grpcVersion}"
25+
26+
providedImplementation "javax.servlet:javax.servlet-api:4.0.1",
27+
"org.apache.tomcat:annotations-api:6.0.53"
28+
}
29+
30+
protobuf {
31+
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
32+
plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } }
33+
generateProtoTasks {
34+
all()*.plugins { grpc {} }
35+
}
36+
}
37+
38+
// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code.
39+
sourceSets {
40+
main {
41+
java {
42+
srcDirs 'build/generated/source/proto/main/grpc'
43+
srcDirs 'build/generated/source/proto/main/java'
44+
}
45+
}
46+
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pluginManagement {
2+
repositories {
3+
maven { // The google mirror is less flaky than mavenCentral()
4+
url "https://maven-central.storage-download.googleapis.com/maven2/"
5+
}
6+
gradlePluginPortal()
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2018 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.servlet.examples.helloworld;
18+
19+
import io.grpc.stub.StreamObserver;
20+
import io.grpc.examples.helloworld.GreeterGrpc;
21+
import io.grpc.examples.helloworld.HelloReply;
22+
import io.grpc.examples.helloworld.HelloRequest;
23+
import io.grpc.servlet.ServletAdapter;
24+
import io.grpc.servlet.ServletServerBuilder;
25+
import java.io.IOException;
26+
import javax.servlet.annotation.WebServlet;
27+
import javax.servlet.http.HttpServlet;
28+
import javax.servlet.http.HttpServletRequest;
29+
import javax.servlet.http.HttpServletResponse;
30+
31+
/**
32+
* A servlet that hosts a gRPC server over HTTP/2 and shares the resource URI for the normal servlet
33+
* clients over HTTP/1.0+.
34+
*
35+
* <p>For creating a servlet that solely serves gRPC services, do not follow this example, simply
36+
* extend or register a {@link io.grpc.servlet.GrpcServlet} instead.
37+
*/
38+
@WebServlet(urlPatterns = {"/helloworld.Greeter/SayHello"}, asyncSupported = true)
39+
public class HelloWorldServlet extends HttpServlet {
40+
private static final long serialVersionUID = 1L;
41+
42+
private final ServletAdapter servletAdapter =
43+
new ServletServerBuilder().addService(new GreeterImpl()).buildServletAdapter();
44+
45+
private static final class GreeterImpl extends GreeterGrpc.GreeterImplBase {
46+
GreeterImpl() {}
47+
48+
@Override
49+
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
50+
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
51+
responseObserver.onNext(reply);
52+
responseObserver.onCompleted();
53+
}
54+
}
55+
56+
@Override
57+
protected void doGet(HttpServletRequest request, HttpServletResponse response)
58+
throws IOException {
59+
response.setContentType("text/html");
60+
response.getWriter().println("<p>Hello World!</p>");
61+
}
62+
63+
@Override
64+
protected void doPost(HttpServletRequest request, HttpServletResponse response)
65+
throws IOException {
66+
if (ServletAdapter.isGrpc(request)) {
67+
servletAdapter.doPost(request, response);
68+
} else {
69+
response.setContentType("text/html");
70+
response.getWriter().println("<p>Hello non-gRPC client!</p>");
71+
}
72+
}
73+
74+
@Override
75+
public void destroy() {
76+
servletAdapter.destroy();
77+
super.destroy();
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2015 The gRPC Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
syntax = "proto3";
15+
16+
option java_multiple_files = true;
17+
option java_package = "io.grpc.examples.helloworld";
18+
option java_outer_classname = "HelloWorldProto";
19+
option objc_class_prefix = "HLW";
20+
21+
package helloworld;
22+
23+
// The greeting service definition.
24+
service Greeter {
25+
// Sends a greeting
26+
rpc SayHello (HelloRequest) returns (HelloReply) {}
27+
}
28+
29+
// The request message containing the user's name.
30+
message HelloRequest {
31+
string name = 1;
32+
}
33+
34+
// The response message containing the greetings
35+
message HelloReply {
36+
string message = 1;
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- Need this file for deployment to GlassFish -->
3+
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
4+
<glassfish-web-app error-url="">
5+
<class-loader delegate="false"/>
6+
</glassfish-web-app>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- Need this file for deployment to WildFly -->
3+
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="
6+
http://www.jboss.com/xml/ns/javaee
7+
http://www.jboss.org/j2ee/schema/jboss-web_5_1.xsd">
8+
<context-root>/</context-root>
9+
</jboss-web>

interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,8 @@ public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata
244244

245245
protected static final Empty EMPTY = Empty.getDefaultInstance();
246246

247-
private void startServer() {
248-
maybeStartHandshakerServer();
249-
ServerBuilder<?> builder = getServerBuilder();
247+
private void configBuilder(@Nullable ServerBuilder<?> builder) {
250248
if (builder == null) {
251-
server = null;
252249
return;
253250
}
254251
testServiceExecutor = Executors.newScheduledThreadPool(2);
@@ -266,6 +263,14 @@ private void startServer() {
266263
new TestServiceImpl(testServiceExecutor),
267264
allInterceptors))
268265
.addStreamTracerFactory(serverStreamTracerFactory);
266+
}
267+
268+
protected void startServer(@Nullable ServerBuilder<?> builder) {
269+
maybeStartHandshakerServer();
270+
if (builder == null) {
271+
server = null;
272+
return;
273+
}
269274

270275
try {
271276
server = builder.build().start();
@@ -333,7 +338,9 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
333338
*/
334339
@Before
335340
public void setUp() {
336-
startServer();
341+
ServerBuilder<?> serverBuilder = getServerBuilder();
342+
configBuilder(serverBuilder);
343+
startServer(serverBuilder);
337344
channel = createChannel();
338345

339346
blockingStub =

netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java

+8
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package io.grpc.netty;
1818

19+
import com.google.common.annotations.VisibleForTesting;
1920
import io.grpc.Internal;
2021
import io.grpc.internal.ClientTransportFactory;
2122
import io.grpc.internal.GrpcUtil;
2223
import io.grpc.internal.SharedResourcePool;
24+
import io.grpc.internal.TransportTracer;
2325
import io.netty.channel.socket.nio.NioSocketChannel;
2426

2527
/**
@@ -107,5 +109,11 @@ public static ClientTransportFactory buildTransportFactory(NettyChannelBuilder b
107109
return builder.buildTransportFactory();
108110
}
109111

112+
@VisibleForTesting
113+
public static void setTransportTracerFactory(
114+
NettyChannelBuilder builder, TransportTracer.Factory factory) {
115+
builder.setTransportTracerFactory(factory);
116+
}
117+
110118
private InternalNettyChannelBuilder() {}
111119
}

0 commit comments

Comments
 (0)