HelloWebServer.java 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package hello;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import com.github.mustachejava.DefaultMustacheFactory;
  4. import com.github.mustachejava.MustacheFactory;
  5. import com.google.common.cache.CacheBuilder;
  6. import com.google.common.cache.CacheLoader;
  7. import com.google.common.cache.LoadingCache;
  8. import com.google.common.net.MediaType;
  9. import com.mongodb.DB;
  10. import com.mongodb.MongoClient;
  11. import io.undertow.Handlers;
  12. import io.undertow.Undertow;
  13. import io.undertow.UndertowOptions;
  14. import io.undertow.util.Headers;
  15. import org.xnio.Options;
  16. import javax.sql.DataSource;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.sql.Connection;
  20. import java.sql.PreparedStatement;
  21. import java.sql.ResultSet;
  22. import java.sql.SQLException;
  23. import java.util.Properties;
  24. /**
  25. * An implementation of the TechEmpower benchmark tests using the Undertow web
  26. * server. The only test that truly exercises Undertow in isolation is the
  27. * plaintext test. For the rest, it uses best-of-breed components that are
  28. * expected to perform well. The idea is that using these components enables
  29. * these tests to serve as performance baselines for hypothetical, Undertow-based
  30. * frameworks. For instance, it is unlikely that such frameworks would complete
  31. * the JSON test faster than this will, because this implementation uses
  32. * Undertow and Jackson in the most direct way possible to fulfill the test
  33. * requirements.
  34. */
  35. public final class HelloWebServer {
  36. //MediaType.toString() does non-trivial work and does not cache the result
  37. //so we cache it here
  38. public static final String JSON_UTF8 = MediaType.JSON_UTF_8.toString();
  39. public static final String TEXT_PLAIN = MediaType.PLAIN_TEXT_UTF_8.toString();
  40. public static final String HTML_UTF8 = MediaType.HTML_UTF_8.toString();
  41. public static void main(String[] args) throws Exception {
  42. new HelloWebServer();
  43. }
  44. /**
  45. * Creates and starts a new web server whose configuration is specified in the
  46. * {@code server.properties} file.
  47. *
  48. * @throws IOException if the application properties file cannot be read or
  49. * the Mongo database hostname cannot be resolved
  50. * @throws SQLException if reading from the SQL database (while priming the
  51. * cache) fails
  52. */
  53. public HelloWebServer() throws ClassNotFoundException, IOException, SQLException {
  54. Class.forName("org.postgresql.Driver");
  55. Properties properties = new Properties();
  56. try (InputStream in = HelloWebServer.class.getResourceAsStream(
  57. "server.properties")) {
  58. properties.load(in);
  59. }
  60. final ObjectMapper objectMapper = new ObjectMapper();
  61. final MustacheFactory mustacheFactory = new DefaultMustacheFactory();
  62. final DataSource mysql = Helper.newDataSource(
  63. properties.getProperty("mysql.uri"),
  64. properties.getProperty("mysql.user"),
  65. properties.getProperty("mysql.password"));
  66. final DataSource postgresql = Helper.newDataSource(
  67. properties.getProperty("postgresql.uri"),
  68. properties.getProperty("postgresql.user"),
  69. properties.getProperty("postgresql.password"));
  70. final DB mongodb = new MongoClient(properties.getProperty("mongodb.host"))
  71. .getDB(properties.getProperty("mongodb.name"));
  72. //
  73. // The world cache is primed at startup with all values. It doesn't
  74. // matter which database backs it; they all contain the same information
  75. // and the CacheLoader.load implementation below is never invoked.
  76. //
  77. final LoadingCache<Integer, World> worldCache = CacheBuilder.newBuilder()
  78. .build(new CacheLoader<Integer, World>() {
  79. @Override
  80. public World load(Integer id) throws Exception {
  81. try (Connection connection = mysql.getConnection();
  82. PreparedStatement statement = connection.prepareStatement(
  83. "SELECT * FROM World WHERE id = ?",
  84. ResultSet.TYPE_FORWARD_ONLY,
  85. ResultSet.CONCUR_READ_ONLY)) {
  86. statement.setInt(1, id);
  87. try (ResultSet resultSet = statement.executeQuery()) {
  88. resultSet.next();
  89. return new World(
  90. resultSet.getInt("id"),
  91. resultSet.getInt("randomNumber"));
  92. }
  93. }
  94. }
  95. });
  96. try (Connection connection = mysql.getConnection();
  97. PreparedStatement statement = connection.prepareStatement(
  98. "SELECT * FROM World",
  99. ResultSet.TYPE_FORWARD_ONLY,
  100. ResultSet.CONCUR_READ_ONLY);
  101. ResultSet resultSet = statement.executeQuery()) {
  102. while (resultSet.next()) {
  103. World world = new World(
  104. resultSet.getInt("id"),
  105. resultSet.getInt("randomNumber"));
  106. worldCache.put(world.id, world);
  107. }
  108. }
  109. Undertow.builder()
  110. .addHttpListener(
  111. Integer.parseInt(properties.getProperty("web.port")),
  112. properties.getProperty("web.host"))
  113. .setBufferSize(1024 * 16)
  114. .setIoThreads(Runtime.getRuntime().availableProcessors() * 2) //this seems slightly faster in some configurations
  115. .setSocketOption(Options.BACKLOG, 10000)
  116. .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false) //don't send a keep-alive header for HTTP/1.1 requests, as it is not required
  117. .setServerOption(UndertowOptions.ALWAYS_SET_DATE, true)
  118. .setHandler(Handlers.header(Handlers.path()
  119. .addPrefixPath("/json",
  120. new JsonHandler(objectMapper))
  121. .addPrefixPath("/db/mysql",
  122. new DbSqlHandler(objectMapper, mysql, false))
  123. .addPrefixPath("/queries/mysql",
  124. new DbSqlHandler(objectMapper, mysql, true))
  125. .addPrefixPath("/db/postgresql",
  126. new DbSqlHandler(objectMapper, postgresql, false))
  127. .addPrefixPath("/queries/postgresql",
  128. new DbSqlHandler(objectMapper, postgresql, true))
  129. .addPrefixPath("/db/mongodb",
  130. new DbMongoHandler(objectMapper, mongodb, false))
  131. .addPrefixPath("/queries/mongodb",
  132. new DbMongoHandler(objectMapper, mongodb, true))
  133. .addPrefixPath("/fortunes/mysql",
  134. new FortunesSqlHandler(mustacheFactory, mysql))
  135. .addPrefixPath("/fortunes/postgresql",
  136. new FortunesSqlHandler(mustacheFactory, postgresql))
  137. .addPrefixPath("/fortunes/mongodb",
  138. new FortunesMongoHandler(mustacheFactory, mongodb))
  139. .addPrefixPath("/updates/mysql",
  140. new UpdatesSqlHandler(objectMapper, mysql))
  141. .addPrefixPath("/updates/postgresql",
  142. new UpdatesSqlHandler(objectMapper, postgresql))
  143. .addPrefixPath("/updates/mongodb",
  144. new UpdatesMongoHandler(objectMapper, mongodb))
  145. .addPrefixPath("/plaintext",
  146. new PlaintextHandler())
  147. .addPrefixPath("/cache",
  148. new CacheHandler(objectMapper, worldCache)),
  149. Headers.SERVER_STRING, "U-tow"))
  150. .setWorkerThreads(200)
  151. .build()
  152. .start();
  153. }
  154. }