Skip to content

Commit f7bcc7f

Browse files
committed
Improved HTTP2Connection dump information
Signed-off-by: znight1020 <[email protected]>
1 parent 7e4a9be commit f7bcc7f

File tree

3 files changed

+274
-2
lines changed

3 files changed

+274
-2
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@
4141
import org.eclipse.jetty.util.BufferUtil;
4242
import org.eclipse.jetty.util.Callback;
4343
import org.eclipse.jetty.util.TypeUtil;
44+
import org.eclipse.jetty.util.component.Dumpable;
4445
import org.eclipse.jetty.util.component.LifeCycle;
4546
import org.eclipse.jetty.util.thread.AutoLock;
4647
import org.eclipse.jetty.util.thread.ExecutionStrategy;
4748
import org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy;
4849
import org.slf4j.Logger;
4950
import org.slf4j.LoggerFactory;
5051

51-
public class HTTP2Connection extends AbstractConnection implements Parser.Listener, Connection.UpgradeTo
52+
public class HTTP2Connection extends AbstractConnection implements Parser.Listener, Connection.UpgradeTo, Dumpable
5253
{
5354
private static final Logger LOG = LoggerFactory.getLogger(HTTP2Connection.class);
5455

@@ -333,6 +334,12 @@ public String toConnectionString()
333334
return "%s@%x[%s]".formatted(TypeUtil.toShortName(getClass()), hashCode(), strategy);
334335
}
335336

337+
@Override
338+
public void dump(Appendable out, String indent) throws IOException
339+
{
340+
Dumpable.dumpObjects(out, indent, this, session);
341+
}
342+
336343
protected class HTTP2Producer implements ExecutionStrategy.Producer
337344
{
338345
private static final RetainableByteBuffer.Mutable STOPPED = new RetainableByteBuffer.NonRetainableByteBuffer(BufferUtil.EMPTY_BUFFER);

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Collection;
2424
import java.util.Collections;
2525
import java.util.EventListener;
26+
import java.util.HashMap;
2627
import java.util.Iterator;
2728
import java.util.List;
2829
import java.util.Map;
@@ -34,6 +35,7 @@
3435
import java.util.concurrent.TimeoutException;
3536
import java.util.concurrent.atomic.AtomicInteger;
3637
import java.util.concurrent.atomic.AtomicLong;
38+
import java.util.concurrent.atomic.AtomicReference;
3739
import java.util.function.BiConsumer;
3840
import java.util.function.Consumer;
3941
import java.util.function.Predicate;
@@ -77,6 +79,7 @@
7779
import org.eclipse.jetty.util.component.AbstractLifeCycle;
7880
import org.eclipse.jetty.util.component.Dumpable;
7981
import org.eclipse.jetty.util.component.DumpableCollection;
82+
import org.eclipse.jetty.util.component.DumpableMap;
8083
import org.eclipse.jetty.util.thread.AutoLock;
8184
import org.eclipse.jetty.util.thread.Invocable;
8285
import org.eclipse.jetty.util.thread.Scheduler;
@@ -94,6 +97,8 @@ public abstract class HTTP2Session extends AbstractLifeCycle implements Session,
9497
private final Set<Integer> priorityStreams = ConcurrentHashMap.newKeySet();
9598
private final List<FrameListener> frameListeners = new CopyOnWriteArrayList<>();
9699
private final List<LifeCycleListener> lifeCycleListeners = new CopyOnWriteArrayList<>();
100+
private final AtomicReference<Map<Integer, Integer>> localSettingsSnapshot = new AtomicReference<>(Map.of());
101+
private final AtomicReference<Map<Integer, Integer>> remoteSettingsSnapshot = new AtomicReference<>(Map.of());
97102
private final AtomicLong streamsOpened = new AtomicLong();
98103
private final AtomicLong streamsClosed = new AtomicLong();
99104
private final StreamsState streamsState = new StreamsState();
@@ -299,6 +304,26 @@ public long getBytesWritten()
299304
return bytesWritten.get();
300305
}
301306

