Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit f8ea86b

Browse files
committed
More HTTP/2 work
* Implemented for netty, undertow + jetty * Only HTTP/2 over TLS is supported for now * Push Promise implemented in all the servers, but works badly in Netty * This isnt a final commit, still need to cleanup and add unit/integration tests
1 parent 6d3e6cb commit f8ea86b

File tree

26 files changed

+361
-71
lines changed

26 files changed

+361
-71
lines changed

jooby-jetty/src/main/java/org/jooby/internal/jetty/JettyHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void handle(final String target, final Request baseRequest,
7676
multipart = true;
7777
}
7878

79-
ServletServletRequest nreq = new ServletServletRequest(request, tmpdir, multipart)
79+
ServletServletRequest nreq = new JettyRequest(request, tmpdir, multipart)
8080
.with(new ServletUpgrade() {
8181

8282
@SuppressWarnings("unchecked")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.jooby.internal.jetty;
2+
3+
import java.io.IOException;
4+
import java.util.Map;
5+
6+
import javax.servlet.http.HttpServletRequest;
7+
8+
import org.eclipse.jetty.server.Request;
9+
import org.jooby.servlet.ServletServletRequest;
10+
11+
public class JettyRequest extends ServletServletRequest {
12+
13+
public JettyRequest(final HttpServletRequest req, final String tmpdir, final boolean multipart)
14+
throws IOException {
15+
super(req, tmpdir, multipart);
16+
}
17+
18+
@Override
19+
public void push(final String method, final String path, final Map<String, String> headers) {
20+
((Request) servletRequest()).getPushBuilder().method(method).path(path).push();
21+
}
22+
}

jooby-jetty/src/test/java/jetty/H2.java

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package jetty;
2+
3+
import java.nio.charset.StandardCharsets;
4+
5+
import org.jooby.Jooby;
6+
import org.jooby.MediaType;
7+
import org.jooby.Results;
8+
9+
import com.google.common.io.ByteStreams;
10+
11+
import javaslang.Lazy;
12+
import javaslang.control.Try;
13+
14+
public class H2Jetty extends Jooby {
15+
16+
Lazy<String> html = Lazy.of(() -> {
17+
return Try.of(() -> {
18+
byte[] bytes = ByteStreams.toByteArray(getClass().getResourceAsStream("/index.html"));
19+
return new String(bytes, StandardCharsets.UTF_8);
20+
}).get();
21+
});
22+
23+
{
24+
http2();
25+
securePort(8443);
26+
27+
assets("/assets/**");
28+
get("/", req -> {
29+
req.push("/assets/index.js");
30+
return Results.ok(html.get()).type(MediaType.html);
31+
});
32+
33+
}
34+
35+
public static void main(final String[] args) throws Throwable {
36+
run(H2Jetty::new, args);
37+
}
38+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<configuration>
2+
<configuration scan="true" scanPeriod="5 seconds" debug="false">
33
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
44
<encoder>
5-
<pattern>%-5p [%d{ISO8601}] [%thread] %msg%n</pattern>
5+
<pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
66
</encoder>
77
</appender>
88

9-
<root level="INFO">
9+
<logger name="org.eclipse.jetty.http2" level="trace" />
10+
11+
<root level="info">
1012
<appender-ref ref="STDOUT" />
1113
</root>
1214
</configuration>

jooby-netty/src/main/java/org/jooby/internal/netty/NettyHttpAPN.java renamed to jooby-netty/src/main/java/org/jooby/internal/netty/NettyHttp2Handler.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
import io.netty.channel.ChannelHandlerContext;
44
import io.netty.channel.ChannelPipeline;
55
import io.netty.handler.codec.http2.DefaultHttp2Connection;
6+
import io.netty.handler.codec.http2.Http2FrameLogger;
7+
import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandler;
68
import io.netty.handler.codec.http2.HttpToHttp2ConnectionHandlerBuilder;
79
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter;
810
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapterBuilder;
11+
import io.netty.handler.logging.LogLevel;
912
import io.netty.handler.ssl.ApplicationProtocolNames;
1013
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
1114

12-
public class NettyHttpAPN extends ApplicationProtocolNegotiationHandler {
15+
public class NettyHttp2Handler extends ApplicationProtocolNegotiationHandler {
1316

1417
private NettyInitializer initializer;
1518

16-
public NettyHttpAPN(final NettyInitializer initializer) {
19+
public NettyHttp2Handler(final NettyInitializer initializer) {
1720
super(ApplicationProtocolNames.HTTP_1_1);
1821
this.initializer = initializer;
1922
}
@@ -31,9 +34,13 @@ protected void configurePipeline(final ChannelHandlerContext ctx, final String p
3134
.build();
3235

3336
ChannelPipeline pipeline = ctx.pipeline();
34-
pipeline.addLast(new HttpToHttp2ConnectionHandlerBuilder()
37+
HttpToHttp2ConnectionHandler http2handler = new HttpToHttp2ConnectionHandlerBuilder()
3538
.frameListener(listener)
36-
.connection(connection).build());
39+
.frameLogger(new Http2FrameLogger(LogLevel.DEBUG))
40+
.connection(connection)
41+
.build();
42+
ctx.channel().attr(NettyRequest.HTT2).set(http2handler.encoder());
43+
pipeline.addLast(http2handler);
3744
initializer.pipeline(pipeline, false);
3845
} else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
3946
initializer.pipeline(ctx.pipeline(), true);

jooby-netty/src/main/java/org/jooby/internal/netty/NettyInitializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public NettyInitializer(final EventExecutorGroup executor, final HttpHandler han
7474
@Override
7575
protected void initChannel(final SocketChannel ch) throws Exception {
7676
if (http2) {
77-
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new NettyHttpAPN(this));
77+
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new NettyHttp2Handler(this));
7878
} else {
7979
if (sslCtx != null) {
8080
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));

jooby-netty/src/main/java/org/jooby/internal/netty/NettyRequest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Collection;
2828
import java.util.Collections;
2929
import java.util.List;
30+
import java.util.Map;
3031
import java.util.Optional;
3132
import java.util.function.Function;
3233
import java.util.stream.Collectors;
@@ -58,11 +59,19 @@
5859
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
5960
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
6061
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
62+
import io.netty.handler.codec.http2.DefaultHttp2Headers;
63+
import io.netty.handler.codec.http2.Http2Connection;
64+
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
65+
import io.netty.handler.codec.http2.Http2Headers;
66+
import io.netty.handler.codec.http2.HttpConversionUtil;
6167
import io.netty.handler.ssl.SslHandler;
6268
import io.netty.util.AttributeKey;
6369

6470
public class NettyRequest implements NativeRequest {
6571

72+
public static final AttributeKey<Http2ConnectionEncoder> HTT2 = AttributeKey
73+
.newInstance(NettyRequest.class.getName() + ".http2");
74+
6675
public static final AttributeKey<Boolean> NEED_FLUSH = AttributeKey
6776
.newInstance(NettyRequest.class.getName() + ".needFlush");
6877

@@ -219,6 +228,24 @@ public void startAsync() {
219228
ctx.channel().attr(ASYNC).set(true);
220229
}
221230

231+
@Override
232+
public void push(final String method, final String path, final Map<String, String> headers) {
233+
Http2ConnectionEncoder encoder = ctx.channel().attr(HTT2).get();
234+
Http2Connection connection = encoder.connection();
235+
int nextStreamId = connection.local().incrementAndGetNextStreamId();
236+
int streamId = req.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
237+
String autority = header("host").orElse(ip());
238+
String scheme = secure() ? "https" : "http";
239+
Http2Headers h2headers = new DefaultHttp2Headers()
240+
.path(path)
241+
.method(method)
242+
.authority(autority)
243+
.scheme(scheme);
244+
headers.forEach(h2headers::set);
245+
encoder.writePushPromise(ctx, streamId, nextStreamId, h2headers, 0, ctx.newPromise());
246+
ctx.flush();
247+
}
248+
222249
private org.jooby.Cookie cookie(final Cookie c) {
223250
org.jooby.Cookie.Definition cookie = new org.jooby.Cookie.Definition(c.name(), c.value());
224251
Optional.ofNullable(c.domain()).ifPresent(cookie::domain);

jooby-netty/src/main/java/org/jooby/internal/netty/NettyServer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private Channel bootstrap(final EventExecutorGroup executor, final SslContext ss
133133
boolean epoll = bossLoop instanceof EpollEventLoopGroup;
134134
bootstrap.group(bossLoop, workerLoop)
135135
.channel(epoll ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
136-
.handler(new LoggingHandler(Server.class, LogLevel.DEBUG))
136+
.handler(new LoggingHandler(Server.class, LogLevel.INFO))
137137
.childHandler(new NettyInitializer(executor, dispatcher, conf, sslCtx, http2));
138138

139139
configure(conf.getConfig("netty.options"), "netty.options",

jooby-netty/src/test/java/netty/H2.java

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)