-
Notifications
You must be signed in to change notification settings - Fork 380
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
### What changes were proposed in this pull request? This PR proposes to add Jetty server support for Graviton. ### Why are the changes needed? The purpose of introducing Jetty as embedded web server is that: 1. Jetty is a light-weighted web server that can be easily embedded into our project compared to Tomcat and other services. 2. We basically don't want to introduce a bunch of Springboot related code to build our REST API. In that case, Jersey + Jetty would be the best choice. 3. If later on the performance of Jetty cannot meet our requirements, we can shift to use other web servers instead. Fix: #5 ### Does this PR introduce _any_ user-facing change? N/A ### How was this patch tested? Local manual test
- Loading branch information
Showing
7 changed files
with
297 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
plugins { | ||
`maven-publish` | ||
id("java") | ||
id("idea") | ||
id("com.diffplug.spotless") | ||
} | ||
|
||
dependencies { | ||
implementation(project(":core")); | ||
implementation(libs.jackson.databind) | ||
implementation(libs.jackson.annotations) | ||
implementation(libs.jackson.datatype.jdk8) | ||
implementation(libs.jackson.datatype.jsr310) | ||
implementation(libs.guava) | ||
implementation(libs.bundles.log4j) | ||
implementation(libs.bundles.jetty) | ||
implementation(libs.bundles.jersey) | ||
|
||
compileOnly(libs.lombok) | ||
annotationProcessor(libs.lombok) | ||
testCompileOnly(libs.lombok) | ||
testAnnotationProcessor(libs.lombok) | ||
|
||
testImplementation(libs.junit.jupiter.api) | ||
testImplementation(libs.junit.jupiter.params) | ||
testRuntimeOnly(libs.junit.jupiter.engine) | ||
} |
16 changes: 16 additions & 0 deletions
16
server/src/main/java/com/datastrato/graviton/server/GravitonServerException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.datastrato.graviton.server; | ||
|
||
public class GravitonServerException extends RuntimeException { | ||
|
||
public GravitonServerException(String exception) { | ||
super(exception); | ||
} | ||
|
||
public GravitonServerException(String exception, Throwable cause) { | ||
super(exception, cause); | ||
} | ||
|
||
public GravitonServerException(Throwable cause) { | ||
super(cause); | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
server/src/main/java/com/datastrato/graviton/server/ServerConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.datastrato.graviton.server; | ||
|
||
import com.datastrato.graviton.Config; | ||
import com.datastrato.graviton.config.ConfigBuilder; | ||
import com.datastrato.graviton.config.ConfigEntry; | ||
|
||
public class ServerConfig extends Config { | ||
|
||
public static final ConfigEntry<String> WEBSERVER_HOST = | ||
new ConfigBuilder("graviton.server.webserver.host") | ||
.doc("The host name of the built-in web server") | ||
.version("0.1.0") | ||
.stringConf() | ||
.createWithDefault("0.0.0.0"); | ||
|
||
public static final ConfigEntry<Integer> WEBSERVER_HTTP_PORT = | ||
new ConfigBuilder("graviton.server.webserver.httpPort") | ||
.doc("The http port number of the built-in web server") | ||
.version("0.1.0") | ||
.intConf() | ||
.createWithDefault(8090); | ||
|
||
public static final ConfigEntry<Integer> WEBSERVER_CORE_THREADS = | ||
new ConfigBuilder("graviton.server.webserver.coreThreads") | ||
.doc("The core thread size of the built-in web server") | ||
.version("0.1.0") | ||
.intConf() | ||
.createWithDefault(Math.min(Runtime.getRuntime().availableProcessors() * 2, 100)); | ||
|
||
public static final ConfigEntry<Integer> WEBSERVER_MAX_THREADS = | ||
new ConfigBuilder("graviton.server.webserver.maxThreads") | ||
.doc("The max thread size of the built-in web server") | ||
.version("0.1.0") | ||
.intConf() | ||
.createWithDefault(Math.max(Runtime.getRuntime().availableProcessors() * 4, 400)); | ||
|
||
public static final ConfigEntry<Long> WEBSERVER_STOP_IDLE_TIMEOUT = | ||
new ConfigBuilder("graviton.server.webserver.stopIdleTimeout") | ||
.doc("The stop idle timeout of the built-in web server") | ||
.version("0.1.0") | ||
.longConf() | ||
.createWithDefault(30 * 1000L); | ||
|
||
public static final ConfigEntry<Integer> WEBSERVER_REQUEST_HEADER_SIZE = | ||
new ConfigBuilder("graviton.server.webserver.requestHeaderSize") | ||
.doc("The request header size of the built-in web server") | ||
.version("0.1.0") | ||
.intConf() | ||
.createWithDefault(128 * 1024); | ||
|
||
public static final ConfigEntry<Integer> WEBSERVER_RESPONSE_HEADER_SIZE = | ||
new ConfigBuilder("graviton.server.webserver.responseHeaderSize") | ||
.doc("The response header size of the built-in web server") | ||
.version("0.1.0") | ||
.intConf() | ||
.createWithDefault(128 * 1024); | ||
|
||
public ServerConfig(boolean loadDefaults) { | ||
super(loadDefaults); | ||
} | ||
|
||
public ServerConfig() { | ||
this(true); | ||
} | ||
} |
173 changes: 173 additions & 0 deletions
173
server/src/main/java/com/datastrato/graviton/server/web/JettyServer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package com.datastrato.graviton.server.web; | ||
|
||
import com.datastrato.graviton.Config; | ||
import com.datastrato.graviton.server.GravitonServerException; | ||
import com.datastrato.graviton.server.ServerConfig; | ||
import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
import java.net.BindException; | ||
import java.util.concurrent.LinkedBlockingQueue; | ||
import java.util.concurrent.ThreadPoolExecutor; | ||
import java.util.concurrent.TimeUnit; | ||
import javax.servlet.Servlet; | ||
import org.eclipse.jetty.server.*; | ||
import org.eclipse.jetty.server.handler.ErrorHandler; | ||
import org.eclipse.jetty.server.handler.HandlerCollection; | ||
import org.eclipse.jetty.servlet.DefaultServlet; | ||
import org.eclipse.jetty.servlet.ServletContextHandler; | ||
import org.eclipse.jetty.servlet.ServletHolder; | ||
import org.eclipse.jetty.util.component.LifeCycle; | ||
import org.eclipse.jetty.util.thread.*; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public final class JettyServer { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(JettyServer.class); | ||
|
||
private Server server; | ||
|
||
private String host; | ||
|
||
private int httpPort; | ||
|
||
private ServletContextHandler servletContextHandler; | ||
|
||
public JettyServer() {} | ||
|
||
public synchronized void initialize(Config config) { | ||
int coreThreads = config.get(ServerConfig.WEBSERVER_CORE_THREADS); | ||
int maxThreads = config.get(ServerConfig.WEBSERVER_MAX_THREADS); | ||
ExecutorThreadPool threadPool = createThreadPool(coreThreads, maxThreads); | ||
|
||
// Create and config Jetty Server | ||
server = new Server(threadPool); | ||
server.setStopAtShutdown(true); | ||
server.setStopTimeout(config.get(ServerConfig.WEBSERVER_STOP_IDLE_TIMEOUT)); | ||
|
||
// Set error handler for Jetty Server | ||
ErrorHandler errorHandler = new ErrorHandler(); | ||
errorHandler.setShowStacks(true); | ||
errorHandler.setServer(server); | ||
server.addBean(errorHandler); | ||
|
||
// Create and set Http ServerConnector | ||
int reqHeaderSize = config.get(ServerConfig.WEBSERVER_REQUEST_HEADER_SIZE); | ||
int respHeaderSize = config.get(ServerConfig.WEBSERVER_RESPONSE_HEADER_SIZE); | ||
host = config.get(ServerConfig.WEBSERVER_HOST); | ||
httpPort = config.get(ServerConfig.WEBSERVER_HTTP_PORT); | ||
ServerConnector httpConnector = | ||
createHttpServerConnector(server, reqHeaderSize, respHeaderSize, host, httpPort); | ||
server.addConnector(httpConnector); | ||
|
||
// TODO. Create and set https connector @jerry | ||
|
||
// Initialize ServletContextHandler | ||
initializeServletContextHandler(server); | ||
} | ||
|
||
public synchronized void start() throws GravitonServerException { | ||
try { | ||
server.start(); | ||
} catch (BindException e) { | ||
LOG.error( | ||
"Failed to start web server on host {} port {}, which is already in use.", | ||
host, | ||
httpPort, | ||
e); | ||
throw new GravitonServerException("Failed to start web server.", e); | ||
|
||
} catch (Exception e) { | ||
LOG.error("Failed to start web server.", e); | ||
throw new GravitonServerException("Failed to start web server.", e); | ||
} | ||
|
||
LOG.info("Graviton web server started on host {} port {}.", host, httpPort); | ||
} | ||
|
||
public synchronized void join() { | ||
try { | ||
server.join(); | ||
} catch (InterruptedException e) { | ||
LOG.info("Interrupted while web server is joining."); | ||
} | ||
} | ||
|
||
public synchronized void stop() { | ||
if (server != null) { | ||
try { | ||
// Referring from Spark's implementation to avoid the issues. | ||
ThreadPool threadPool = server.getThreadPool(); | ||
if (threadPool instanceof QueuedThreadPool) { | ||
((QueuedThreadPool) threadPool).setStopTimeout(0); | ||
} | ||
|
||
server.stop(); | ||
|
||
if (threadPool instanceof LifeCycle) { | ||
((LifeCycle) threadPool).stop(); | ||
} | ||
|
||
LOG.info("Graviton web server stopped on host {} port {}.", host, httpPort); | ||
} catch (Exception e) { | ||
// Swallow the exception. | ||
LOG.warn("Failed to stop web server.", e); | ||
} | ||
|
||
server = null; | ||
} | ||
} | ||
|
||
public void addServlet(Servlet servlet, String pathSpec) { | ||
servletContextHandler.addServlet(new ServletHolder(servlet), pathSpec); | ||
} | ||
|
||
private void initializeServletContextHandler(Server server) { | ||
this.servletContextHandler = new ServletContextHandler(); | ||
servletContextHandler.setContextPath("/"); | ||
servletContextHandler.addServlet(DefaultServlet.class, "/"); | ||
|
||
HandlerCollection handlers = new HandlerCollection(); | ||
handlers.addHandler(servletContextHandler); | ||
|
||
server.setHandler(handlers); | ||
} | ||
|
||
private ServerConnector createHttpServerConnector( | ||
Server server, int reqHeaderSize, int respHeaderSize, String host, int port) { | ||
HttpConfiguration httpConfig = new HttpConfiguration(); | ||
httpConfig.setRequestHeaderSize(reqHeaderSize); | ||
httpConfig.setResponseHeaderSize(respHeaderSize); | ||
httpConfig.setSendServerVersion(true); | ||
|
||
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig); | ||
ServerConnector connector = | ||
creatorServerConnector(server, new ConnectionFactory[] {httpConnectionFactory}); | ||
connector.setHost(host); | ||
connector.setPort(port); | ||
connector.setReuseAddress(true); | ||
|
||
return connector; | ||
} | ||
|
||
private ServerConnector creatorServerConnector( | ||
Server server, ConnectionFactory[] connectionFactories) { | ||
Scheduler serverExecutor = | ||
new ScheduledExecutorScheduler("graviton-webserver-JettyScheduler", true); | ||
|
||
return new ServerConnector(server, null, serverExecutor, null, -1, -1, connectionFactories); | ||
} | ||
|
||
private ExecutorThreadPool createThreadPool(int coreThreads, int maxThreads) { | ||
return new ExecutorThreadPool( | ||
new ThreadPoolExecutor( | ||
coreThreads, | ||
maxThreads, | ||
60, | ||
TimeUnit.SECONDS, | ||
new LinkedBlockingQueue<>(), | ||
new ThreadFactoryBuilder() | ||
.setDaemon(true) | ||
.setNameFormat("jetty-webserver-%d") | ||
.build())); | ||
} | ||
} |