Skip to content

Commit adac055

Browse files
committed
HTTP/2 Support
- Add new/internal extension Http2Configurer - The new extension operates as function or side-effect (depends on web server) - H2, H2C and HTTP 1.1 Upgrade support - No push support yet - Update tests - Update docs Fixes jooby-project#1780
1 parent fa8e9b9 commit adac055

File tree

36 files changed

+1135
-161
lines changed

36 files changed

+1135
-161
lines changed

docs/asciidoc/servers.adoc

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Server options are available via javadoc:ServerOptions[] class:
4747
.setMaxRequestSize(10485760)
4848
.setSecurePort(8433)
4949
.setSsl(SslOptions.selfSigned())
50+
.setHttp2(true)
5051
);
5152
}
5253
----
@@ -67,6 +68,7 @@ Server options are available via javadoc:ServerOptions[] class:
6768
maxRequestSize = 10485760
6869
securePort = 8443
6970
ssl = SslOptions.selfSigned()
71+
http2 = true
7072
}
7173
}
7274
----
@@ -80,8 +82,9 @@ Server options are available via javadoc:ServerOptions[] class:
8082
- singleLoop: Indicates if the web server should use a single loop/group for doing IO or not. **Netty only**.
8183
- defaultHeaders: Configure server to set the following headers: `Date`, `Content-Type` and `Server` headers.
8284
- maxRequestSize: Maximum request size in bytes. Request exceeding this value results in 413(REQUEST_ENTITY_TOO_LARGE) response. Default is `10mb`.
83-
- securePort: Configure Jooby to do HTTPs. This option is fully convered in next section.
84-
- ssl: SSL options with certificate details. This option is fully convered in next section.
85+
- securePort: Enable HTTPS. This option is fully covered in next section.
86+
- ssl: SSL options with certificate details. This option is fully covered in next section.
87+
- http2: Enable HTTP 2.0.
8588

8689
Server options are available as application configuration properties too:
8790

@@ -98,9 +101,10 @@ server.defaultHeaders = true
98101
server.maxRequestSize = 10485760
99102
server.securePort = 8443
100103
server.ssl.type = self-signed
104+
server.http2 = true
101105
----
102106

103-
=== SSL
107+
=== HTTPS Support
104108

