Skip to content

Commit 0a56509

Browse files
authored
Fixes #11822 - HTTP/2 responses exceeding SETTINGS_MAX_HEADER_LIST_SIZE do not result in RST_STREAM or GOAWAY. (#12165)
Now HpackException.SessionException is treated specially in HTTP2Flusher. It is caught, it fails all the entries, and then tries to send a GOAWAY, which will be the only frame allowed into the HTTP2FLusher at that point. Signed-off-by: Simone Bordet <[email protected]>
1 parent b13f3cc commit 0a56509

File tree

5 files changed

+210
-33
lines changed

5 files changed

+210
-33
lines changed

jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ private HTTP2ClientConnection(HTTP2Client client, EndPoint endpoint, HTTP2Client
9595
public void onOpen()
9696
{
9797
Map<Integer, Integer> settings = listener.onPreface(getSession());
98-
if (settings == null)
99-
settings = new HashMap<>();
98+
settings = settings == null ? new HashMap<>() : new HashMap<>(settings);
10099

101100
// Below we want to populate any settings to send to the server
102101
// that have a different default than what prescribed by the RFC.

jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java

+5
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,11 @@ protected Entry(Frame frame, HTTP2Stream stream, Callback callback)
12481248
this.stream = stream;
12491249
}
12501250

1251+
public Frame frame()
1252+
{
1253+
return frame;
1254+
}
1255+
12511256
public abstract int getFrameBytesGenerated();
12521257

12531258
public int getDataBytesRemaining()

jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/internal/HTTP2Flusher.java

+50-12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import java.io.IOException;
1717
import java.nio.ByteBuffer;
18+
import java.nio.channels.ClosedChannelException;
1819
import java.util.ArrayDeque;
1920
import java.util.ArrayList;
2021
import java.util.Collection;
@@ -25,9 +26,11 @@
2526
import java.util.Queue;
2627
import java.util.Set;
2728

29+
import org.eclipse.jetty.http2.ErrorCode;
2830
import org.eclipse.jetty.http2.FlowControlStrategy;
2931
import org.eclipse.jetty.http2.HTTP2Session;
3032
import org.eclipse.jetty.http2.HTTP2Stream;
33+
import org.eclipse.jetty.http2.frames.FrameType;
3134
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
3235
import org.eclipse.jetty.http2.hpack.HpackException;
3336
import org.eclipse.jetty.io.ByteBufferPool;
@@ -92,10 +95,9 @@ public boolean prepend(HTTP2Session.Entry entry)
9295
entries.offerFirst(entry);
9396
if (LOG.isDebugEnabled())
9497
LOG.debug("Prepended {}, entries={}", entry, entries.size());
98+
return true;
9599
}
96100
}
97-
if (closed == null)
98-
return true;
99101
closed(entry, closed);
100102
return false;
101103
}
@@ -106,15 +108,17 @@ public boolean append(HTTP2Session.Entry entry)
106108
try (AutoLock ignored = lock.lock())
107109
{
108110
closed = terminated;
111+
// If it was not possible to HPACK encode, then allow to send a GOAWAY.
112+
if (closed instanceof HpackException.SessionException && entry.frame().getType() == FrameType.GO_AWAY)
113+
closed = null;
109114
if (closed == null)
110115
{
111116
entries.offer(entry);
112117
if (LOG.isDebugEnabled())
113118
LOG.debug("Appended {}, entries={}, {}", entry, entries.size(), this);
119+
return true;
114120
}
115121
}
116-
if (closed == null)
117-
return true;
118122
closed(entry, closed);
119123
return false;
120124
}
@@ -130,10 +134,9 @@ public boolean append(List<HTTP2Session.Entry> list)
130134
list.forEach(entries::offer);
131135
if (LOG.isDebugEnabled())
132136
LOG.debug("Appended {}, entries={} {}", list, entries.size(), this);
137+
return true;
133138
}
134139
}
135-
if (closed == null)
136-
return true;
137140
list.forEach(entry -> closed(entry, closed));
138141
return false;
139142
}
@@ -163,7 +166,21 @@ protected Action process() throws Throwable
163166
try (AutoLock ignored = lock.lock())
164167
{
165168
if (terminated != null)
166-
throw terminated;
169+
{
170+
boolean rethrow = true;
171+
if (terminated instanceof HpackException.SessionException)
172+
{
173+
HTTP2Session.Entry entry = entries.peek();
174+
if (entry != null && entry.frame().getType() == FrameType.GO_AWAY)
175+
{
176+
// Allow a SessionException to be processed once to send a GOAWAY.
177+
terminated = new ClosedChannelException().initCause(terminated);
178+
rethrow = false;
179+
}
180+
}
181+
if (rethrow)
182+
throw terminated;
183+
}
167184

168185
WindowEntry windowEntry;
169186
while ((windowEntry = windows.poll()) != null)
@@ -248,6 +265,15 @@ protected Action process() throws Throwable
248265
entry.failed(failure);
249266
pending.remove();
250267
}
268+
catch (HpackException.SessionException failure)
269+
{
270+
if (LOG.isDebugEnabled())
271+
LOG.debug("Failure generating {}", entry, failure);
272+
onSessionFailure(failure);
273+
// The method above will try to send
274+
// a GOAWAY, so we will iterate again.
275+
return Action.IDLE;
276+
}
251277
catch (Throwable failure)
252278
{
253279
// Failure to generate the entry is catastrophic.
@@ -339,7 +365,23 @@ protected void onCompleteSuccess()
339365
protected void onCompleteFailure(Throwable x)
340366
{
341367
accumulator.release();
368+
Throwable closed = fail(x);
369+
// If the failure came from within the
370+
// flusher, we need to close the connection.
371+
if (closed == null)
372+
session.onWriteFailure(x);
373+
}
374+
375+
private void onSessionFailure(Throwable x)
376+
{
377+
accumulator.release();
378+
Throwable closed = fail(x);
379+
if (closed == null)
380+
session.close(ErrorCode.COMPRESSION_ERROR.code, null, NOOP);
381+
}
342382

383+
private Throwable fail(Throwable x)
384+
{
343385
Throwable closed;
344386
Set<HTTP2Session.Entry> allEntries;
345387
try (AutoLock ignored = lock.lock())
@@ -361,11 +403,7 @@ protected void onCompleteFailure(Throwable x)
361403
allEntries.addAll(pendingEntries);
362404
pendingEntries.clear();
363405
allEntries.forEach(entry -> entry.failed(x));
364-
365-
// If the failure came from within the
366-
// flusher, we need to close the connection.
367-
if (closed == null)
368-
session.onWriteFailure(x);
406+
return closed;
369407
}
370408

371409
public void terminate(Throwable cause)

jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HTTP2ServerTest.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,20 @@ public boolean handle(Request request, Response response, Callback callback)
428428
}
429429
output.flush();
430430

431+
AtomicBoolean goAway = new AtomicBoolean();
431432
Parser parser = new Parser(bufferPool, 8192);
432-
parser.init(new Parser.Listener() {});
433+
parser.init(new Parser.Listener()
434+
{
435+
@Override
436+
public void onGoAway(GoAwayFrame frame)
437+
{
438+
goAway.set(true);
439+
}
440+
});
433441
boolean closed = parseResponse(client, parser);
434442

435-
assertTrue(closed);
443+
assertFalse(closed);
444+
assertTrue(goAway.get());
436445
}
437446
}
438447
}

0 commit comments

Comments
 (0)