Skip to content

Commit

Permalink
servlet: Implement gRPC server as a Servlet (#8596)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
3 people authored Jan 20, 2023
1 parent 44847bf commit 706646f
Show file tree
Hide file tree
Showing 29 changed files with 3,387 additions and 5 deletions.
1 change: 1 addition & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ $ VERSION_FILES=(
examples/example-jwt-auth/pom.xml
examples/example-hostname/build.gradle
examples/example-hostname/pom.xml
examples/example-servlet/build.gradle
examples/example-tls/build.gradle
examples/example-tls/pom.xml
examples/example-xds/build.gradle
Expand Down
2 changes: 2 additions & 0 deletions all/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def subprojects = [
project(':grpc-protobuf-lite'),
project(':grpc-rls'),
project(':grpc-services'),
project(':grpc-servlet'),
project(':grpc-servlet-jakarta'),
project(':grpc-stub'),
project(':grpc-testing'),
project(':grpc-xds'),
Expand Down
37 changes: 37 additions & 0 deletions examples/example-servlet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Hello World Example using Servlets

This example uses Java Servlets instead of Netty for the gRPC server. This example requires `grpc-java`
and `protoc-gen-grpc-java` to already be built. You are strongly encouraged to check out a git release
tag, since these builds will already be available.

```bash
git checkout v<major>.<minor>.<patch>
```
Otherwise, you must follow [COMPILING](../../COMPILING.md).

To build the example,

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).**

2. In this directory, build the war file
```bash
$ ../gradlew war
```

To run this, deploy the war, now found in `build/libs/example-servlet.war` to your choice of servlet
container. Note that this container must support the Servlet 4.0 spec, for this particular example must
use `javax.servlet` packages instead of the more modern `jakarta.servlet`, though there is a `grpc-servlet-jakarta`
artifact that can be used for Jakarta support. Be sure to enable http/2 support in the servlet container,
or clients will not be able to connect.

To test that this is working properly, build the HelloWorldClient example and direct it to connect to your
http/2 server. From the parent directory:

1. Build the executables:
```bash
$ ../gradlew installDist
```
2. Run the client app, specifying the name to say hello to and the server's address:
```bash
$ ./build/install/examples/bin/hello-world-client World localhost:8080
```
46 changes: 46 additions & 0 deletions examples/example-servlet/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
plugins {
// ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions
id 'com.google.protobuf' version '0.8.17'
// Generate IntelliJ IDEA's .idea & .iml project files
id 'idea'
id 'war'
}

repositories {
maven { // The google mirror is less flaky than mavenCentral()
url "https://maven-central.storage-download.googleapis.com/maven2/" }
mavenLocal()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

def grpcVersion = '1.53.0-SNAPSHOT' // CURRENT_GRPC_VERSION
def protocVersion = '3.21.7'

dependencies {
implementation "io.grpc:grpc-protobuf:${grpcVersion}",
"io.grpc:grpc-servlet:${grpcVersion}",
"io.grpc:grpc-stub:${grpcVersion}"

providedImplementation "javax.servlet:javax.servlet-api:4.0.1",
"org.apache.tomcat:annotations-api:6.0.53"
}

protobuf {
protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } }
generateProtoTasks {
all()*.plugins { grpc {} }
}
}

// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code.
sourceSets {
main {
java {
srcDirs 'build/generated/source/proto/main/grpc'
srcDirs 'build/generated/source/proto/main/java'
}
}
}
8 changes: 8 additions & 0 deletions examples/example-servlet/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pluginManagement {
repositories {
maven { // The google mirror is less flaky than mavenCentral()
url "https://maven-central.storage-download.googleapis.com/maven2/"
}
gradlePluginPortal()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2018 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 io.grpc.servlet.examples.helloworld;

import io.grpc.stub.StreamObserver;
import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.servlet.ServletAdapter;
import io.grpc.servlet.ServletServerBuilder;
import java.io.IOException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* A servlet that hosts a gRPC server over HTTP/2 and shares the resource URI for the normal servlet
* clients over HTTP/1.0+.
*
* <p>For creating a servlet that solely serves gRPC services, do not follow this example, simply
* extend or register a {@link io.grpc.servlet.GrpcServlet} instead.
*/
@WebServlet(urlPatterns = {"/helloworld.Greeter/SayHello"}, asyncSupported = true)
public class HelloWorldServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

private final ServletAdapter servletAdapter =
new ServletServerBuilder().addService(new GreeterImpl()).buildServletAdapter();

private static final class GreeterImpl extends GreeterGrpc.GreeterImplBase {
GreeterImpl() {}

@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/html");
response.getWriter().println("<p>Hello World!</p>");
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
if (ServletAdapter.isGrpc(request)) {
servletAdapter.doPost(request, response);
} else {
response.setContentType("text/html");
response.getWriter().println("<p>Hello non-gRPC client!</p>");
}
}

@Override
public void destroy() {
servletAdapter.destroy();
super.destroy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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.
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// 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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Need this file for deployment to GlassFish -->
<!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">
<glassfish-web-app error-url="">
<class-loader delegate="false"/>
</glassfish-web-app>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Need this file for deployment to WildFly -->
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.jboss.com/xml/ns/javaee
http://www.jboss.org/j2ee/schema/jboss-web_5_1.xsd">
<context-root>/</context-root>
</jboss-web>
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,8 @@ public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata

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

private void startServer() {
maybeStartHandshakerServer();
ServerBuilder<?> builder = getServerBuilder();
private void configBuilder(@Nullable ServerBuilder<?> builder) {
if (builder == null) {
server = null;
return;
}
testServiceExecutor = Executors.newScheduledThreadPool(2);
Expand All @@ -266,6 +263,14 @@ private void startServer() {
new TestServiceImpl(testServiceExecutor),
allInterceptors))
.addStreamTracerFactory(serverStreamTracerFactory);
}

protected void startServer(@Nullable ServerBuilder<?> builder) {
maybeStartHandshakerServer();
if (builder == null) {
server = null;
return;
}

try {
server = builder.build().start();
Expand Down Expand Up @@ -333,7 +338,9 @@ public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
*/
@Before
public void setUp() {
startServer();
ServerBuilder<?> serverBuilder = getServerBuilder();
configBuilder(serverBuilder);
startServer(serverBuilder);
channel = createChannel();

blockingStub =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package io.grpc.netty;

import com.google.common.annotations.VisibleForTesting;
import io.grpc.Internal;
import io.grpc.internal.ClientTransportFactory;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourcePool;
import io.grpc.internal.TransportTracer;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
Expand Down Expand Up @@ -107,5 +109,11 @@ public static ClientTransportFactory buildTransportFactory(NettyChannelBuilder b
return builder.buildTransportFactory();
}

@VisibleForTesting
public static void setTransportTracerFactory(
NettyChannelBuilder builder, TransportTracer.Factory factory) {
builder.setTransportTracerFactory(factory);
}

private InternalNettyChannelBuilder() {}
}
Loading

0 comments on commit 706646f

Please sign in to comment.