HelloWebServer.java 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. package hello;
  2. import com.mongodb.MongoClient;
  3. import com.mongodb.MongoClientOptions;
  4. import com.mongodb.ServerAddress;
  5. import com.mongodb.async.client.MongoClientSettings;
  6. import com.mongodb.async.client.MongoClients;
  7. import com.mongodb.client.MongoDatabase;
  8. import com.mongodb.connection.ClusterConnectionMode;
  9. import com.mongodb.connection.ClusterSettings;
  10. import com.mongodb.connection.ConnectionPoolSettings;
  11. import com.zaxxer.hikari.HikariConfig;
  12. import com.zaxxer.hikari.HikariDataSource;
  13. import io.undertow.Undertow;
  14. import io.undertow.UndertowOptions;
  15. import io.undertow.server.HttpHandler;
  16. import io.undertow.server.handlers.BlockingHandler;
  17. import io.undertow.server.handlers.PathHandler;
  18. import io.undertow.server.handlers.SetHeaderHandler;
  19. import java.io.InputStream;
  20. import java.util.List;
  21. import java.util.Properties;
  22. import javax.sql.DataSource;
  23. /**
  24. * Provides the {@link #main(String[])} method, which launches the application.
  25. */
  26. public final class HelloWebServer {
  27. private HelloWebServer() {
  28. throw new AssertionError();
  29. }
  30. public static void main(String[] args) throws Exception {
  31. ServerMode serverMode = ServerMode.valueOf(args[0]);
  32. Properties config = new Properties();
  33. try (InputStream in =
  34. Thread.currentThread()
  35. .getContextClassLoader()
  36. .getResourceAsStream("hello/server.properties")) {
  37. config.load(in);
  38. }
  39. int port = Integer.parseInt(config.getProperty("undertow.port"));
  40. String host = config.getProperty("undertow.host");
  41. HttpHandler pathHandler = serverMode.newPathHandler(config);
  42. HttpHandler rootHandler = new SetHeaderHandler(pathHandler, "Server", "U-tow");
  43. Undertow.builder()
  44. .addHttpListener(port, host)
  45. // In HTTP/1.1, connections are persistent unless declared
  46. // otherwise. Adding a "Connection: keep-alive" header to every
  47. // response would only add useless bytes.
  48. .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, false)
  49. .setHandler(rootHandler)
  50. .build()
  51. .start();
  52. }
  53. enum ServerMode {
  54. /**
  55. * The server will only implement the test types that do not require a
  56. * database.
  57. */
  58. NO_DATABASE {
  59. @Override
  60. HttpHandler newPathHandler(Properties config) {
  61. return new PathHandler()
  62. .addExactPath("/plaintext", new PlaintextHandler())
  63. .addExactPath("/json", new JsonHandler());
  64. }
  65. },
  66. /**
  67. * The server will use a MySQL database and will only implement the test
  68. * types that require a database.
  69. */
  70. MYSQL {
  71. @Override
  72. HttpHandler newPathHandler(Properties config) {
  73. String jdbcUrl = config.getProperty("mysql.jdbcUrl");
  74. String username = config.getProperty("mysql.username");
  75. String password = config.getProperty("mysql.password");
  76. int connections = Integer.parseInt(config.getProperty("mysql.connections"));
  77. DataSource db = newSqlDataSource(jdbcUrl, username, password, connections);
  78. return new PathHandler()
  79. .addExactPath("/db", new BlockingHandler(new DbSqlHandler(db)))
  80. .addExactPath("/queries", new BlockingHandler(new QueriesSqlHandler(db)))
  81. .addExactPath("/fortunes", new BlockingHandler(new FortunesSqlHandler(db)))
  82. .addExactPath("/updates", new BlockingHandler(new UpdatesSqlHandler(db)));
  83. }
  84. },
  85. /**
  86. * The server will use a PostgreSQL database and will only implement the
  87. * test types that require a database.
  88. */
  89. POSTGRESQL {
  90. @Override
  91. HttpHandler newPathHandler(Properties config) {
  92. String jdbcUrl = config.getProperty("postgresql.jdbcUrl");
  93. String username = config.getProperty("postgresql.username");
  94. String password = config.getProperty("postgresql.password");
  95. int connections = Integer.parseInt(config.getProperty("postgresql.connections"));
  96. DataSource db = newSqlDataSource(jdbcUrl, username, password, connections);
  97. return new PathHandler()
  98. .addExactPath("/db", new BlockingHandler(new DbSqlHandler(db)))
  99. .addExactPath("/queries", new BlockingHandler(new QueriesSqlHandler(db)))
  100. .addExactPath("/fortunes", new BlockingHandler(new FortunesSqlHandler(db)))
  101. .addExactPath("/updates", new BlockingHandler(new UpdatesSqlHandler(db)));
  102. }
  103. },
  104. /**
  105. * The server will use a MongoDB database and will only implement the test
  106. * types that require a database.
  107. */
  108. MONGODB {
  109. @Override
  110. HttpHandler newPathHandler(Properties config) {
  111. String host = config.getProperty("mongodb.host");
  112. String databaseName = config.getProperty("mongodb.databaseName");
  113. int connections = Integer.parseInt(config.getProperty("mongodb.connections"));
  114. MongoDatabase db = newMongoDatabase(host, databaseName, connections);
  115. return new PathHandler()
  116. .addExactPath("/db", new BlockingHandler(new DbMongoHandler(db)))
  117. .addExactPath("/queries", new BlockingHandler(new QueriesMongoHandler(db)))
  118. .addExactPath("/fortunes", new BlockingHandler(new FortunesMongoHandler(db)))
  119. .addExactPath("/updates", new BlockingHandler(new UpdatesMongoHandler(db)));
  120. }
  121. },
  122. /**
  123. * The server will use a MongoDB database with an asynchronous API and will
  124. * only implement the test types that require a database.
  125. */
  126. MONGODB_ASYNC {
  127. @Override
  128. HttpHandler newPathHandler(Properties config) {
  129. String host = config.getProperty("mongodb.host");
  130. String databaseName = config.getProperty("mongodb.databaseName");
  131. int connections = Integer.parseInt(config.getProperty("mongodb.connections"));
  132. com.mongodb.async.client.MongoDatabase db =
  133. newMongoDatabaseAsync(host, databaseName, connections);
  134. return new PathHandler()
  135. .addExactPath("/db", new AsyncHandler(new DbMongoAsyncHandler(db)))
  136. .addExactPath("/queries", new AsyncHandler(new QueriesMongoAsyncHandler(db)))
  137. .addExactPath("/fortunes", new AsyncHandler(new FortunesMongoAsyncHandler(db)))
  138. .addExactPath("/updates", new AsyncHandler(new UpdatesMongoAsyncHandler(db)));
  139. }
  140. };
  141. /**
  142. * Returns an HTTP handler that provides routing for all the
  143. * test-type-specific endpoints of the server.
  144. *
  145. * @param config the server configuration
  146. */
  147. abstract HttpHandler newPathHandler(Properties config);
  148. /**
  149. * Provides a source of connections to a SQL database.
  150. */
  151. static DataSource newSqlDataSource(String jdbcUrl,
  152. String username,
  153. String password,
  154. int connections) {
  155. HikariConfig config = new HikariConfig();
  156. config.setJdbcUrl(jdbcUrl);
  157. config.setUsername(username);
  158. config.setPassword(password);
  159. config.setMaximumPoolSize(connections);
  160. // Attempt to work around "FATAL: the database system is starting up"
  161. // error seen in Travis+Docker+PostgreSQL environment.
  162. for (int i = 0;;) {
  163. try {
  164. return new HikariDataSource(config);
  165. } catch (com.zaxxer.hikari.pool.HikariPool.PoolInitializationException e) {
  166. if (++i >= 10) throw e;
  167. try {
  168. Thread.sleep(1000);
  169. } catch (InterruptedException e2) {
  170. Thread.currentThread().interrupt();
  171. }
  172. }
  173. }
  174. }
  175. /**
  176. * Provides a source of connections to a MongoDB database.
  177. */
  178. static MongoDatabase newMongoDatabase(String host,
  179. String databaseName,
  180. int connections) {
  181. MongoClientOptions.Builder options = MongoClientOptions.builder();
  182. options.connectionsPerHost(connections);
  183. options.threadsAllowedToBlockForConnectionMultiplier(
  184. (int) Math.ceil((double) MAX_DB_REQUEST_CONCURRENCY / connections));
  185. MongoClient client = new MongoClient(host, options.build());
  186. return client.getDatabase(databaseName);
  187. }
  188. /**
  189. * Provides a source of connections to a MongoDB database with an
  190. * asynchronous API.
  191. */
  192. static com.mongodb.async.client.MongoDatabase
  193. newMongoDatabaseAsync(String host,
  194. String databaseName,
  195. int connections) {
  196. ClusterSettings clusterSettings =
  197. ClusterSettings
  198. .builder()
  199. .mode(ClusterConnectionMode.SINGLE)
  200. .hosts(List.of(new ServerAddress(host)))
  201. .build();
  202. ConnectionPoolSettings connectionPoolSettings =
  203. ConnectionPoolSettings
  204. .builder()
  205. .maxSize(connections)
  206. .maxWaitQueueSize(
  207. MAX_DB_REQUEST_CONCURRENCY * MAX_DB_QUERIES_PER_REQUEST)
  208. .build();
  209. MongoClientSettings clientSettings =
  210. MongoClientSettings
  211. .builder()
  212. .clusterSettings(clusterSettings)
  213. .connectionPoolSettings(connectionPoolSettings)
  214. .build();
  215. com.mongodb.async.client.MongoClient client =
  216. MongoClients.create(clientSettings);
  217. return client.getDatabase(databaseName);
  218. }
  219. private static final int MAX_DB_REQUEST_CONCURRENCY = 512;
  220. private static final int MAX_DB_QUERIES_PER_REQUEST = 20;
  221. }
  222. }