This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 875c79e494 Fix incorrect window size when upgrading from HTTP/1.1
875c79e494 is described below
commit 875c79e4940c570731f7068dbb20f979a3b18d1c
Author: remm <[email protected]>
AuthorDate: Mon Aug 31 14:14:32 2026 +0200
Fix incorrect window size when upgrading from HTTP/1.1
Co authored with OpenCode, found in code review.
---
.../apache/coyote/http2/Http2UpgradeHandler.java | 11 +++++
.../coyote/http2/TestHttp2UpgradeHandler.java | 56 ++++++++++++++++++++++
webapps/docs/changelog.xml | 4 ++
3 files changed, 71 insertions(+)
diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
index 262ca60cb0..a1ba6f8110 100644
--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
@@ -283,6 +283,8 @@ class Http2UpgradeHandler extends AbstractStream implements
InternalHttpUpgradeH
// Settings are only valid on stream 0
FrameType.SETTINGS.check(0, settings.length);
+ long oldInitialWindowSize =
remoteSettings.getInitialWindowSize();
+
for (int i = 0; i < settings.length / 6; i++) {
int id = ByteUtil.getTwoBytes(settings, i * 6);
long value = ByteUtil.getFourBytes(settings, (i * 6) + 2);
@@ -293,6 +295,15 @@ class Http2UpgradeHandler extends AbstractStream
implements InternalHttpUpgradeH
}
remoteSettings.set(key, value);
}
+
+ // Stream 1 was created before the client settings were
+ // processed and so was given the default initial window
+ // size. Apply any change in the initial window size to
+ // stream 1 (mirrors setting()).
+ long delta = remoteSettings.getInitialWindowSize() -
oldInitialWindowSize;
+ if (delta != 0) {
+ stream.incrementWindowSize((int) delta);
+ }
} catch (IllegalArgumentException | Http2Exception e) {
throw new
ProtocolException(sm.getString("upgradeHandler.upgrade.fail", connectionId), e);
}
diff --git a/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
b/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
index d754e6e721..5f7ad85939 100644
--- a/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
+++ b/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
@@ -18,6 +18,7 @@ package org.apache.coyote.http2;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import org.junit.Assert;
import org.junit.Test;
@@ -194,6 +195,61 @@ public class TestHttp2UpgradeHandler extends Http2TestBase
{
}
+ @Test
+ public void testUpgradeAdvertisedInitialWindowSize() throws Exception {
+ enableHttp2();
+
+ Tomcat tomcat = getTomcatInstance();
+
+ Context ctxt = getProgrammaticRootContext();
+ Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
+ ctxt.addServletMapping("/simple", "simple");
+
+ tomcat.start();
+
+ openClientConnection();
+
+ // Advertise an initial window size that is smaller than the default
+ int initialWindowSize = 4096;
+ byte[] settingsPayload = new byte[6];
+ ByteUtil.setTwoBytes(settingsPayload, 0,
Setting.INITIAL_WINDOW_SIZE.getId());
+ ByteUtil.setFourBytes(settingsPayload, 2, initialWindowSize);
+ String settingsHeader = "HTTP2-Settings: " +
Base64.getUrlEncoder().encodeToString(settingsPayload) + "\r\n";
+
+ doHttpUpgrade(DEFAULT_CONNECTION_HEADER_VALUE, "h2c", settingsHeader,
true);
+
+ sendClientPreface();
+
+ // - 101 response acts as acknowledgement of the HTTP2-Settings header
+ // Need to read 5 frames
+ // - settings (server settings - must be first)
+ // - settings ack (for the settings frame in the client preface)
+ // - ping
+ // - headers (for response)
+ // - data (for response body, limited to the advertised initial window)
+ parser.readFrame();
+ parser.readFrame();
+ parser.readFrame();
+ parser.readFrame();
+ parser.readFrame();
+
+ Assert.assertEquals("0-Settings-[3]-[200]\n" + "0-Settings-End\n" +
"0-Settings-Ack\n" +
+ "0-Ping-[0,0,0,0,0,0,0,1]\n" + "1-HeadersStart\n" +
"1-Header-[:status]-[200]\n" +
+ "1-Header-[content-type]-[application/octet-stream]\n" +
"1-Header-[content-length]-[" +
+ SimpleServlet.CONTENT_LENGTH + "]\n" + "1-Header-[date]-[" +
DEFAULT_DATE + "]\n" + "1-HeadersEnd\n" +
+ "1-Body-" + initialWindowSize + "\n", output.getTrace());
+ output.clearTrace();
+
+ // The advertised stream window is now exhausted. Expand it and the
+ // rest of the response should follow.
+ sendWindowUpdate(1, initialWindowSize);
+ parser.readFrame();
+
+ Assert.assertEquals("1-Body-" + (SimpleServlet.CONTENT_LENGTH -
initialWindowSize) + "\n" + "1-EndOfStream\n",
+ output.getTrace());
+ }
+
+
@Test
public void testActiveConnectionCountAndClientTimeout() throws Exception {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 44dffdd6ef..4b9e608d19 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -345,6 +345,10 @@
In HTTP/2 after half closed (remote), any unexpected frame should be
a stream error. (remm)
</fix>
+ <fix>
+ Fix incorrect initial window size calculation when upgrading to HTTP/2.
+ (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]