This is a simple experiment using a build of Java 16 to create a simple Spark Java application that’s backed by Project Loom’s Virtual Threads as Jetty’s ThreadPool implementation. It is based on prior art
What does it do?
It’s basically a web server whose routes/endpoints are specified with Spark to serve an HTML page and some JavaScript - written inline using Text Blocks!.
What’s needed to run it?
You will need the following dependencies, assuming you use Maven:
<properties>
<jetty.version>9.4.31.v20200723</jetty.version>
<spark.version>2.9.2</spark.version>
<java.version>16</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>
package example;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.ThreadPool;
import spark.Spark;
import spark.embeddedserver.EmbeddedServers;
import spark.embeddedserver.jetty.EmbeddedJettyFactory;
import spark.embeddedserver.jetty.JettyServerFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class SparkWithLoom {
public static void main(String... args) {
// We specify the host and address because Spark#ipAddress and Spark#port
// won't set the host or port in this implementation right now, subject to further research...
EmbeddedJettyFactory factory = createEmbeddedFactory("localhost", 4567);
EmbeddedServers.add(EmbeddedServers.Identifiers.JETTY, factory);
Spark.redirect.any("/", "index.html");
Spark.get("/index.html", (request, response) -> {
response.type("text/html;charset=utf-8");
String htmlPage = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello SparkJava + Project Loom Virtual Threads</title>
</head>
<body>
<div>
<h1 id="awesome-header">Awesome</h1>
<p>This is an HTML page served by SparkJava (Jetty) using Project Loom's Virtual Threads</p>
<p id="hidden-message" style="display: none">
Maybe, you didn't notice but text block was made visible by JavaScript which is being served via <code>/js/generated.js</code> endpoint
and implemented with the following code:
<code><pre>
Spark.get("/js/generated.js", (request, response) -> {
response.type("application/javascript");
return ""\"
document.addEventListener("DOMContentLoaded", (event) => {
console.log("Some JavaScript generated by the server!");
let awEl = document.getElementById("awesome-header");
let hiddenEl = document.getElementById("hidden-message");
awEl.innerHTML = "Java is Awesome!";
hiddenEl.style = "display: block;";
});
""\";
});
</pre></code>
</div>
<script type="text/javascript" src="/js/generated.js"></script>
</body>
</html>
""";
return htmlPage;
});
Spark.get("/js/generated.js", (request, response) -> {
response.type("application/javascript");
return """
document.addEventListener("DOMContentLoaded", (event) => {
console.log("Some JavaScript generated by the server!");
let awEl = document.getElementById("awesome-header");
let hiddenEl = document.getElementById("hidden-message");
awEl.innerHTML = "Java is Awesome!";
hiddenEl.style = "display: block;";
});
""";
});
}
public static EmbeddedJettyFactory createEmbeddedFactory(String ipAddress, int port) {
return new EmbeddedJettyFactory(new JettyServerFactory() {
@Override
public Server create(int i, int i1, int i2) {
var server = new Server(new LoomThreadPool());
ServerConnector connector = new ServerConnector(server);
connector.setHost(ipAddress);
connector.setPort(port);
server.setConnectors(new Connector[]{connector});
// server.setRequestLog(requestLog);
return server;
}
@Override
public Server create(ThreadPool threadPool) {
return create(1, 1, 1);
}
});
}
// Copied from: https://github.com/rodrigovedovato/jetty-loom
public static class LoomThreadPool implements ThreadPool {
ExecutorService executorService = Executors.newVirtualThreadExecutor();
@Override
public void join() throws InterruptedException {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
}
@Override
public int getThreads() {
return 1;
}
@Override
public int getIdleThreads() {
return 1;
}
@Override
public boolean isLowOnThreads() {
return false;
}
@Override
public void execute(Runnable command) {
executorService.submit(command);
}
}
}