Skip to content

Commit 997047f

Browse files
committed
Netty HTTP/2 push
1 parent f8ea86b commit 997047f

File tree

30 files changed

+311
-135
lines changed

30 files changed

+311
-135
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.jooby.servlet.ServletServletRequest;
3535
import org.jooby.servlet.ServletUpgrade;
3636
import org.jooby.spi.HttpHandler;
37+
import org.jooby.spi.NativePushPromise;
3738
import org.jooby.spi.NativeWebSocket;
3839
import org.slf4j.Logger;
3940
import org.slf4j.LoggerFactory;
@@ -76,7 +77,7 @@ public void handle(final String target, final Request baseRequest,
7677
multipart = true;
7778
}
7879

79-
ServletServletRequest nreq = new JettyRequest(request, tmpdir, multipart)
80+
ServletServletRequest nreq = new ServletServletRequest(request, tmpdir, multipart)
8081
.with(new ServletUpgrade() {
8182

8283
@SuppressWarnings("unchecked")
@@ -93,6 +94,8 @@ public <T> T upgrade(final Class<T> type) throws Exception {
9394
}
9495
} else if (type == Sse.class) {
9596
return (T) new JettySse(baseRequest, (Response) response);
97+
} else if (type == NativePushPromise.class) {
98+
return (T) new JettyPush(baseRequest);
9699
}
97100
throw new UnsupportedOperationException("Not Supported: " + type);
98101
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.jooby.internal.jetty;
2+
3+
import java.util.Map;
4+
5+
import org.eclipse.jetty.server.PushBuilder;
6+
import org.eclipse.jetty.server.Request;
7+
import org.jooby.spi.NativePushPromise;
8+
9+
public class JettyPush implements NativePushPromise {
10+
11+
private Request req;
12+
13+
public JettyPush(final Request req) {
14+
this.req = req;
15+
}
16+
17+
@Override
18+
public void push(final String method, final String path, final Map<String, String> headers) {
19+
PushBuilder pb = req.getPushBuilder()
20+
.path(path)
21+
.method(method);
22+
headers.forEach(pb::addHeader);
23+
pb.push();
24+
}
25+
26+
}

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

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

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import javax.net.ssl.SSLContext;
3232

3333
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
34+
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
3435
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
3536
import org.eclipse.jetty.server.HttpConfiguration;
3637
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -75,7 +76,7 @@ public JettyServer(final HttpHandler handler, final Config conf,
7576
}
7677

7778
private Server server(final HttpHandler handler, final Config conf,
78-
final Provider<SSLContext> sslCtx) {
79+
final Provider<SSLContext> sslCtx) {
7980
System.setProperty("org.eclipse.jetty.util.UrlEncoded.charset",
8081
conf.getString("jetty.url.charset"));
8182

@@ -89,12 +90,12 @@ private Server server(final HttpHandler handler, final Config conf,
8990
server.setStopAtShutdown(false);
9091

9192
// HTTP connector
92-
ServerConnector http = http(server, conf.getConfig(JETTY_HTTP), JETTY_HTTP);
93+
boolean http2 = conf.getBoolean("server.http2.enabled");
94+
95+
ServerConnector http = http(server, conf.getConfig(JETTY_HTTP), JETTY_HTTP, http2);
9396
http.setPort(conf.getInt("application.port"));
9497
http.setHost(conf.getString("application.host"));
9598

96-
boolean http2 = conf.getBoolean("server.http2.enabled");
97-
9899
if (conf.hasPath("application.securePort")) {
99100

100101
ServerConnector https = https(server, conf.getConfig(JETTY_HTTP), JETTY_HTTP,
@@ -121,13 +122,18 @@ private Server server(final HttpHandler handler, final Config conf,
121122
return server;
122123
}
123124

124-
private ServerConnector http(final Server server, final Config conf, final String path) {
125+
private ServerConnector http(final Server server, final Config conf, final String path,
126+
final boolean http2) {
125127
HttpConfiguration httpConfig = conf(new HttpConfiguration(), conf.withoutPath(CONNECTOR),
126128
path);
127129

128-
HttpConnectionFactory httpFactory = new HttpConnectionFactory(httpConfig);
129-
130-
ServerConnector connector = new ServerConnector(server, httpFactory);
130+
ServerConnector connector;
131+
if (http2) {
132+
connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig),
133+
new HTTP2CServerConnectionFactory(httpConfig));
134+
} else {
135+
connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
136+
}
131137

132138
return conf(connector, conf.getConfig(CONNECTOR), path + "." + CONNECTOR);
133139
}
@@ -140,25 +146,28 @@ private ServerConnector https(final Server server, final Config conf, final Stri
140146

141147
SslContextFactory sslContextFactory = new SslContextFactory();
142148
sslContextFactory.setSslContext(sslContext);
149+
sslContextFactory.setIncludeProtocols("TLSv1.2");
150+
sslContextFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
143151

144152
HttpConfiguration httpsConf = new HttpConfiguration(httpConf);
145153
httpsConf.addCustomizer(new SecureRequestCustomizer());
146154

155+
HttpConnectionFactory https11 = new HttpConnectionFactory(httpsConf);
156+
147157
if (http2) {
148-
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(H2, H2_17);
158+
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(H2, H2_17, HTTP_1_1);
149159
alpn.setDefaultProtocol(HTTP_1_1);
150160

151-
HTTP2ServerConnectionFactory http2Factory = new HTTP2ServerConnectionFactory(httpsConf);
161+
HTTP2ServerConnectionFactory https2 = new HTTP2ServerConnectionFactory(httpsConf);
152162

153163
ServerConnector connector = new ServerConnector(server,
154-
new SslConnectionFactory(sslContextFactory, "alpn"), alpn, http2Factory);
164+
new SslConnectionFactory(sslContextFactory, "alpn"), alpn, https2, https11);
155165

156166
return conf(connector, conf.getConfig(CONNECTOR), path + ".connector");
157167
} else {
158-
HttpConnectionFactory httpsFactory = new HttpConnectionFactory(httpsConf);
159168

160169
ServerConnector connector = new ServerConnector(server,
161-
new SslConnectionFactory(sslContextFactory, HTTP_1_1), httpsFactory);
170+
new SslConnectionFactory(sslContextFactory, HTTP_1_1), https11);
162171

163172
return conf(connector, conf.getConfig(CONNECTOR), path + ".connector");
164173
}

jooby-jetty/src/main/resources/org/jooby/spi/server.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# jetty defaults
22
server.module = org.jooby.jetty.Jetty
33

4+
server.http2.cleartext = true
5+
46
jetty {
57

68
threads {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import org.jooby.Jooby;
66
import org.jooby.MediaType;
77
import org.jooby.Results;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
810

911
import com.google.common.io.ByteStreams;
1012

@@ -13,6 +15,9 @@
1315

1416
public class H2Jetty extends Jooby {
1517

18+
/** The logging system. */
19+
private final Logger log = LoggerFactory.getLogger(getClass());
20+
1621
Lazy<String> html = Lazy.of(() -> {
1722
return Try.of(() -> {
1823
byte[] bytes = ByteStreams.toByteArray(getClass().getResourceAsStream("/index.html"));
@@ -24,6 +29,10 @@ public class H2Jetty extends Jooby {
2429
http2();
2530
securePort(8443);
2631

32+
use("*", (req, rsp) -> {
33+
log.info("************ {} ************", req.path());
34+
});
35+
2736
assets("/assets/**");
2837
get("/", req -> {
2938
req.push("/assets/index.js");

jooby-jetty/src/test/resources/logback.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
</encoder>
77
</appender>
88

9-
<logger name="org.eclipse.jetty.http2" level="trace" />
10-
119
<root level="info">
1210
<appender-ref ref="STDOUT" />
1311
</root>

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,13 @@ public void channelRead0(final ChannelHandlerContext ctx, final Object msg) {
8383
boolean keepAlive = HttpUtil.isKeepAlive(req);
8484

8585
try {
86-
String streamId = req.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
86+
String streamId = req.headers()
87+
.get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
88+
8789
handler.handle(
8890
new NettyRequest(ctx, req, tmpdir, wsMaxMessageSize),
8991
new NettyResponse(ctx, bufferSize, keepAlive, streamId));
92+
9093
} catch (Throwable ex) {
9194
exceptionCaught(ctx, ex);
9295
}

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@
3636

3737
public class NettyInitializer extends ChannelInitializer<SocketChannel> {
3838

39+
// private static class H2 implements UpgradeCodecFactory {
40+
//
41+
// private NettyInitializer initializer;
42+
//
43+
// public H2(final NettyInitializer initializer) {
44+
// this.initializer = initializer;
45+
// }
46+
//
47+
// @Override
48+
// public UpgradeCodec newUpgradeCodec(final CharSequence protocol) {
49+
// if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
50+
// return new Http2ServerUpgradeCodec(new Http2Codec(true, new HelloWorldHttp2Handler()));
51+
// } else {
52+
// return null;
53+
// }
54+
// }
55+
//
56+
// }
57+
3958
private EventExecutorGroup executor;
4059

4160
private HttpHandler handler;
@@ -73,13 +92,21 @@ public NettyInitializer(final EventExecutorGroup executor, final HttpHandler han
7392

7493
@Override
7594
protected void initChannel(final SocketChannel ch) throws Exception {
76-
if (http2) {
77-
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new NettyHttp2Handler(this));
78-
} else {
79-
if (sslCtx != null) {
95+
if (sslCtx != null) {
96+
if (http2) {
97+
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()), new NettyHttp2Handler(this));
98+
} else {
8099
ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
81100
}
101+
} else {
102+
// if (http2) {
103+
// HttpServerCodec sourceCodec = new HttpServerCodec();
104+
// ch.pipeline().addLast(sourceCodec);
105+
// ch.pipeline()
106+
// .addLast(new HttpServerUpgradeHandler(sourceCodec, new H2(this), Integer.MAX_VALUE));
107+
// } else {
82108
pipeline(ch.pipeline(), true);
109+
// }
83110
}
84111
}
85112

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.jooby.internal.netty;
2+
3+
import java.util.Map;
4+
5+
import org.jooby.spi.NativePushPromise;
6+
7+
import io.netty.buffer.Unpooled;
8+
import io.netty.channel.ChannelHandlerContext;
9+
import io.netty.handler.codec.http.DefaultFullHttpRequest;
10+
import io.netty.handler.codec.http.DefaultHttpHeaders;
11+
import io.netty.handler.codec.http.EmptyHttpHeaders;
12+
import io.netty.handler.codec.http.HttpMethod;
13+
import io.netty.handler.codec.http.HttpVersion;
14+
import io.netty.handler.codec.http2.DefaultHttp2Headers;
15+
import io.netty.handler.codec.http2.Http2Connection;
16+
import io.netty.handler.codec.http2.Http2ConnectionEncoder;
17+
import io.netty.handler.codec.http2.Http2Headers;
18+
import io.netty.handler.codec.http2.HttpConversionUtil;
19+
import io.netty.util.AsciiString;
20+
21+
public class NettyPush implements NativePushPromise {
22+
23+
private ChannelHandlerContext ctx;
24+
private Http2ConnectionEncoder encoder;
25+
private int streamId;
26+
private String authority;
27+
private String scheme;
28+
29+
public NettyPush(final ChannelHandlerContext ctx, final Http2ConnectionEncoder encoder,
30+
final int streamId, final String authority, final String scheme) {
31+
this.ctx = ctx;
32+
this.encoder = encoder;
33+
this.streamId = streamId;
34+
this.authority = authority;
35+
this.scheme = scheme;
36+
}
37+
38+
@Override
39+
public void push(final String method, final String path, final Map<String, String> headers) {
40+
ctx.channel().eventLoop().execute(() -> {
41+
AsciiString streamIdHeader = HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text();
42+
Http2Connection connection = encoder.connection();
43+
int nextStreamId = connection.local().incrementAndGetNextStreamId();
44+
Http2Headers h2headers = new DefaultHttp2Headers()
45+
.path(path)
46+
.method(method)
47+
.authority(authority)
48+
.scheme(scheme);
49+
headers.forEach(h2headers::set);
50+
encoder.writePushPromise(ctx, streamId, nextStreamId, h2headers, 0, ctx.newPromise());
51+
52+
// TODO: Is there another way of handling a push promise?
53+
DefaultFullHttpRequest pushRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
54+
HttpMethod.valueOf(method.toUpperCase()), path, Unpooled.EMPTY_BUFFER,
55+
new DefaultHttpHeaders(false).set(streamIdHeader, nextStreamId),
56+
EmptyHttpHeaders.INSTANCE);
57+
ctx.pipeline().fireChannelRead(pushRequest);
58+
ctx.pipeline().fireChannelReadComplete();
59+
});
60+
}
61+
62+
}

0 commit comments

Comments
 (0)