307+
/**
308+
* Returns a snapshot of the last SETTINGS that this endpoint sent
309+
* <p>The map keys are SETTINGS ids as defined in {@link SettingsFrame}.
310+
* @return local (sent) SETTINGS
311+
*/
312+
public Map<Integer, Integer> getCurrentLocalSettings()
313+
{
314+
return localSettingsSnapshot.get();
315+
}
316+
317+
/**
318+
* Returns a snapshot of the last SETTINGS that the peer sent
319+
* <p>The map keys are SETTINGS ids as defined in {@link SettingsFrame}.
320+
* @return remote (received) SETTINGS
321+
*/
322+
public Map<Integer, Integer> getCurrentRemoteSettings()
323+
{
324+
return remoteSettingsSnapshot.get();
325+
}
326+
302327
@Override
303328
public void onData(DataFrame frame)
304329
{
@@ -445,6 +470,15 @@ public void onSettings(SettingsFrame frame, boolean reply)
445470
return;
446471

447472
Map<Integer, Integer> settings = frame.getSettings();
473+
remoteSettingsSnapshot.updateAndGet(origin ->
474+
{
475+
if (settings.isEmpty())
476+
return origin;
477+
Map<Integer, Integer> updated = new HashMap<>(origin);
478+
updated.putAll(settings);
479+
return Map.copyOf(updated);
480+
});
481+
448482
configure(settings, false);
449483
notifySettings(this, frame);
450484

@@ -811,6 +845,18 @@ public void succeeded(Stream pushed)
811845
@Override
812846
public void settings(SettingsFrame frame, Callback callback)
813847
{
848+
if (!frame.isReply())
849+
{
850+
Map<Integer, Integer> settings = frame.getSettings();
851+
localSettingsSnapshot.updateAndGet(origin ->
852+
{
853+
if (settings.isEmpty())
854+
return origin;
855+
Map<Integer, Integer> updated = new HashMap<>(origin);
856+
updated.putAll(settings);
857+
return Map.copyOf(updated);
858+
});
859+
}
814860
control(null, callback, frame);
815861
}
816862

@@ -1442,7 +1488,9 @@ protected static boolean isClientStream(int streamId)
14421488
@Override
14431489
public void dump(Appendable out, String indent) throws IOException
14441490
{
1445-
Dumpable.dumpObjects(out, indent, this, flowControl, flusher, new DumpableCollection("streams", streams.values()));
1491+
DumpableMap localSettings = new DumpableMap("local settings", getCurrentLocalSettings());
1492+
DumpableMap remoteSettings = new DumpableMap("remote settings", getCurrentRemoteSettings());
1493+
Dumpable.dumpObjects(out, indent, this, flowControl, flusher, new DumpableCollection("streams", streams.values()), localSettings, remoteSettings);
14461494
}
14471495

14481496
@Override
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
//
2+
// ========================================================================
3+
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
4+
//
5+
// This program and the accompanying materials are made available under the
6+
// terms of the Eclipse Public License v. 2.0 which is available at
7+
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
8+
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
9+
//
10+
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
11+
// ========================================================================
12+
//
13+
14+
package org.eclipse.jetty.http2.tests;
15+
16+
import java.io.IOException;
17+
import java.io.OutputStream;
18+
import java.net.Socket;
19+
import java.util.Collections;
20+
import java.util.Map;
21+
22+
import org.eclipse.jetty.http2.api.Session;
23+
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
24+
import org.eclipse.jetty.http2.frames.PrefaceFrame;
25+
import org.eclipse.jetty.http2.frames.SettingsFrame;
26+
import org.eclipse.jetty.http2.parser.Parser;
27+
import org.eclipse.jetty.io.Connection;
28+
import org.eclipse.jetty.io.Content;
29+
import org.eclipse.jetty.io.EndPoint;
30+
import org.eclipse.jetty.io.RetainableByteBuffer;
31+
import org.eclipse.jetty.util.Callback;
32+
import org.eclipse.jetty.util.component.Dumpable;
33+
import org.junit.jupiter.api.BeforeEach;
34+
import org.junit.jupiter.api.Test;
35+
36+
import static org.junit.jupiter.api.Assertions.assertNotNull;
37+
import static org.junit.jupiter.api.Assertions.assertTrue;
38+
39+
public class HTTP2ConnectionDumpTest extends AbstractServerTest
40+
{
41+
@BeforeEach
42+
public void setUp() throws Exception
43+
{
44+
startServer(new ServerSessionListener()
45+
{
46+
@Override
47+
public void onAccept(Session session)
48+
{
49+
session.settings(new SettingsFrame(Map.of(
50+
SettingsFrame.MAX_CONCURRENT_STREAMS, 124,
51+
SettingsFrame.MAX_FRAME_SIZE, 32768
52+
), false), Callback.NOOP);
53+
}
54+
});
55+
}
56+
57+
@Test
58+
public void testDumpSettingsLocalAndRemote() throws Exception
59+
{
60+
try (Socket client = new Socket("localhost", connector.getLocalPort()))
61+
{
62+
client.setTcpNoDelay(true);
63+
client.setSoTimeout(5000);
64+
65+
OutputStream output = client.getOutputStream();
66+
RetainableByteBuffer.Mutable accumulator = new RetainableByteBuffer.DynamicCapacity();
67+
generator.control(accumulator, new PrefaceFrame());
68+
generator.control(accumulator, new SettingsFrame(Map.of(SettingsFrame.MAX_CONCURRENT_STREAMS, 128), false));
69+
accumulator.writeTo(Content.Sink.from(output), false);
70+
output.flush();
71+
72+
Parser parser = new Parser(bufferPool, 8192);
73+
parser.init(new Parser.Listener() {});
74+
parseResponse(client, parser, 1000);
75+
}
76+
77+
String dump = getConnectionDump();
78+
assertNotNull(dump);
79+
80+
assertTrue(dump.contains("local settings"), dump);
81+
assertTrue(dump.contains("size=2"), dump);
82+
assertTrue(dump.contains("+> 3: 124"), dump);
83+
assertTrue(dump.contains("+> 5: 32768"), dump);
84+
85+
assertTrue(dump.contains("remote settings"), dump);
86+
assertTrue(dump.contains("size=1"), dump);
87+
assertTrue(dump.contains("+> 3: 128"), dump);
88+
}
89+
90+
@Test
91+
public void testDumpSettingsUpdate() throws Exception
92+
{
93+
try (Socket client = new Socket("localhost", connector.getLocalPort()))
94+
{
95+
client.setTcpNoDelay(true);
96+
client.setSoTimeout(5000);
97+
98+
OutputStream output = client.getOutputStream();
99+
Parser parser = new Parser(bufferPool, 8192);
100+
parser.init(new Parser.Listener() {});
101+
102+
RetainableByteBuffer.Mutable accumulator1 = new RetainableByteBuffer.DynamicCapacity();
103+
generator.control(accumulator1, new PrefaceFrame());
104+
generator.control(accumulator1, new SettingsFrame(Map.of(SettingsFrame.MAX_CONCURRENT_STREAMS, 128), false));
105+
accumulator1.writeTo(Content.Sink.from(output), false);
106+
output.flush();
107+
parseResponse(client, parser, 1000);
108+
109+
RetainableByteBuffer.Mutable accumulator2 = new RetainableByteBuffer.DynamicCapacity();
110+
generator.control(accumulator2, new SettingsFrame(Map.of(
111+
SettingsFrame.ENABLE_CONNECT_PROTOCOL, 1
112+
), false));
113+
accumulator2.writeTo(Content.Sink.from(output), false);
114+
output.flush();
115+
parseResponse(client, parser, 1000);
116+
}
117+
118+
String dump = getConnectionDump();
119+
assertNotNull(dump);
120+
121+
// New params are added and existing ones preserved.
122+
assertTrue(dump.contains("remote settings size=2"), dump);
123+
assertTrue(dump.contains("+> 3: 128"), dump);
124+
assertTrue(dump.contains("+> 8: 1"), dump);
125+
}
126+
127+
@Test
128+
public void testDumpSettingsIgnoreEmpty() throws Exception
129+
{
130+
try (Socket client = new Socket("localhost", connector.getLocalPort()))
131+
{
132+
client.setTcpNoDelay(true);
133+
client.setSoTimeout(5000);
134+
135+
OutputStream output = client.getOutputStream();
136+
Parser parser = new Parser(bufferPool, 8192);
137+
parser.init(new Parser.Listener() {});
138+
139+
RetainableByteBuffer.Mutable acc1 = new RetainableByteBuffer.DynamicCapacity();
140+
generator.control(acc1, new PrefaceFrame());
141+
generator.control(acc1, new SettingsFrame(Map.of(SettingsFrame.MAX_CONCURRENT_STREAMS, 128), false));
142+
acc1.writeTo(Content.Sink.from(output), false);
143+
output.flush();
144+
parseResponse(client, parser, 1000);
145+
146+
// Send empty SETTINGS (no-op).
147+
RetainableByteBuffer.Mutable acc2 = new RetainableByteBuffer.DynamicCapacity();
148+
generator.control(acc2, new SettingsFrame(Collections.emptyMap(), false));
149+
acc2.writeTo(Content.Sink.from(output), false);
150+
output.flush();
151+
parseResponse(client, parser, 1000);
152+
}
153+
154+
String dump = getConnectionDump();
155+
assertNotNull(dump);
156+
157+
// Unchanged local, remote settings.
158+
assertTrue(dump.contains("local settings size=2"), dump);
159+
assertTrue(dump.contains("+> 3: 124"), dump);
160+
assertTrue(dump.contains("+> 5: 32768"), dump);
161+
assertTrue(dump.contains("remote settings size=1"), dump);
162+
assertTrue(dump.contains("+> 3: 128"), dump);
163+
}
164+
165+
@Test
166+
public void testDumpSettingsIgnoreAck() throws Exception
167+
{
168+
try (Socket client = new Socket("localhost", connector.getLocalPort()))
169+
{
170+
client.setTcpNoDelay(true);
171+
client.setSoTimeout(5000);
172+
173+
OutputStream output = client.getOutputStream();
174+
RetainableByteBuffer.Mutable accumulator1 = new RetainableByteBuffer.DynamicCapacity();
175+
generator.control(accumulator1, new PrefaceFrame());
176+
generator.control(accumulator1, new SettingsFrame(Map.of(SettingsFrame.MAX_CONCURRENT_STREAMS, 128), false));
177+
accumulator1.writeTo(Content.Sink.from(output), false);
178+
output.flush();
179+
180+
Parser parser = new Parser(bufferPool, 8192);
181+
parser.init(new Parser.Listener() {});
182+
parseResponse(client, parser, 1000);
183+
184+
// Sent ACK, remote to local.
185+
RetainableByteBuffer.Mutable accumulator2 = new RetainableByteBuffer.DynamicCapacity();
186+
generator.control(accumulator2, new SettingsFrame(Collections.emptyMap(), true));
187+
accumulator2.writeTo(Content.Sink.from(output), false);
188+
output.flush();
189+
parseResponse(client, parser, 1000);
190+
}
191+
192+
String dump = getConnectionDump();
193+
assertNotNull(dump);
194+
195+
// Unchanged local, remote settings.
196+
assertTrue(dump.contains("local settings size=2"), dump);
197+
assertTrue(dump.contains("+> 3: 124"), dump);
198+
assertTrue(dump.contains("+> 5: 32768"), dump);
199+
assertTrue(dump.contains("remote settings size=1"), dump);
200+
assertTrue(dump.contains("+> 3: 128"), dump);
201+
}
202+
203+
private String getConnectionDump() throws IOException
204+
{
205+
for (EndPoint ep : connector.getConnectedEndPoints())
206+
{
207+
Connection conn = ep.getConnection();
208+
if (conn instanceof Dumpable d)
209+
{
210+
StringBuilder sb = new StringBuilder();
211+
d.dump(sb, "");
212+
return sb.toString();
213+
}
214+
}
215+
return null;
216+
}
217+
}

0 commit comments

Comments
 (0)