Skip to content

Commit fdcb171

Browse files
authored
Optimize http headers creation (#9814)
* Optimize http headers creation * Further optimize encoding * Ensure enough space in dst buffer * Stop the scheduled tasks on disconnect
1 parent c22863e commit fdcb171

File tree

6 files changed

+90
-47
lines changed

6 files changed

+90
-47
lines changed

frameworks/Java/netty/src/main/java/hello/HelloServerHandler.java

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package hello;
22

3-
import static hello.HttpResponses.makeJsonResponse;
4-
import static hello.HttpResponses.makePlaintextResponse;
3+
import static hello.HttpResponses.*;
54
import static hello.JsonUtils.acquireJsonStreamFromEventLoop;
65
import static hello.JsonUtils.releaseJsonStreamFromEventLoop;
76
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
@@ -11,6 +10,7 @@
1110
import java.text.SimpleDateFormat;
1211
import java.util.Date;
1312
import java.util.concurrent.ScheduledExecutorService;
13+
import java.util.concurrent.ScheduledFuture;
1414
import java.util.concurrent.TimeUnit;
1515

1616
import com.jsoniter.output.JsonStream;
@@ -19,11 +19,7 @@
1919
import io.netty.channel.ChannelFutureListener;
2020
import io.netty.channel.ChannelHandlerContext;
2121
import io.netty.channel.ChannelInboundHandlerAdapter;
22-
import io.netty.handler.codec.http.DefaultFullHttpResponse;
23-
import io.netty.handler.codec.http.DefaultHttpRequest;
24-
import io.netty.handler.codec.http.FullHttpResponse;
25-
import io.netty.handler.codec.http.HttpRequest;
26-
import io.netty.handler.codec.http.LastHttpContent;
22+
import io.netty.handler.codec.http.*;
2723
import io.netty.util.AsciiString;
2824
import io.netty.util.ReferenceCountUtil;
2925
import io.netty.util.concurrent.FastThreadLocal;
@@ -37,15 +33,19 @@ protected DateFormat initialValue() {
3733
}
3834
};
3935

40-
protected volatile AsciiString date = new AsciiString(FORMAT.get().format(new Date()));
36+
private HttpHeaders jsonHeaders = makeJsonHeaders(new AsciiString(FORMAT.get().format(new Date())));
37+
private HttpHeaders plaintextHeaders = makePlaintextHeaders(new AsciiString(FORMAT.get().format(new Date())));
38+
private ScheduledFuture<?> refreshHeaders;
4139

