-
Notifications
You must be signed in to change notification settings - Fork 181
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example for h2 keep alive (#2203)
Motivation: Add an example demonstrating how the HTTP/2 transport keep alive feature can be used for gRPC clients and servers.
- Loading branch information
1 parent
9aec0e0
commit 7cd1c78
Showing
11 changed files
with
439 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
== ServiceTalk gRPC KeepAlive Example | ||
|
||
Demonstrates how to use HTTP/2 keep alive for gRPC | ||
link:{source-root}/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveServer.java[server] | ||
and | ||
link:{source-root}/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveClient.java[client]. | ||
Keep alive uses transport control frames to ensure the peer is still able to read and write to open connections. If the | ||
peer is not able to respond to the control frame within the configured amount of time, the connection is closed. This | ||
is useful if your environment doesn't provide other forms of connection keep alive (e.g. | ||
link:https://docs.oracle.com/javase/8/docs/api/java/net/StandardSocketOptions.html#SO_KEEPALIVE[SO_KEEPALIVE], and maybe | ||
preferred to lower level keep alive because it is closer the application logic (more likely if this check works, that | ||
your application is able to read/write). Keep alive can be helpful to detect scenarios such as non-graceful disconnects | ||
(e.g. power outage, ethernet cable pulled, buggy middle box) and general network disconnects. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright © 2019 Apple Inc. and the ServiceTalk project 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. | ||
*/ | ||
|
||
buildscript { | ||
dependencies { | ||
classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufGradlePluginVersion" | ||
} | ||
} | ||
|
||
apply plugin: "java" | ||
apply plugin: "com.google.protobuf" | ||
apply from: "../../gradle/idea.gradle" | ||
|
||
dependencies { | ||
implementation project(":servicetalk-annotations") | ||
implementation project(":servicetalk-grpc-netty") | ||
implementation project(":servicetalk-grpc-protoc") | ||
implementation project(":servicetalk-grpc-protobuf") | ||
|
||
implementation "org.slf4j:slf4j-api:$slf4jVersion" | ||
runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" | ||
} | ||
|
||
protobuf { | ||
protoc { | ||
artifact = "com.google.protobuf:protoc:$protobufVersion" | ||
} | ||
|
||
//// REMOVE if outside of ServiceTalk gradle project | ||
def pluginJar = file("${project.rootProject.rootDir}/servicetalk-grpc-protoc/build" + | ||
"/buildExecutable/servicetalk-grpc-protoc-${project.version}-all.jar") | ||
//// REMOVE if outside of ServiceTalk gradle project | ||
|
||
plugins { | ||
servicetalk_grpc { | ||
//// REMOVE if outside of ServiceTalk gradle project - use "artifact" as demonstrated below | ||
//// "path" is used only because we want to use the gradle project local version of the plugin. | ||
path = pluginJar.path | ||
//// REMOVE if outside of ServiceTalk gradle project - use "artifact" as demonstrated below | ||
|
||
// artifact = "io.servicetalk:servicetalk-grpc-protoc:$serviceTalkVersion:all@jar" | ||
} | ||
} | ||
generateProtoTasks { | ||
all().each { task -> | ||
//// REMOVE if outside of ServiceTalk gradle project | ||
task.dependsOn(":servicetalk-grpc-protoc:buildExecutable") // use gradle project local grpc-protoc dependency | ||
|
||
// you may need to manually add the artifact name as an input | ||
task.inputs | ||
.file(pluginJar) | ||
.withNormalizer(ClasspathNormalizer) | ||
.withPropertyName("servicetalkPluginJar") | ||
.withPathSensitivity(PathSensitivity.RELATIVE) | ||
//// REMOVE if outside of ServiceTalk gradle project | ||
|
||
task.plugins { | ||
servicetalk_grpc { | ||
// Need to tell protobuf-gradle-plugin to output in the correct directory if all generated | ||
// code for a single proto goes to a single file (e.g. "java_multiple_files = false" in the .proto). | ||
outputSubDir = "java" | ||
} | ||
} | ||
} | ||
} | ||
generatedFilesBaseDir = "$buildDir/generated/sources/proto" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>io.servicetalk.examples.grpc.helloworld</groupId> | ||
<artifactId>helloworld-maven</artifactId> | ||
<packaging>jar</packaging> | ||
<version>1.0-SNAPSHOT</version> | ||
<name>st-examples</name> | ||
<url>https://servicetalk.io</url> | ||
|
||
<properties> | ||
<!-- servicetalk.version is updated automatically by release.sh during the ServiceTalk release process --> | ||
<servicetalk.version>0.42.0</servicetalk.version> | ||
<protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version> | ||
<protoc.version>3.19.2</protoc.version> | ||
<os-maven-plugin.version>1.6.0</os-maven-plugin.version> | ||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version> | ||
</properties> | ||
|
||
<build> | ||
<extensions> | ||
<extension> | ||
<groupId>kr.motd.maven</groupId> | ||
<artifactId>os-maven-plugin</artifactId> | ||
<version>${os-maven-plugin.version}</version> | ||
</extension> | ||
</extensions> | ||
|
||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>${maven-compiler-plugin.version}</version> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.xolstice.maven.plugins</groupId> | ||
<artifactId>protobuf-maven-plugin</artifactId> | ||
<version>${protobuf-maven-plugin.version}</version> | ||
<extensions>true</extensions> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>compile</goal> | ||
</goals> | ||
<configuration> | ||
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact> | ||
<protocPlugins> | ||
<protocPlugin> | ||
<id>servicetalk-grpc-protoc</id> | ||
<groupId>io.servicetalk</groupId> | ||
<artifactId>servicetalk-grpc-protoc</artifactId> | ||
<version>${servicetalk.version}</version> | ||
<mainClass>io.servicetalk.grpc.protoc.Main</mainClass> | ||
</protocPlugin> | ||
</protocPlugins> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.servicetalk</groupId> | ||
<artifactId>servicetalk-bom</artifactId> | ||
<version>${servicetalk.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.servicetalk</groupId> | ||
<artifactId>servicetalk-annotations</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.servicetalk</groupId> | ||
<artifactId>servicetalk-grpc-netty</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.servicetalk</groupId> | ||
<artifactId>servicetalk-grpc-protoc</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.servicetalk</groupId> | ||
<artifactId>servicetalk-grpc-protobuf</artifactId> | ||
</dependency> | ||
</dependencies> | ||
</project> |
91 changes: 91 additions & 0 deletions
91
.../grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.grpc.keepalive; | ||
|
||
import io.servicetalk.grpc.netty.GrpcClients; | ||
|
||
import io.grpc.examples.keepalive.HelloReply; | ||
import io.grpc.examples.keepalive.HelloRequest; | ||
import io.grpc.examples.keepalive.StreamingGreeter.BlockingStreamingGreeterClient; | ||
import io.grpc.examples.keepalive.StreamingGreeter.ClientFactory; | ||
|
||
import java.io.IOException; | ||
import java.util.Iterator; | ||
import java.util.function.Supplier; | ||
|
||
import static io.servicetalk.http.netty.H2KeepAlivePolicies.whenIdleFor; | ||
import static io.servicetalk.http.netty.HttpProtocolConfigs.h2; | ||
import static io.servicetalk.logging.api.LogLevel.TRACE; | ||
import static java.time.Duration.ofSeconds; | ||
|
||
/** | ||
* Example that demonstrates how to enable HTTP/2 keep alive for a gRPC client. | ||
*/ | ||
public final class KeepAliveClient { | ||
public static void main(String... args) throws Exception { | ||
try (BlockingStreamingGreeterClient client = GrpcClients.forAddress("localhost", 8080) | ||
.initializeHttp(httpBuilder -> httpBuilder.protocols( | ||
// 4 second timeout is typically much shorter than necessary, but demonstrates PING frame traffic. | ||
// Using the default value is suitable in most scenarios, but if you want to customize the value | ||
// consider how many resources (network traffic, CPU for local timer management) vs time to detect | ||
// bad connection. | ||
// The keep alive is only sent when no traffic is detected, so if both peers have keep alive the | ||
// faster interval will be the primary sender. | ||
h2().keepAlivePolicy(whenIdleFor(ofSeconds(4))) | ||
// Enable frame logging so we can see the PING frames sent/received. | ||
.enableFrameLogging("servicetalk-examples-h2-frame-logger", TRACE, () -> true) | ||
.build())) | ||
.buildBlocking(new ClientFactory())) { | ||
HelloReply reply = client.streamHello(new StdInIterable<>(() -> | ||
HelloRequest.newBuilder().setName("World").build())); | ||
System.out.println("Got reply: " + reply); | ||
} | ||
} | ||
|
||
/** | ||
* Infinite input stream, each item is sent when data is read from std in. | ||
* @param <T> The type of items to iterate. | ||
*/ | ||
private static final class StdInIterable<T> implements Iterable<T> { | ||
private final Supplier<T> itemSupplier; | ||
|
||
private StdInIterable(final Supplier<T> itemSupplier) { | ||
this.itemSupplier = itemSupplier; | ||
} | ||
|
||
@Override | ||
public Iterator<T> iterator() { | ||
return new Iterator<T>() { | ||
@Override | ||
public boolean hasNext() { | ||
System.out.println("Press any key to send next item..."); | ||
try { | ||
@SuppressWarnings("unused") | ||
int r = System.in.read(); | ||
} catch (IOException e) { | ||
throw new RuntimeException("Unexpected exception waiting for input", e); | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public T next() { | ||
return itemSupplier.get(); | ||
} | ||
}; | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
.../grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveServer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.grpc.keepalive; | ||
|
||
import io.servicetalk.concurrent.api.Single; | ||
import io.servicetalk.grpc.netty.GrpcServers; | ||
|
||
import io.grpc.examples.keepalive.StreamingGreeter.StreamingGreeterService; | ||
|
||
import static io.servicetalk.http.netty.H2KeepAlivePolicies.whenIdleFor; | ||
import static io.servicetalk.http.netty.HttpProtocolConfigs.h2; | ||
import static io.servicetalk.logging.api.LogLevel.TRACE; | ||
import static java.time.Duration.ofSeconds; | ||
|
||
/** | ||
* Example that demonstrates how to enable HTTP/2 keep alive for a gRPC server. | ||
*/ | ||
public final class KeepAliveServer { | ||
public static void main(String... args) throws Exception { | ||
GrpcServers.forPort(8080) | ||
.initializeHttp(httpBuilder -> httpBuilder.protocols( | ||
// 6 second timeout is typically much shorter than necessary, but demonstrates PING frame traffic. | ||
// Using the default value is suitable in most scenarios, but if you want to customize the value | ||
// consider how many resources (network traffic, CPU for local timer management) vs time to detect | ||
// bad connection. | ||
// The keep alive is only sent when no traffic is detected, so if both peers have keep alive the | ||
// faster interval will be the primary sender. | ||
h2().keepAlivePolicy(whenIdleFor(ofSeconds(6))) | ||
// Enable frame logging so we can see the PING frames sent/received. | ||
.enableFrameLogging("servicetalk-examples-h2-frame-logger", TRACE, () -> true) | ||
.build())) | ||
.listenAndAwait((StreamingGreeterService) (ctx, request) -> | ||
request.whenOnNext(item -> System.out.println("Got request: " + item)).ignoreElements() | ||
// Never return a response so we can see keep alive in action. | ||
.concat(Single.never())) | ||
.awaitShutdown(); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
...les/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* | ||
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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. | ||
*/ | ||
@ElementsAreNonnullByDefault | ||
package io.servicetalk.examples.grpc.keepalive; | ||
|
||
import io.servicetalk.annotations.ElementsAreNonnullByDefault; |
Oops, something went wrong.