package hello;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.MustacheFactory;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.net.MediaType;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.UndertowOptions;
import io.undertow.util.Headers;
import org.xnio.Options;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* An implementation of the TechEmpower benchmark tests using the Undertow web
* server. The only test that truly exercises Undertow in isolation is the
* plaintext test. For the rest, it uses best-of-breed components that are
* expected to perform well. The idea is that using these components enables
* these tests to serve as performance baselines for hypothetical, Undertow-based
* frameworks. For instance, it is unlikely that such frameworks would complete
* the JSON test faster than this will, because this implementation uses
* Undertow and Jackson in the most direct way possible to fulfill the test
* requirements.
*/
public final class HelloWebServer {
//MediaType.toString() does non-trivial work and does not cache the result
//so we cache it here
public static final String JSON_UTF8 = MediaType.JSON_UTF_8.toString();
public static final String TEXT_PLAIN = MediaType.PLAIN_TEXT_UTF_8.toString();
public static final String HTML_UTF8 = MediaType.HTML_UTF_8.toString();
public static void main(String[] args) throws Exception {
new HelloWebServer();
}
/**
* Creates and starts a new web server whose configuration is specified in the
* {@code server.properties} file.
*
* @throws IOException if the application properties file cannot be read or
* the Mongo database hostname cannot be resolved
* @throws SQLException if reading from the SQL database (while priming the
* cache) fails
*/
public HelloWebServer() throws ClassNotFoundException, IOException, SQLException {
Class.forName("org.postgresql.Driver");
Properties properties = new Properties();
try (InputStream in = HelloWebServer.class.getResourceAsStream(
"server.properties")) {
properties.load(in);
}
final ObjectMapper objectMapper = new ObjectMapper();
final MustacheFactory mustacheFactory = new DefaultMustacheFactory();
final DataSource mysql = Helper.newDataSource(
properties.getProperty("mysql.uri"),
properties.getProperty("mysql.user"),
properties.getProperty("mysql.password"));
final DataSource postgresql = Helper.newDataSource(
properties.getProperty("postgresql.uri"),
properties.getProperty("postgresql.user"),
properties.getProperty("postgresql.password"));
final DB mongodb = new MongoClient(properties.getProperty("mongodb.host"))
.getDB(properties.getProperty("mongodb.name"));
//
// The world cache is primed at startup with all values. It doesn't
// matter which database backs it; they all contain the same information
// and the CacheLoader.load implementation below is never invoked.
//
final LoadingCache<Integer, World> worldCache = CacheBuilder.newBuilder()
.build(new CacheLoader<Integer, World>() {
@Override
public World load(Integer id) throws Exception {
try (Connection connection = mysql.getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM World WHERE id = ?",
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY)) {
statement.setInt(1, id);
try (ResultSet resultSet = statement.executeQuery()) {
resultSet.next();
return new World(
resultSet.getInt("id"),
resultSet.getInt("randomNumber"));
}
}
}
});
try (Connection connection = mysql.getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM World",
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
World world = new World(
resultSet.getInt("id"),
resultSet.getInt("randomNumber"));
worldCache.put(world.id, world);
}
}
Undertow.builder()
.addHttpListener(
Integer.parseInt(properties.getProperty("web.port")),
properties.getProperty("web.host"))
.setBufferSize(1024 * 16)
.setIoThreads(Runtime.getRuntime().availableProcessors() * 2) //this seems slightly faster in some configurations
.setSocketOption(Options.BACKLOG, 10000)
.setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) //don't send a keep-alive header for HTTP/1.1 requests, as it is not required
.setServerOption(UndertowOptions.ALWAYS_SET_DATE, true)
.setHandler(Handlers.header(Handlers.path()
.addPrefixPath("/json",
new JsonHandler(objectMapper))
.addPrefixPath("/db/mysql",
new DbSqlHandler(objectMapper, mysql, false))
.addPrefixPath("/queries/mysql",
new DbSqlHandler(objectMapper, mysql, true))
.addPrefixPath("/db/postgresql",
new DbSqlHandler(objectMapper, postgresql, false))
.addPrefixPath("/queries/postgresql",
new DbSqlHandler(objectMapper, postgresql, true))
.addPrefixPath("/db/mongodb",
new DbMongoHandler(objectMapper, mongodb, false))
.addPrefixPath("/queries/mongodb",
new DbMongoHandler(objectMapper, mongodb, true))
.addPrefixPath("/fortunes/mysql",
new FortunesSqlHandler(mustacheFactory, mysql))
.addPrefixPath("/fortunes/postgresql",
new FortunesSqlHandler(mustacheFactory, postgresql))
.addPrefixPath("/fortunes/mongodb",
new FortunesMongoHandler(mustacheFactory, mongodb))
.addPrefixPath("/updates/mysql",
new UpdatesSqlHandler(objectMapper, mysql))
.addPrefixPath("/updates/postgresql",
new UpdatesSqlHandler(objectMapper, postgresql))
.addPrefixPath("/updates/mongodb",
new UpdatesMongoHandler(objectMapper, mongodb))
.addPrefixPath("/plaintext",
new PlaintextHandler())
.addPrefixPath("/cache",
new CacheHandler(objectMapper, worldCache)),
Headers.SERVER_STRING, "U-tow"))
.setWorkerThreads(200)
.build()
.start();
}
}