4240
public HelloServerHandler(ScheduledExecutorService service) {
43-
service.scheduleWithFixedDelay(new Runnable() {
41+
refreshHeaders = service.scheduleWithFixedDelay(new Runnable() {
4442
private final DateFormat format = FORMAT.get();
4543

4644
@Override
4745
public void run() {
48-
date = new AsciiString(format.format(new Date()));
46+
var date = new AsciiString(format.format(new Date()));
47+
jsonHeaders = makeJsonHeaders(date);
48+
plaintextHeaders = makePlaintextHeaders(date);
4949
}
5050
}, 1000, 1000, TimeUnit.MILLISECONDS);
5151
}
@@ -80,13 +80,13 @@ private void process(ChannelHandlerContext ctx, HttpRequest request) throws Exce
8080
String uri = request.uri();
8181
switch (uri) {
8282
case "/plaintext":
83-
writePlainResponse(ctx, date);
83+
writePlainResponse(ctx, plaintextHeaders);
8484
return;
8585
case "/json":
8686
// even for the virtual thread case we expect virtual threads to be executed inlined!
8787
var stream = acquireJsonStreamFromEventLoop();
8888
try {
89-
writeJsonResponse(ctx, stream, date);
89+
writeJsonResponse(ctx, stream, jsonHeaders);
9090
} finally {
9191
releaseJsonStreamFromEventLoop(stream);
9292
}
@@ -98,12 +98,12 @@ private void process(ChannelHandlerContext ctx, HttpRequest request) throws Exce
9898
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
9999
}
100100

101-
protected void writePlainResponse(ChannelHandlerContext ctx, AsciiString date) {
102-
ctx.write(makePlaintextResponse(date), ctx.voidPromise());
101+
protected void writePlainResponse(ChannelHandlerContext ctx, HttpHeaders plaintextHeaders) {
102+
ctx.write(makePlaintextResponse(plaintextHeaders), ctx.voidPromise());
103103
}
104104

105-
protected void writeJsonResponse(ChannelHandlerContext ctx, JsonStream stream, AsciiString date) {
106-
ctx.write(makeJsonResponse(stream, date), ctx.voidPromise());
105+
protected void writeJsonResponse(ChannelHandlerContext ctx, JsonStream stream, HttpHeaders jsonHeaders) {
106+
ctx.write(makeJsonResponse(stream, jsonHeaders), ctx.voidPromise());
107107
}
108108

109109
@Override
@@ -115,4 +115,13 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
115115
public void channelReadComplete(ChannelHandlerContext ctx) {
116116
ctx.flush();
117117
}
118+
119+
@Override
120+
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
121+
super.channelInactive(ctx);
122+
if (refreshHeaders != null) {
123+
refreshHeaders.cancel(false);
124+
refreshHeaders = null;
125+
}
126+
}
118127
}

frameworks/Java/netty/src/main/java/hello/HelloServerInitializer.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,45 @@
22

33
import java.util.concurrent.ScheduledExecutorService;
44

5+
import io.netty.buffer.ByteBuf;
6+
import io.netty.buffer.Unpooled;
57
import io.netty.channel.ChannelInitializer;
68
import io.netty.channel.socket.SocketChannel;
7-
import io.netty.handler.codec.http.DefaultFullHttpResponse;
8-
import io.netty.handler.codec.http.DefaultHttpRequest;
9-
import io.netty.handler.codec.http.HttpMessage;
10-
import io.netty.handler.codec.http.HttpMethod;
11-
import io.netty.handler.codec.http.HttpRequestDecoder;
12-
import io.netty.handler.codec.http.HttpResponseEncoder;
13-
import io.netty.handler.codec.http.HttpVersion;
9+
import io.netty.handler.codec.http.*;
1410

1511
public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {
1612

17-
protected final ScheduledExecutorService service;
18-
19-
public HelloServerInitializer(ScheduledExecutorService service) {
20-
this.service = service;
13+
public HelloServerInitializer() {
2114
}
2215

2316
@Override
2417
public void initChannel(SocketChannel ch) throws Exception {
2518
ch.pipeline()
2619
.addLast("encoder", new HttpResponseEncoder() {
20+
21+
private ByteBuf encodedHeaders;
22+
private HttpHeaders lastSeenHeaders;
23+
24+
@Override
25+
protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) {
26+
if (lastSeenHeaders != headers) {
27+
updateEncodedHttpHeaders(headers, buf);
28+
}
29+
var encodedHeaders = this.encodedHeaders;
30+
buf.ensureWritable(encodedHeaders.readableBytes());
31+
encodedHeaders.getBytes(encodedHeaders.readerIndex(), buf, encodedHeaders.readableBytes());
32+
}
33+
34+
private void updateEncodedHttpHeaders(HttpHeaders headers, ByteBuf buf) {
35+
if (encodedHeaders == null) {
36+
encodedHeaders = Unpooled.buffer(buf.writableBytes());
37+
} else {
38+
encodedHeaders.clear().ensureWritable(buf.writableBytes());
39+
}
40+
super.encodeHeaders(headers, encodedHeaders);
41+
lastSeenHeaders = headers;
42+
}
43+
2744
@Override
2845
public boolean acceptOutboundMessage(final Object msg) throws Exception {
2946
if (msg.getClass() == DefaultFullHttpResponse.class) {
@@ -46,7 +63,7 @@ protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
4663
return false;
4764
}
4865
})
49-
.addLast("handler", newHelloServerHandler(service));
66+
.addLast("handler", newHelloServerHandler(ch.eventLoop()));
5067
}
5168

5269
protected HelloServerHandler newHelloServerHandler(ScheduledExecutorService service) {

frameworks/Java/netty/src/main/java/hello/HelloWebServer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ public void run() throws Exception {
6767
}
6868
var channelB = b.group(group).channel(serverChannelClass);
6969
if (EVENT_LOOP_CARRIER) {
70-
channelB.childHandler(new HelloLoomServerInitializer((MultithreadVirtualEventExecutorGroup) group, group.next()));
70+
channelB.childHandler(new HelloLoomServerInitializer((MultithreadVirtualEventExecutorGroup) group));
7171
} else {
72-
channelB.childHandler(new HelloServerInitializer(group.next()));
72+
channelB.childHandler(new HelloServerInitializer());
7373
}
7474
b.childOption(ChannelOption.SO_REUSEADDR, true);
7575

frameworks/Java/netty/src/main/java/hello/HttpResponses.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,43 @@
2020
import io.netty.buffer.ByteBuf;
2121
import io.netty.buffer.Unpooled;
2222
import io.netty.handler.codec.http.DefaultFullHttpResponse;
23+
import io.netty.handler.codec.http.DefaultHttpHeadersFactory;
2324
import io.netty.handler.codec.http.FullHttpResponse;
25+
import io.netty.handler.codec.http.HttpHeaders;
26+
import io.netty.handler.codec.http.HttpHeadersFactory;
2427
import io.netty.util.AsciiString;
2528

2629
public class HttpResponses {
2730

28-
public static FullHttpResponse makePlaintextResponse(AsciiString date) {
29-
return makeResponse(Unpooled.wrappedBuffer(STATIC_PLAINTEXT), TEXT_PLAIN, PLAINTEXT_CLHEADER_VALUE, date);
30-
}
31+
private static final HttpHeadersFactory HEADERS_FACTORY = DefaultHttpHeadersFactory.headersFactory()
32+
.withValidation(false);
3133

32-
public static FullHttpResponse makeJsonResponse(JsonStream stream, AsciiString date) {
33-
return makeResponse(Unpooled.wrappedBuffer(serializeMsg(newMsg(), stream)), APPLICATION_JSON, JSON_CLHEADER_VALUE, date);
34+
public static HttpHeaders makeJsonHeaders(AsciiString date) {
35+
return HEADERS_FACTORY.newHeaders()
36+
.set(CONTENT_TYPE, APPLICATION_JSON)
37+
.set(SERVER, SERVER_NAME)
38+
.set(DATE, date)
39+
.set(CONTENT_LENGTH, JSON_CLHEADER_VALUE);
3440
}
3541

36-
private static FullHttpResponse makeResponse(ByteBuf buf, CharSequence contentType, CharSequence contentLength, AsciiString date) {
37-
final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, buf, false);
38-
response.headers()
39-
.set(CONTENT_TYPE, contentType)
42+
43+
public static HttpHeaders makePlaintextHeaders(AsciiString date) {
44+
return HEADERS_FACTORY.newHeaders()
45+
.set(CONTENT_TYPE, TEXT_PLAIN)
4046
.set(SERVER, SERVER_NAME)
4147
.set(DATE, date)
42-
.set(CONTENT_LENGTH, contentLength);
43-
return response;
48+
.set(CONTENT_LENGTH, PLAINTEXT_CLHEADER_VALUE);
49+
}
50+
51+
public static FullHttpResponse makePlaintextResponse(HttpHeaders plaintextHeaders) {
52+
return makeResponse(Unpooled.wrappedBuffer(STATIC_PLAINTEXT), plaintextHeaders);
53+
}
54+
55+
public static FullHttpResponse makeJsonResponse(JsonStream stream, HttpHeaders jsonHeaders) {
56+
return makeResponse(Unpooled.wrappedBuffer(serializeMsg(newMsg(), stream)), jsonHeaders);
57+
}
58+
59+
private static FullHttpResponse makeResponse(ByteBuf buf, HttpHeaders headers) {
60+
return new DefaultFullHttpResponse(HTTP_1_1, OK, buf, headers, HttpHeaders.EMPTY_HEADERS);
4461
}
4562
}

frameworks/Java/netty/src/main/java/hello/loom/HelloLoomServerInitializer.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ public class HelloLoomServerInitializer extends HelloServerInitializer {
99

1010
private final MultithreadVirtualEventExecutorGroup group;
1111

12-
public HelloLoomServerInitializer(MultithreadVirtualEventExecutorGroup group, ScheduledExecutorService service) {
13-
super(service);
12+
public HelloLoomServerInitializer(MultithreadVirtualEventExecutorGroup group) {
1413
this.group = group;
1514
}
1615

frameworks/Java/netty/src/main/java/hello/loom/VirtualThreadHelloServerHandler.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import hello.HttpResponses;
1010
import io.netty.channel.ChannelHandlerContext;
1111
import io.netty.handler.codec.http.FullHttpResponse;
12+
import io.netty.handler.codec.http.HttpHeaders;
1213
import io.netty.util.AsciiString;
1314

1415
public class VirtualThreadHelloServerHandler extends HelloServerHandler {
@@ -22,16 +23,16 @@ public VirtualThreadHelloServerHandler(ScheduledExecutorService service, Multith
2223
}
2324

2425
@Override
25-
protected void writePlainResponse(ChannelHandlerContext ctx, AsciiString date) {
26+
protected void writePlainResponse(ChannelHandlerContext ctx, HttpHeaders plainTextHeaders) {
2627
group.eventLoopVirtualThreadFactory().newThread(() -> {
27-
responses.add(HttpResponses.makePlaintextResponse(date));
28+
responses.add(HttpResponses.makePlaintextResponse(plainTextHeaders));
2829
}).start();
2930
}
3031

3132
@Override
32-
protected void writeJsonResponse(ChannelHandlerContext ctx, JsonStream stream, AsciiString date) {
33+
protected void writeJsonResponse(ChannelHandlerContext ctx, JsonStream stream, HttpHeaders jsonHeaders) {
3334
group.eventLoopVirtualThreadFactory().newThread(() -> {
34-
responses.add(HttpResponses.makeJsonResponse(stream, date));
35+
responses.add(HttpResponses.makeJsonResponse(stream, jsonHeaders));
3536
}).start();
3637
}
3738

0 commit comments

Comments
 (0)