diff --git a/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc b/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc
index 1994cbcb2f..7f5140971e 100644
--- a/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc
+++ b/servicetalk-examples/docs/modules/ROOT/pages/_partials/nav-versioned.adoc
@@ -20,6 +20,7 @@
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#HelloWorld[Hello World]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Compression[Compression]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Deadlines[Deadlines]
+** xref:{page-version}@servicetalk-examples::grpc/index.adoc#KeepAlive[Keep Alive]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Observer[Observer]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#Health[Health Checking]
** xref:{page-version}@servicetalk-examples::grpc/index.adoc#errors[Application Errors]
diff --git a/servicetalk-examples/docs/modules/ROOT/pages/grpc/index.adoc b/servicetalk-examples/docs/modules/ROOT/pages/grpc/index.adoc
index 961770886c..6e4646b597 100644
--- a/servicetalk-examples/docs/modules/ROOT/pages/grpc/index.adoc
+++ b/servicetalk-examples/docs/modules/ROOT/pages/grpc/index.adoc
@@ -96,6 +96,21 @@ https://grpc.io/docs/what-is-grpc/core-concepts/#deadlines[gRPC deadlines] (aka
– Sends hello requests to the server with 1 minute deadline and 3 second deadline and receives a greeting response
within that time or cancels the request.
+[#KeepAlive]
+== Keep Alive
+
+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.
+
[#Debugging]
== Debugging
diff --git a/servicetalk-examples/grpc/keepalive/README.adoc b/servicetalk-examples/grpc/keepalive/README.adoc
new file mode 100644
index 0000000000..65ca5d88ec
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/README.adoc
@@ -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.
\ No newline at end of file
diff --git a/servicetalk-examples/grpc/keepalive/build.gradle b/servicetalk-examples/grpc/keepalive/build.gradle
new file mode 100644
index 0000000000..23e050aac0
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/build.gradle
@@ -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"
+}
diff --git a/servicetalk-examples/grpc/keepalive/pom.xml b/servicetalk-examples/grpc/keepalive/pom.xml
new file mode 100644
index 0000000000..9406c0df65
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/pom.xml
@@ -0,0 +1,97 @@
+
+ 4.0.0
+ io.servicetalk.examples.grpc.helloworld
+ helloworld-maven
+ jar
+ 1.0-SNAPSHOT
+ st-examples
+ https://servicetalk.io
+
+
+
+ 0.42.0
+ 0.6.1
+ 3.19.2
+ 1.6.0
+ 3.8.1
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${os-maven-plugin.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ 1.8
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-maven-plugin.version}
+ true
+
+
+
+ compile
+
+
+ com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
+
+
+ servicetalk-grpc-protoc
+ io.servicetalk
+ servicetalk-grpc-protoc
+ ${servicetalk.version}
+ io.servicetalk.grpc.protoc.Main
+
+
+
+
+
+
+
+
+
+
+
+
+ io.servicetalk
+ servicetalk-bom
+ ${servicetalk.version}
+ pom
+ import
+
+
+
+
+
+
+ io.servicetalk
+ servicetalk-annotations
+
+
+ io.servicetalk
+ servicetalk-grpc-netty
+
+
+ io.servicetalk
+ servicetalk-grpc-protoc
+
+
+ io.servicetalk
+ servicetalk-grpc-protobuf
+
+
+
diff --git a/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveClient.java b/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveClient.java
new file mode 100644
index 0000000000..44502bb3cb
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveClient.java
@@ -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 The type of items to iterate.
+ */
+ private static final class StdInIterable implements Iterable {
+ private final Supplier itemSupplier;
+
+ private StdInIterable(final Supplier itemSupplier) {
+ this.itemSupplier = itemSupplier;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ @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();
+ }
+ };
+ }
+ }
+}
diff --git a/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveServer.java b/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveServer.java
new file mode 100644
index 0000000000..a11b31edd3
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/KeepAliveServer.java
@@ -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();
+ }
+}
diff --git a/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/package-info.java b/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/package-info.java
new file mode 100644
index 0000000000..4f1b1f8573
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/src/main/java/io/servicetalk/examples/grpc/keepalive/package-info.java
@@ -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;
diff --git a/servicetalk-examples/grpc/keepalive/src/main/proto/helloworld.proto b/servicetalk-examples/grpc/keepalive/src/main/proto/helloworld.proto
new file mode 100644
index 0000000000..b5719e50bd
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/src/main/proto/helloworld.proto
@@ -0,0 +1,32 @@
+// 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.keepalive";
+option java_outer_classname = "KeepAliveProto";
+
+package keepalive;
+
+service StreamingGreeter {
+ rpc StreamHello (stream HelloRequest) returns (HelloReply) {}
+}
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloReply {
+ string message = 1;
+}
diff --git a/servicetalk-examples/grpc/keepalive/src/main/resources/log4j2.xml b/servicetalk-examples/grpc/keepalive/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..f2b28781b3
--- /dev/null
+++ b/servicetalk-examples/grpc/keepalive/src/main/resources/log4j2.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index c142ddc673..1952486853 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -50,6 +50,7 @@ include "servicetalk-annotations",
"servicetalk-examples:grpc:execution-strategy",
"servicetalk-examples:grpc:observer",
"servicetalk-examples:grpc:health",
+ "servicetalk-examples:grpc:keepalive",
"servicetalk-examples:http:helloworld",
"servicetalk-examples:http:mutual-tls",
"servicetalk-examples:http:redirects",
@@ -117,6 +118,7 @@ project(":servicetalk-examples:grpc:errors").name = "servicetalk-examples-grpc-e
project(":servicetalk-examples:grpc:execution-strategy").name = "servicetalk-examples-grpc-execution-strategy"
project(":servicetalk-examples:grpc:observer").name = "servicetalk-examples-grpc-observer"
project(":servicetalk-examples:grpc:health").name = "servicetalk-examples-grpc-health"
+project(":servicetalk-examples:grpc:keepalive").name = "servicetalk-examples-grpc-keepalive"
project(":servicetalk-examples:http:http2").name = "servicetalk-examples-http-http2"
project(":servicetalk-examples:http:helloworld").name = "servicetalk-examples-http-helloworld"
project(":servicetalk-examples:http:debugging").name = "servicetalk-examples-http-debugging"