Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a handler for checking connection status using Ping frame in HTTP/2 #3612

Open
wants to merge 11 commits into
base: 1.2.x
Choose a base branch
from
4 changes: 4 additions & 0 deletions docs/modules/ROOT/pages/http-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ include::{examples-dir}/http2/H2Application.java[lines=18..42]
----
<1> Configures the client to support only `HTTP/2`
<2> Configures `SSL`
<3> Sets the interval for sending `HTTP/2` `PING` frames and receiving `ACK` responses
<4> Sets the execution interval for the scheduler that sends `HTTP/2` `PING frames and periodically checks for `ACK` responses
<5> Sets the threshold for retrying `HTTP/2` `PING` frame transmissions.


The following listing presents a simple `H2C` example:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2024 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2011-2025 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -111,6 +111,7 @@ public interface NettyPipeline {
String OnChannelReadIdle = LEFT + "onChannelReadIdle";
String OnChannelWriteIdle = LEFT + "onChannelWriteIdle";
String ProxyHandler = LEFT + "proxyHandler";
String H2LivenessHandler = LEFT + "h2LivenessHandler";
/**
* Use to register a special handler which ensures that any {@link io.netty.channel.VoidChannelPromise}
* will be converted to "unvoided" promises.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2020-2025 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -21,13 +21,20 @@
import reactor.netty.http.client.HttpClient;
import reactor.util.function.Tuple2;

import java.time.Duration;

public class H2Application {

public static void main(String[] args) {
HttpClient client =
HttpClient.create()
.protocol(HttpProtocol.H2) //<1>
.secure(); //<2>
.secure() //<2>
.http2Settings(
builder -> builder.pingAckTimeout(Duration.ofMillis(600)) // <3>
.pingScheduleInterval(Duration.ofMillis(300)) // <4>
.pingAckDropThreshold(2) // <5>
);

Tuple2<String, HttpHeaders> response =
client.get()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2023 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2020-2025 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,7 @@
import io.netty.handler.codec.http2.Http2Settings;
import reactor.util.annotation.Nullable;

import java.time.Duration;
import java.util.Objects;

/**
Expand Down Expand Up @@ -93,6 +94,79 @@ public interface Builder {
* @return {@code this}
*/
//Builder pushEnabled(boolean pushEnabled);

/**
* Sets the interval for sending HTTP/2 PING frames and receiving ACK responses.
*
* <p>
* This method configures the time interval at which PING frames are sent to the peer.
* The interval should be chosen carefully to balance between detecting connection issues
* and minimizing unnecessary network traffic.
* </p>
*
* <p>
* If the interval is set too short, it may cause excessive network overhead.
* If set too long, connection failures may not be detected in a timely manner.
* </p>
*
* @param pingAckTimeout the interval in between consecutive PING frames
* and ACK responses. Must be a positive value.
*/
default Builder pingAckTimeout(Duration pingAckTimeout) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets the interval for sending HTTP/2 PING frames and receiving ACK responses.

return this;
}

/**
* Sets the execution interval for the scheduler that sends HTTP/2 PING frames
* and periodically checks for ACK responses.
*
* <p>
* This method configures the time interval at which the scheduler runs
* to send PING frames and verify if ACK responses are received within
* the expected timeframe.
* Proper tuning of this interval helps in detecting connection issues
* while avoiding unnecessary network overhead.
* </p>
*
* <p>
* If the interval is too short, it may increase network and CPU usage.
* Conversely, setting it too long may delay the detection of connection failures.
* </p>
*
* @param pingScheduleInterval the interval in at which the scheduler executes.
* Must be a positive value.
*/
default Builder pingScheduleInterval(Duration pingScheduleInterval) {
return this;
}
Comment on lines +139 to +141
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets the execution interval for the scheduler that sends HTTP/2 PING frames


/**
* Sets the threshold for retrying HTTP/2 PING frame transmissions.
*
* <p>
* This method defines the maximum number of attempts to send a PING frame
* before considering the connection as unresponsive.
* If the threshold is exceeded without receiving an ACK response,
* the connection may be closed or marked as unhealthy.
* </p>
*
* <p>
* A lower threshold can detect connection failures more quickly but may lead
* to premature disconnections. Conversely, a higher threshold allows more retries
* but may delay failure detection.
* </p>
*
* <p>
* If this value is not specified, it defaults to 0, meaning only one attempt to send a PING frame is made without retries.
* </p>
*
* @param pingAckDropThreshold the maximum number of PING transmission attempts.
* Must be a positive integer.
* The default value is 0, meaning no retries will occur and only one PING frame will be sent.
*/
default Builder pingAckDropThreshold(Integer pingAckDropThreshold) {
return this;
}
Comment on lines +167 to +169
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets the threshold for retrying HTTP/2 PING frame transmissions. (default. 0)

Comment on lines +98 to +169
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is declared as a default method for backward compatibility.

}

/**
Expand Down Expand Up @@ -176,6 +250,36 @@ public Boolean pushEnabled() {
return pushEnabled;
}

/**
* Returns the configured {@code pingAckTimeout} value or null.
*
* @return the configured {@code pingAckTimeout} value or null
*/
@Nullable
public Duration pingAckTimeout() {
return pingAckTimeout;
}

/**
* Returns the configured {@code pingScheduleInterval} value or null.
*
* @return the configured {@code pingScheduleInterval} value or null
*/
@Nullable
public Duration pingScheduleInterval() {
return pingScheduleInterval;
}

/**
* Returns the configured {@code pingAckDropThreshold} value or null.
*
* @return the configured {@code pingAckDropThreshold} value or null
*/
@Nullable
public Integer pingAckDropThreshold() {
return pingAckDropThreshold;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -191,7 +295,10 @@ public boolean equals(Object o) {
Objects.equals(maxFrameSize, that.maxFrameSize) &&
maxHeaderListSize.equals(that.maxHeaderListSize) &&
Objects.equals(maxStreams, that.maxStreams) &&
Objects.equals(pushEnabled, that.pushEnabled);
Objects.equals(pushEnabled, that.pushEnabled) &&
Objects.equals(pingAckTimeout, that.pingAckTimeout) &&
Objects.equals(pingScheduleInterval, that.pingScheduleInterval) &&
Objects.equals(pingAckDropThreshold, that.pingAckDropThreshold);
}

@Override
Expand All @@ -204,6 +311,9 @@ public int hashCode() {
result = 31 * result + Long.hashCode(maxHeaderListSize);
result = 31 * result + Long.hashCode(maxStreams);
result = 31 * result + Boolean.hashCode(pushEnabled);
result = 31 * result + Objects.hashCode(pingAckTimeout);
result = 31 * result + Objects.hashCode(pingScheduleInterval);
result = 31 * result + Objects.hashCode(pingAckDropThreshold);
return result;
}

Expand All @@ -214,6 +324,9 @@ public int hashCode() {
final Long maxHeaderListSize;
final Long maxStreams;
final Boolean pushEnabled;
final Duration pingAckTimeout;
final Duration pingScheduleInterval;
final Integer pingAckDropThreshold;

Http2SettingsSpec(Build build) {
Http2Settings settings = build.http2Settings;
Expand All @@ -230,10 +343,16 @@ public int hashCode() {
maxHeaderListSize = settings.maxHeaderListSize();
maxStreams = build.maxStreams;
pushEnabled = settings.pushEnabled();
pingAckTimeout = build.pingAckTimeout;
pingScheduleInterval = build.pingScheduleInterval;
pingAckDropThreshold = build.pingAckDropThreshold;
}

static final class Build implements Builder {
Long maxStreams;
Duration pingAckTimeout;
Duration pingScheduleInterval;
Integer pingAckDropThreshold;
final Http2Settings http2Settings = Http2Settings.defaultSettings();

@Override
Expand Down Expand Up @@ -280,6 +399,24 @@ public Builder maxStreams(long maxStreams) {
return this;
}

@Override
public Builder pingAckTimeout(Duration pingAckTimeout) {
this.pingAckTimeout = pingAckTimeout;
return this;
}

@Override
public Builder pingScheduleInterval(Duration pingScheduleInterval) {
this.pingScheduleInterval = pingScheduleInterval;
return this;
}

@Override
public Builder pingAckDropThreshold(Integer pingAckDropThreshold) {
this.pingAckDropThreshold = pingAckDropThreshold;
return this;
}

/*
@Override
public Builder pushEnabled(boolean pushEnabled) {
Expand Down
Loading