Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP 2 Support #1183

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@
<version>${jetty.version}</version>
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-server</artifactId>
<version>${jetty.version}</version>
</dependency>

<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-server</artifactId>
<version>${jetty.version}</version>
</dependency>

<!-- JUNIT DEPENDENCY FOR TESTING -->
<dependency>
<groupId>junit</groupId>
Expand Down Expand Up @@ -124,6 +136,30 @@
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-conscrypt-server</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-conscrypt-client</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-http-client-transport</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
36 changes: 25 additions & 11 deletions src/main/java/spark/Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,8 @@
*/
package spark;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import spark.embeddedserver.EmbeddedServer;
import spark.embeddedserver.EmbeddedServers;
import spark.embeddedserver.jetty.websocket.WebSocketHandlerClassWrapper;
Expand All @@ -40,6 +30,15 @@
import spark.staticfiles.MimeType;
import spark.staticfiles.StaticFilesConfiguration;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;
import static spark.globalstate.ServletFlag.isRunningFromServlet;

Expand All @@ -62,6 +61,7 @@ public final class Service extends Routable {

protected int port = SPARK_DEFAULT_PORT;
protected String ipAddress = "0.0.0.0";
protected boolean http2Enabled = false;

protected SslStores sslStores;

Expand Down Expand Up @@ -131,6 +131,19 @@ public synchronized Service ipAddress(String ipAddress) {
return this;
}

/**
* Enables HTTP 2
*
* @return the object with HTTP 2 enabled
*/
public synchronized Service http2() {
if (initialized) {
throwBeforeRouteMappingException();
}
this.http2Enabled = true;
return this;
}

/**
* Set the port that Spark should listen on. If not called the default port
* is 4567. This has to be called before any route mapping is done.
Expand Down Expand Up @@ -577,7 +590,8 @@ public synchronized void init() {
sslStores,
maxThreads,
minThreads,
threadIdleTimeoutMillis);
threadIdleTimeoutMillis,
http2Enabled);
} catch (Exception e) {
initExceptionHandler.accept(e);
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/spark/Spark.java
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,13 @@ public static void ipAddress(String ipAddress) {
getInstance().ipAddress(ipAddress);
}

/**
* Enables HTTP 2
*/
public static void http2() {
getInstance().http2();
}

/**
* Set the default response transformer. All requests not using a custom transformer will use this one
*
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/spark/embeddedserver/EmbeddedServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
*/
package spark.embeddedserver;

import java.util.Map;
import java.util.Optional;

import spark.embeddedserver.jetty.websocket.WebSocketHandlerWrapper;
import spark.ssl.SslStores;

import java.util.Map;
import java.util.Optional;

/**
* Represents an embedded server that can be used in Spark. (this is currently Jetty by default).
*/
Expand All @@ -37,14 +37,16 @@ public interface EmbeddedServer {
* @param maxThreads - max nbr of threads.
* @param minThreads - min nbr of threads.
* @param threadIdleTimeoutMillis - idle timeout (ms).
* @param http2Enabled - whether http2 is enabled or not.
* @return The port number the server was launched on.
*/
int ignite(String host,
int port,
SslStores sslStores,
int maxThreads,
int minThreads,
int threadIdleTimeoutMillis) throws Exception;
int threadIdleTimeoutMillis,
boolean http2Enabled) throws Exception;

/**
* Configures the web sockets for the embedded server.
Expand Down
30 changes: 19 additions & 11 deletions src/main/java/spark/embeddedserver/jetty/EmbeddedJettyServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@
*/
package spark.embeddedserver.jetty;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
Expand All @@ -32,12 +25,18 @@
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import spark.embeddedserver.EmbeddedServer;
import spark.embeddedserver.jetty.websocket.WebSocketHandlerWrapper;
import spark.embeddedserver.jetty.websocket.WebSocketServletContextHandlerFactory;
import spark.ssl.SslStores;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Spark server implementation
*
Expand Down Expand Up @@ -82,7 +81,8 @@ public int ignite(String host,
SslStores sslStores,
int maxThreads,
int minThreads,
int threadIdleTimeoutMillis) throws Exception {
int threadIdleTimeoutMillis,
boolean http2Enabled) throws Exception {

boolean hasCustomizedConnectors = false;

Expand All @@ -105,9 +105,17 @@ public int ignite(String host,
ServerConnector connector;

if (sslStores == null) {
connector = SocketConnectorFactory.createSocketConnector(server, host, port);
if (http2Enabled) {
connector = SocketConnectorFactory.createHttp2SocketConnector(server, host, port);
} else {
connector = SocketConnectorFactory.createSocketConnector(server, host, port);
}
} else {
connector = SocketConnectorFactory.createSecureSocketConnector(server, host, port, sslStores);
if(http2Enabled) {
connector = SocketConnectorFactory.createSecureHttp2SocketConnector(server, host, port, sslStores);
} else {
connector = SocketConnectorFactory.createSecureSocketConnector(server, host, port, sslStores);
}
}

Connector previousConnectors[] = server.getConnectors();
Expand Down
123 changes: 99 additions & 24 deletions src/main/java/spark/embeddedserver/jetty/SocketConnectorFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@
*/
package spark.embeddedserver.jetty;

import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http2.HTTP2Cipher;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import spark.ssl.SslStores;
import spark.utils.Assert;

import java.util.concurrent.TimeUnit;

/**
* Creates socket connectors.
*/
Expand All @@ -45,7 +51,8 @@ public static ServerConnector createSocketConnector(Server server, String host,
Assert.notNull(server, "'server' must not be null");
Assert.notNull(host, "'host' must not be null");

HttpConnectionFactory httpConnectionFactory = createHttpConnectionFactory();
HttpConfiguration httpConfiguration = createHttpConfiguration();
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfiguration);
ServerConnector connector = new ServerConnector(server, httpConnectionFactory);
initializeConnector(connector, host, port);
return connector;
Expand All @@ -69,7 +76,93 @@ public static ServerConnector createSecureSocketConnector(Server server,
Assert.notNull(host, "'host' must not be null");
Assert.notNull(sslStores, "'sslStores' must not be null");

SslContextFactory sslContextFactory = new SslContextFactory(sslStores.keystoreFile());
SslContextFactory sslContextFactory = createSslContextFactory(sslStores);

HttpConfiguration httpConfiguration = createHttpConfiguration();
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfiguration);

ServerConnector connector = new ServerConnector(server, sslContextFactory, httpConnectionFactory);
initializeConnector(connector, host, port);
return connector;
}

/**
* Creates an ordinary, non-secured Jetty http2 server.
*
* @param server Jetty server
* @param host host
* @param port port
* @return - a server jetty
*/
public static ServerConnector createHttp2SocketConnector(Server server, String host, int port) {
Assert.notNull(server, "'server' must not be null");
Assert.notNull(host, "'host' must not be null");

HttpConfiguration httpConfiguration = createHttpConfiguration();

HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2CServerConnectionFactory http2c = new HTTP2CServerConnectionFactory(httpConfiguration);

ServerConnector connector = new ServerConnector(server, http1, http2c);
initializeConnector(connector, host, port);
return connector;
}

/**
* Creates a ssl http2 jetty socket jetty. Keystore required, truststore
* optional. If truststore not specified keystore will be reused.
*
* @param server Jetty server
* @param sslStores the security sslStores.
* @param host host
* @param port port
* @return a ssl socket jetty
*/
public static ServerConnector createSecureHttp2SocketConnector(Server server,
String host,
int port,
SslStores sslStores) {
Assert.notNull(server, "'server' must not be null");
Assert.notNull(host, "'host' must not be null");
Assert.notNull(sslStores, "'sslStores' must not be null");

SslContextFactory sslContextFactory = createSslContextFactory(sslStores);
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
sslContextFactory.setUseCipherSuitesOrder(true);

HttpConfiguration httpConfiguration = createHttpConfiguration();

HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
NegotiatingServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(http1.getProtocol());

SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());

ServerConnector connector = new ServerConnector(server, ssl, alpn, http2, http1);
initializeConnector(connector, host, port);
return connector;
}

private static void initializeConnector(ServerConnector connector, String host, int port) {
// Set some timeout options to make debugging easier.
connector.setIdleTimeout(TimeUnit.HOURS.toMillis(1));
connector.setHost(host);
connector.setPort(port);
}

private static HttpConfiguration createHttpConfiguration() {
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.addCustomizer(new SecureRequestCustomizer());
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
return httpConfig;
}

private static SslContextFactory createSslContextFactory(SslStores sslStores) {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();

sslContextFactory.setKeyStorePath(sslStores.keystoreFile());

if (sslStores.keystorePassword() != null) {
sslContextFactory.setKeyStorePassword(sslStores.keystorePassword());
Expand All @@ -92,25 +185,7 @@ public static ServerConnector createSecureSocketConnector(Server server,
sslContextFactory.setWantClientAuth(true);
}

HttpConnectionFactory httpConnectionFactory = createHttpConnectionFactory();

ServerConnector connector = new ServerConnector(server, sslContextFactory, httpConnectionFactory);
initializeConnector(connector, host, port);
return connector;
}

private static void initializeConnector(ServerConnector connector, String host, int port) {
// Set some timeout options to make debugging easier.
connector.setIdleTimeout(TimeUnit.HOURS.toMillis(1));
connector.setHost(host);
connector.setPort(port);
}

private static HttpConnectionFactory createHttpConnectionFactory() {
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
return new HttpConnectionFactory(httpConfig);
return sslContextFactory;
}

}