Skip to content

Commit c11dd4d

Browse files
committed
HTTP frame logging.
1 parent 2e588f2 commit c11dd4d

11 files changed

Lines changed: 399 additions & 52 deletions

File tree

vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3ClientConnection.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ public Http3ClientConnection(QuicConnectionInternal connection,
5656
HostAndPort authority,
5757
ClientMetrics<Object, HttpRequest, HttpResponse> clientMetrics,
5858
long keepAliveTimeoutMillis,
59-
Http3Settings localSettings) {
60-
super(connection, localSettings);
59+
Http3Settings localSettings,
60+
Http3FrameLogger frameLogger) {
61+
super(connection, localSettings, frameLogger);
6162

6263
this.authority = authority;
6364
this.clientMetrics = clientMetrics;
@@ -67,8 +68,6 @@ public Http3ClientConnection(QuicConnectionInternal connection,
6768

6869
public void init() {
6970

70-
super.init();
71-
7271
io.netty.handler.codec.http3.Http3Settings nSettings = nettyLocalSettings();
7372

7473
Http3ClientConnectionHandler http3Handler = new Http3ClientConnectionHandler(
@@ -86,6 +85,8 @@ public void init() {
8685
if (clientMetrics != null) {
8786
clientMetrics.connected();
8887
}
88+
89+
super.init();
8990
}
9091

9192
@Override

vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Connection.java

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
package io.vertx.core.http.impl.http3;
1212

1313
import io.netty.channel.ChannelFuture;
14+
import io.netty.channel.ChannelHandler;
15+
import io.netty.channel.ChannelHandlerContext;
16+
import io.netty.channel.ChannelPipeline;
1417
import io.netty.handler.codec.http3.*;
1518
import io.netty.handler.codec.quic.QuicStreamChannel;
1619
import io.netty.util.collection.LongObjectHashMap;
@@ -33,14 +36,14 @@
3336
import java.util.Iterator;
3437
import java.util.List;
3538
import java.util.Map;
36-
import java.util.concurrent.TimeUnit;
3739

3840
/**
3941
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
4042
*/
4143
public abstract class Http3Connection implements HttpConnection {
4244

4345
private final LongObjectMap<Http3Stream<?, ?>> streams;
46+
private final Http3FrameLogger frameLogger;
4447
final ContextInternal context;
4548
final QuicConnectionInternal connection;
4649
private QuicStreamInternal controlStream;
@@ -53,8 +56,9 @@ public abstract class Http3Connection implements HttpConnection {
5356
private Http3Settings remoteSettings;
5457
private Handler<HttpSettings> remoteSettingsHandler;
5558

56-
public Http3Connection(QuicConnectionInternal connection, Http3Settings localSettings) {
59+
public Http3Connection(QuicConnectionInternal connection, Http3Settings localSettings, Http3FrameLogger frameLogger) {
5760
this.streams = new LongObjectHashMap<>();
61+
this.frameLogger = frameLogger;
5862
this.context = connection.context();
5963
this.connection = connection;
6064
this.remoteGoAway = -1L;
@@ -77,9 +81,6 @@ io.netty.handler.codec.http3.Http3Settings nettyLocalSettings() {
7781
return nSettings;
7882
}
7983

80-
void handleStream(QuicStreamInternal quicStream) {
81-
}
82-
8384
void registerStream(Http3Stream<?, ?> stream) {
8485
streams.put(stream.id(), stream);
8586
}
@@ -88,7 +89,32 @@ void unregisterStream(Http3Stream<?, ?> stream) {
8889
streams.remove(stream.id());
8990
}
9091

91-
private void handleControlStream(QuicStreamInternal quicStream) {
92+
void setupFrameLogger(QuicStreamInternal quicStream) {
93+
if (frameLogger != null) {
94+
ChannelHandlerContext chctx = quicStream.channelHandlerContext();
95+
ChannelPipeline pipeline = chctx.pipeline();
96+
pipeline.addBefore("handler", "logging", frameLogger);
97+
}
98+
}
99+
100+
void handleRequestStream(QuicStreamInternal quicStream) {
101+
}
102+
103+
protected void handleOutboundControlStream(QuicStreamChannel quicStream) {
104+
if (frameLogger != null) {
105+
ChannelPipeline pipeline = quicStream.pipeline();
106+
for (Map.Entry<String, ChannelHandler> handler : pipeline) {
107+
String name = handler.getValue().getClass().getName();
108+
if (name.equals("io.netty.handler.codec.http3.Http3FrameCodec")) {
109+
pipeline.addAfter(handler.getKey(), "logging", frameLogger);
110+
break;
111+
}
112+
}
113+
}
114+
}
115+
116+
protected void handleInboundControlStream(QuicStreamInternal quicStream) {
117+
setupFrameLogger(quicStream);
92118
quicStream.idleHandler(idle -> {
93119
// Keep stream alive
94120
});
@@ -121,13 +147,13 @@ public void init() {
121147
if (isStream) {
122148
if (localGoAway == -1L) {
123149
mostRecentRemoteStreamId = stream.id();
124-
handleStream(quicStream);
150+
handleRequestStream(quicStream);
125151
} else {
126152
quicStream.reset(Http3ErrorCode.H3_REQUEST_REJECTED.code());
127153
}
128154
} else {
129155
controlStream = quicStream;
130-
handleControlStream(quicStream);
156+
handleInboundControlStream(quicStream);
131157
}
132158
});
133159
connection.shutdownHandler(timeout -> {
@@ -145,6 +171,9 @@ public void init() {
145171
connection.closeHandler(v -> {
146172
handleClosed();
147173
});
174+
175+
QuicStreamChannel inboundControlStream = Http3.getLocalControlStream(connection.channelHandlerContext().channel());
176+
handleOutboundControlStream(inboundControlStream);
148177
}
149178

150179
@Override
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
package io.vertx.core.http.impl.http3;
12+
13+
import io.netty.buffer.ByteBuf;
14+
import io.netty.buffer.ByteBufUtil;
15+
import io.netty.channel.ChannelDuplexHandler;
16+
import io.netty.channel.ChannelHandler;
17+
import io.netty.channel.ChannelHandlerContext;
18+
import io.netty.channel.ChannelId;
19+
import io.netty.channel.ChannelPromise;
20+
import io.netty.handler.codec.http3.Http3DataFrame;
21+
import io.netty.handler.codec.http3.Http3Frame;
22+
import io.netty.handler.codec.http3.Http3GoAwayFrame;
23+
import io.netty.handler.codec.http3.Http3HeadersFrame;
24+
import io.netty.handler.codec.http3.Http3Settings;
25+
import io.netty.handler.codec.http3.Http3SettingsFrame;
26+
import io.netty.handler.codec.quic.QuicStreamChannel;
27+
import io.netty.util.internal.logging.InternalLogLevel;
28+
import io.netty.util.internal.logging.InternalLogger;
29+
import io.netty.util.internal.logging.InternalLoggerFactory;
30+
31+
@ChannelHandler.Sharable
32+
public class Http3FrameLogger extends ChannelDuplexHandler {
33+
34+
private static final int BUFFER_LENGTH_THRESHOLD = 64;
35+
36+
private final InternalLogLevel level;
37+
private final InternalLogger logger;
38+
39+
public Http3FrameLogger(InternalLogLevel level) {
40+
this.level = level;
41+
this.logger = InternalLoggerFactory.getInstance(Http3FrameLogger.class);
42+
}
43+
44+
public void log(boolean inbound, QuicStreamChannel stream, Http3Frame frame) {
45+
long type = frame.type();
46+
if (logger.isEnabled(level) && type >= 0 && type < 16) {
47+
ChannelId streamId = stream.id();
48+
String direction = inbound ? "INBOUND" : "OUTBOUND";
49+
switch ((int)frame.type()) {
50+
// Data
51+
case 0x0:
52+
Http3DataFrame dataFrame = (Http3DataFrame) frame;
53+
ByteBuf data = dataFrame.content();
54+
logger.log(level, "{} {} DATA: streamId={} length={} bytes={}", stream,
55+
direction, streamId, data.readableBytes(), toString(data));
56+
break;
57+
case 0x1:
58+
Http3HeadersFrame headersFrame = (Http3HeadersFrame) frame;
59+
logger.log(level, "{} {} HEADERS: streamId={} headers={}", stream,
60+
direction, streamId, headersFrame.headers());
61+
break;
62+
case 0x4:
63+
Http3SettingsFrame settingsFrame = (Http3SettingsFrame) frame;
64+
Http3Settings settings = settingsFrame.settings();
65+
logger.log(level, "{} {} SETTINGS: streamId={} settings={}", stream, direction, streamId, settings);
66+
break;
67+
case 0x7:
68+
Http3GoAwayFrame goAwayFrame = (Http3GoAwayFrame) frame;
69+
logger.log(level, "{} {} GO_AWAY: streamId={} id={}", stream,
70+
direction, streamId, goAwayFrame.id());
71+
break;
72+
}
73+
}
74+
}
75+
76+
@Override
77+
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
78+
QuicStreamChannel stream = (QuicStreamChannel) ctx.channel();
79+
if (msg instanceof Http3Frame) {
80+
Http3Frame frame = (Http3Frame) msg;
81+
log(true, stream, frame);
82+
}
83+
super.channelRead(ctx, msg);
84+
}
85+
86+
@Override
87+
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
88+
QuicStreamChannel stream = (QuicStreamChannel) ctx.channel();
89+
if (msg instanceof Http3Frame) {
90+
Http3Frame frame = (Http3Frame) msg;
91+
log(false, stream, frame);
92+
}
93+
super.write(ctx, msg, promise);
94+
}
95+
96+
private String toString(ByteBuf buf) {
97+
if (level == InternalLogLevel.TRACE || buf.readableBytes() <= BUFFER_LENGTH_THRESHOLD) {
98+
// Log the entire buffer.
99+
return ByteBufUtil.hexDump(buf);
100+
}
101+
102+
// Otherwise just log the first 64 bytes.
103+
int length = Math.min(buf.readableBytes(), BUFFER_LENGTH_THRESHOLD);
104+
return ByteBufUtil.hexDump(buf, buf.readerIndex(), length) + "...";
105+
}
106+
}

vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3ServerConnection.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import io.vertx.core.internal.ContextInternal;
2424
import io.vertx.core.internal.quic.QuicConnectionInternal;
2525
import io.vertx.core.internal.quic.QuicStreamInternal;
26+
import io.vertx.core.net.QuicStream;
27+
import io.vertx.core.net.impl.VertxConnection;
28+
import io.vertx.core.net.impl.VertxHandler;
2629
import io.vertx.core.spi.metrics.HttpServerMetrics;
2730
import io.vertx.core.spi.tracing.VertxTracer;
2831
import io.vertx.core.tracing.TracingPolicy;
@@ -37,18 +40,18 @@ public class Http3ServerConnection extends Http3Connection implements HttpServer
3740
private final Supplier<ContextInternal> streamContextProvider;
3841
private final HttpServerMetrics<?, ?> httpMetrics;
3942
private Handler<HttpServerStream> streamHandler;
40-
private QuicStreamChannel outboundControlStream;
4143

4244
public Http3ServerConnection(QuicConnectionInternal connection,
4345
Http3Settings localSettings,
44-
HttpServerMetrics<?, ?> httpMetrics) {
45-
super(connection, localSettings);
46+
HttpServerMetrics<?, ?> httpMetrics,
47+
Http3FrameLogger frameLogger) {
48+
super(connection, localSettings, frameLogger);
4649

4750
this.streamContextProvider = connection.context()::duplicate;
4851
this.httpMetrics = httpMetrics;
4952
}
5053

51-
void handleStream(QuicStreamInternal quicStream) {
54+
void handleRequestStream(QuicStreamInternal quicStream) {
5255
ContextInternal streamContext = streamContextProvider.get();
5356
VertxTracer<?, ?> tracer = context.owner().tracer();
5457
ServerStreamObserver observer;
@@ -63,12 +66,11 @@ void handleStream(QuicStreamInternal quicStream) {
6366
registerStream(httpStream);
6467
Handler<HttpServerStream> handler = streamHandler;
6568
streamContext.emit(httpStream, handler);
69+
super.handleRequestStream(quicStream);
6670
}
6771

6872
public void init() {
6973

70-
super.init();
71-
7274
io.netty.handler.codec.http3.Http3Settings nSettings = nettyLocalSettings();
7375

7476
Http3ServerConnectionHandler http3Handler = new Http3ServerConnectionHandler(
@@ -81,7 +83,7 @@ protected void initChannel(QuicStreamChannel ch) {
8183
new ChannelInitializer<QuicStreamChannel>() {
8284
@Override
8385
protected void initChannel(QuicStreamChannel ch) {
84-
outboundControlStream = ch;
86+
// Nothing to do
8587
}
8688
},
8789
null,
@@ -91,6 +93,8 @@ protected void initChannel(QuicStreamChannel ch) {
9193

9294
ChannelPipeline pipeline = connection.channelHandlerContext().pipeline();
9395
pipeline.addBefore("handler", "http3", http3Handler);
96+
97+
super.init();
9498
}
9599

96100
@Override

vertx-core/src/main/java/io/vertx/core/http/impl/http3/Http3Stream.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ void init() {
212212
stream.shutdownHandler(v -> {
213213
// Not used at the moment
214214
});
215+
216+
connection.setupFrameLogger(stream);
215217
}
216218

217219
public final S customFrameHandler(Handler<HttpFrame> handler) {

vertx-core/src/main/java/io/vertx/core/http/impl/quic/QuicHttpClientTransport.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
package io.vertx.core.http.impl.quic;
1212

1313
import io.netty.handler.codec.http3.Http3;
14+
import io.netty.util.internal.logging.InternalLogLevel;
1415
import io.vertx.core.Future;
1516
import io.vertx.core.http.Http3ClientConfig;
1617
import io.vertx.core.http.Http3Settings;
1718
import io.vertx.core.http.HttpClientConfig;
1819
import io.vertx.core.http.impl.HttpClientConnection;
1920
import io.vertx.core.http.impl.HttpConnectParams;
2021
import io.vertx.core.http.impl.http3.Http3ClientConnection;
22+
import io.vertx.core.http.impl.http3.Http3FrameLogger;
2123
import io.vertx.core.internal.ContextInternal;
2224
import io.vertx.core.internal.VertxInternal;
2325
import io.vertx.core.internal.http.HttpClientTransport;
@@ -38,6 +40,7 @@ public class QuicHttpClientTransport implements HttpClientTransport {
3840
private final QuicClient client;
3941
private final long keepAliveTimeoutMillis;
4042
private final Http3Settings localSettings;
43+
private final Http3FrameLogger frameLogger;
4144

4245
public QuicHttpClientTransport(VertxInternal vertx, HttpClientConfig config) {
4346

@@ -54,12 +57,16 @@ public QuicHttpClientTransport(VertxInternal vertx, HttpClientConfig config) {
5457
localSettings = new Http3Settings();
5558
}
5659

60+
boolean logEnabled = quicConfig.getNetworkLogging() != null && quicConfig.getNetworkLogging().isEnabled();
61+
quicConfig.setNetworkLogging(null);
62+
5763
QuicClient client = new QuicClientImpl(vertx, quicConfig, "http", null);
5864

5965
this.vertx = vertx;
6066
this.keepAliveTimeoutMillis = http3Config.getKeepAliveTimeout() == null ? 0L : http3Config.getKeepAliveTimeout().toMillis();
6167
this.localSettings = localSettings;
6268
this.client = client;
69+
this.frameLogger = logEnabled ? new Http3FrameLogger(InternalLogLevel.DEBUG) : null;
6370
}
6471

6572
public QuicClientImpl client() {
@@ -86,7 +93,8 @@ public Future<HttpClientConnection> connect(ContextInternal context, SocketAddre
8693
authority,
8794
(ClientMetrics<Object, HttpRequest, HttpResponse>) clientMetrics,
8895
keepAliveTimeoutMillis,
89-
localSettings);
96+
localSettings,
97+
frameLogger);
9098
c.init();
9199
return c;
92100
});

0 commit comments

Comments
 (0)