105109
Jooby supports HTTPS out of the box. By default HTTPS is disabled and all requests are served using
106110
HTTP. Jooby supports two certificate formats:
@@ -157,13 +161,13 @@ A better option for development is the https://mkcert.dev[mkcert] tool:
157161
.Generates a PKCS12 certificate
158162
[source,bash,role="primary]
159163
----
160-
mkcrt -pkcs12 localhost
164+
mkcert -pkcs12 localhost
161165
----
162166

163167
.Generates a X.509 certificate
164168
[source,bash,role="secondary"]
165169
----
166-
mkcrt localhost
170+
mkcert localhost
167171
----
168172

169173
==== Using X.509
@@ -329,3 +333,58 @@ none of which are supported, an exception will be thrown.
329333
- TLS 1.3 support in OpenJDK is (beside Azul's OpenJSSE) expected to come into 8u272.
330334
- Java 11.0.3 or higher.
331335
====
336+
337+
=== HTTP/2 Support
338+
339+
HTTP2 support is provided across web server implementation. To enable it, you must add one of the
340+
following dependencies:
341+
342+
HTTP/2 with Jetty:
343+
[dependency, artifactId="jooby-http2-jetty"]
344+
.
345+
346+
HTTP/2 with Netty:
347+
[dependency, artifactId="jooby-http2-netty"]
348+
.
349+
350+
HTTP/2 with Undertow:
351+
[dependency, artifactId="jooby-http2-undertow"]
352+
.
353+
354+
Once the required dependencies are added, Jooby automatically configures HTTP/2.
355+
356+
To use HTTP/2 from browsers you need TLS (the h2 protocol) please refer to
357+
<<server-https-support, HTTPS support>> to configure TLS.
358+
359+
.HTTP/2
360+
[source,java,role="primary"]
361+
----
362+
{
363+
setServerOptions(new ServerOptions()
364+
.setSecurePort(8433)
365+
);
366+
367+
get("/", ctx -> {
368+
ctx.getProtocol()
369+
})
370+
}
371+
----
372+
373+
.Kotlin
374+
[source,kotlin,role="secondary"]
375+
----
376+
{
377+
serverOptions {
378+
securePort = 8433
379+
}
380+
381+
get("/") {
382+
ctx.protocol
383+
}
384+
}
385+
----
386+
387+
[NOTE]
388+
====
389+
There is no support for HTTP/2 Push.
390+
====
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby;
7+
8+
/**
9+
* HTTP/2 extension.
10+
*
11+
* @param <ServerInput> Server input.
12+
* @param <ServerOutput> Server output.
13+
*/
14+
public interface Http2Configurer<ServerInput, ServerOutput> {
15+
16+
/**
17+
* True whenever the extension supports the current server.
18+
*
19+
* @param type Server implementation.
20+
* @return True whenever the extension supports the current server.
21+
*/
22+
boolean support(Class type);
23+
24+
/**
25+
* Configure server to support HTTP/2.
26+
*
27+
* @param input Server input.
28+
* @return Output or <code>null</code> for side-effect configurer.
29+
*/
30+
ServerOutput configure(ServerInput input);
31+
}

jooby/src/main/java/io/jooby/ServerOptions.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ public class ServerOptions {
9797

9898
private Integer compressionLevel;
9999

100+
private Boolean http2;
101+
100102
/**
101103
* Creates server options from config object. The configuration options must provided entries
102104
* like: <code>server.port</code>, <code>server.ioThreads</code>, etc...
@@ -142,6 +144,9 @@ public class ServerOptions {
142144
}
143145
// ssl
144146
SslOptions.from(conf, "server.ssl").ifPresent(options::setSsl);
147+
if (conf.hasPath("server.http2")) {
148+
options.setHttp2(conf.getBoolean("server.http2"));
149+
}
145150

146151
return Optional.of(options);
147152
}
@@ -462,6 +467,29 @@ public void setHost(String host) {
462467
return this;
463468
}
464469

470+
/**
471+
* Specify when HTTP/2 is enabled or not. This value is set to <code>null</code>, which allows
472+
* Jooby to enabled by default when dependency is added it.
473+
*
474+
* To turn off set to false.
475+
*
476+
* @return Whenever HTTP/2 is enabled.
477+
*/
478+
public @Nullable Boolean isHttp2() {
479+
return http2;
480+
}
481+
482+
/**
483+
* Turn on/off HTTP/2 support.
484+
*
485+
* @param http2 True to enabled.
486+
* @return This options.
487+
*/
488+
public ServerOptions setHttp2(@Nullable Boolean http2) {
489+
this.http2 = http2;
490+
return this;
491+
}
492+
465493
/**
466494
* Creates SSL context using the given resource loader. This method attempts to create a
467495
* SSLContext when:
@@ -476,16 +504,36 @@ public void setHost(String host) {
476504
* @return SSLContext or <code>null</code> when SSL is disabled.
477505
*/
478506
public @Nullable SSLContext getSSLContext(@Nonnull ClassLoader loader) {
507+
return getSSLContext(loader, null);
508+
}
509+
510+
/**
511+
* Creates SSL context using the given resource loader. This method attempts to create a
512+
* SSLContext when:
513+
*
514+
* - {@link #getSecurePort()} has been set; or
515+
* - {@link #getSsl()} has been set.
516+
*
517+
* If secure port is set and there is no SSL options, this method configure a SSL context using
518+
* the a self-signed certificate for <code>localhost</code>.
519+
*
520+
* @param loader Resource loader.
521+
* @param provider
522+
* @return SSLContext or <code>null</code> when SSL is disabled.
523+
*/
524+
public @Nullable SSLContext getSSLContext(@Nonnull ClassLoader loader,
525+
@Nullable String provider) {
479526
if (isSSLEnabled()) {
480527
setSecurePort(Optional.ofNullable(securePort).orElse(SEVER_SECURE_PORT));
481528
setSsl(Optional.ofNullable(ssl).orElseGet(SslOptions::selfSigned));
482529
SslOptions options = getSsl();
483-
SslContextProvider provider = Stream.of(SslContextProvider.providers())
530+
531+
SslContextProvider sslContextProvider = Stream.of(SslContextProvider.providers())
484532
.filter(it -> it.supports(options.getType()))
485533
.findFirst()
486534
.orElseThrow(
487535
() -> new UnsupportedOperationException("SSL Type: " + options.getType()));
488-
SSLContext sslContext = provider.create(loader, options);
536+
SSLContext sslContext = sslContextProvider.create(loader, provider, options);
489537
// validate TLS protocol, at least one protocol must be supported
490538
Set<String> supportedProtocols = new LinkedHashSet<>(Arrays
491539
.asList(sslContext.getDefaultSSLParameters().getProtocols()));

jooby/src/main/java/io/jooby/SslOptions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ public String getType() {
295295
* @param protocol TLS protocols.
296296
* @return This options.
297297
*/
298-
public SslOptions setProtocol(@Nonnull String... protocol) {
298+
public @Nonnull SslOptions setProtocol(@Nonnull String... protocol) {
299299
return setProtocol(Arrays.asList(protocol));
300300
}
301301

@@ -307,7 +307,7 @@ public SslOptions setProtocol(@Nonnull String... protocol) {
307307
* @param protocol TLS protocols.
308308
* @return This options.
309309
*/
310-
public SslOptions setProtocol(@Nonnull List<String> protocol) {
310+
public @Nonnull SslOptions setProtocol(@Nonnull List<String> protocol) {
311311
this.protocol = protocol;
312312
return this;
313313
}

jooby/src/main/java/io/jooby/internal/SslContextProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public interface SslContextProvider {
1313

1414
boolean supports(String type);
1515

16-
SSLContext create(ClassLoader loader, SslOptions options);
16+
SSLContext create(ClassLoader loader, String provider, SslOptions options);
1717

1818
static SslContextProvider[] providers() {
1919
return new SslContextProvider[] {new SslPkcs12Provider(), new SslX509Provider()};

jooby/src/main/java/io/jooby/internal/SslPkcs12Provider.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,34 @@
55
*/
66
package io.jooby.internal;
77

8-
import io.jooby.SneakyThrows;
9-
import io.jooby.SslOptions;
8+
import java.io.InputStream;
9+
import java.security.KeyStore;
1010

1111
import javax.net.ssl.KeyManager;
1212
import javax.net.ssl.KeyManagerFactory;
1313
import javax.net.ssl.SSLContext;
1414
import javax.net.ssl.TrustManager;
1515
import javax.net.ssl.TrustManagerFactory;
16-
import java.io.InputStream;
17-
import java.security.KeyStore;
16+
17+
import io.jooby.SneakyThrows;
18+
import io.jooby.SslOptions;
1819

1920
public class SslPkcs12Provider implements SslContextProvider {
2021

2122
@Override public boolean supports(String type) {
2223
return SslOptions.PKCS12.equalsIgnoreCase(type);
2324
}
2425

25-
@Override public SSLContext create(ClassLoader loader, SslOptions options) {
26+
@Override public SSLContext create(ClassLoader loader, String provider, SslOptions options) {
2627
try {
2728
KeyStore store = keystore(options, loader, options.getCert(), options.getPassword());
2829
KeyManagerFactory kmf = KeyManagerFactory
2930
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
3031
kmf.init(store, toCharArray(options.getPassword()));
3132
KeyManager[] kms = kmf.getKeyManagers();
32-
SSLContext context = SSLContext.getInstance("TLS");
33+
SSLContext context = provider == null
34+
? SSLContext.getInstance("TLS")
35+
: SSLContext.getInstance("TLS", provider);
3336

3437
TrustManager[] tms;
3538
if (options.getTrustCert() != null) {

jooby/src/main/java/io/jooby/internal/SslX509Provider.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@
55
*/
66
package io.jooby.internal;
77

8+
import java.io.InputStream;
9+
10+
import javax.net.ssl.SSLContext;
11+
812
import io.jooby.SneakyThrows;
913
import io.jooby.SslOptions;
1014
import io.jooby.internal.x509.SslContext;
1115

12-
import javax.net.ssl.SSLContext;
13-
import java.io.InputStream;
14-
1516
public class SslX509Provider implements SslContextProvider {
1617
@Override public boolean supports(String type) {
1718
return SslOptions.X509.equalsIgnoreCase(type);
1819
}
1920

20-
@Override public SSLContext create(ClassLoader loader, SslOptions options) {
21+
@Override public SSLContext create(ClassLoader loader, String provider, SslOptions options) {
2122
try {
2223
InputStream trustCert;
2324
if (options.getTrustCert() == null) {
@@ -30,7 +31,8 @@ public class SslX509Provider implements SslContextProvider {
3031
String keyStorePass = null;
3132

3233
SSLContext context = SslContext
33-
.newServerContextInternal(trustCert, keyStoreCert, keyStoreKey, keyStorePass, 0, 0)
34+
.newServerContextInternal(provider, trustCert, keyStoreCert, keyStoreKey,
35+
keyStorePass, 0, 0)
3436
.context();
3537

3638
return context;

jooby/src/main/java/io/jooby/internal/x509/JdkSslServerContext.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public final class JdkSslServerContext extends JdkSslContext {
6262
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
6363
* {@code 0} to use the default value.
6464
*/
65-
public JdkSslServerContext(final InputStream trustCertChainFile,
65+
public JdkSslServerContext(final String provider, final InputStream trustCertChainFile,
6666
final InputStream keyCertChainFile, final InputStream keyFile, final String keyPassword,
6767
final long sessionCacheSize, final long sessionTimeout) throws SSLException {
6868

@@ -75,7 +75,10 @@ public JdkSslServerContext(final InputStream trustCertChainFile,
7575
keyPassword);
7676

7777
// Initialize the SSLContext to work with our key managers.
78-
ctx = SSLContext.getInstance(PROTOCOL);
78+
ctx = provider == null
79+
? SSLContext.getInstance(PROTOCOL)
80+
: SSLContext.getInstance(PROTOCOL, provider);
81+
7982
ctx.init(keyManagerFactory.getKeyManagers(),
8083
trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(),
8184
null);

jooby/src/main/java/io/jooby/internal/x509/SslContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ public abstract class SslContext {
8686
}
8787
}
8888

89-
public static SslContext newServerContextInternal(
89+
public static SslContext newServerContextInternal(final String provider,
9090
final InputStream trustCertChainFile,
9191
final InputStream keyCertChainFile, final InputStream keyFile, final String keyPassword,
9292
final long sessionCacheSize, final long sessionTimeout) throws SSLException {
93-
return new JdkSslServerContext(trustCertChainFile, keyCertChainFile,
93+
return new JdkSslServerContext(provider, trustCertChainFile, keyCertChainFile,
9494
keyFile, keyPassword, sessionCacheSize, sessionTimeout);
9595
}
9696

0 commit comments

Comments
 (0)