App.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. package vertx;
  2. import com.julienviet.pgclient.*;
  3. import io.vertx.core.AbstractVerticle;
  4. import io.vertx.core.DeploymentOptions;
  5. import io.vertx.core.Handler;
  6. import io.vertx.core.MultiMap;
  7. import io.vertx.core.Vertx;
  8. import io.vertx.core.buffer.Buffer;
  9. import io.vertx.core.http.HttpHeaders;
  10. import io.vertx.core.http.HttpServer;
  11. import io.vertx.core.http.HttpServerOptions;
  12. import io.vertx.core.http.HttpServerRequest;
  13. import io.vertx.core.http.HttpServerResponse;
  14. import io.vertx.core.json.Json;
  15. import io.vertx.core.json.JsonArray;
  16. import io.vertx.core.json.JsonObject;
  17. import io.vertx.core.logging.Logger;
  18. import io.vertx.core.logging.LoggerFactory;
  19. import vertx.model.Fortune;
  20. import vertx.model.Message;
  21. import vertx.model.World;
  22. import java.io.File;
  23. import java.nio.file.Files;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.Collections;
  27. import java.util.List;
  28. import java.util.concurrent.ThreadLocalRandom;
  29. public class App extends AbstractVerticle implements Handler<HttpServerRequest> {
  30. /**
  31. * Returns the value of the "queries" getRequest parameter, which is an integer
  32. * bound between 1 and 500 with a default value of 1.
  33. *
  34. * @param request the current HTTP request
  35. * @return the value of the "queries" parameter
  36. */
  37. static int getQueries(HttpServerRequest request) {
  38. String param = request.getParam("queries");
  39. if (param == null) {
  40. return 1;
  41. }
  42. try {
  43. int parsedValue = Integer.parseInt(param);
  44. return Math.min(500, Math.max(1, parsedValue));
  45. } catch (NumberFormatException e) {
  46. return 1;
  47. }
  48. }
  49. static Logger logger = LoggerFactory.getLogger(App.class.getName());
  50. private static final String PATH_PLAINTEXT = "/plaintext";
  51. private static final String PATH_JSON = "/json";
  52. private static final String PATH_DB = "/db";
  53. private static final String PATH_QUERIES = "/queries";
  54. private static final String PATH_UPDATES = "/updates";
  55. private static final String PATH_FORTUNES = "/fortunes";
  56. private static final CharSequence RESPONSE_TYPE_PLAIN = HttpHeaders.createOptimized("text/plain");
  57. private static final CharSequence RESPONSE_TYPE_HTML = HttpHeaders.createOptimized("text/html; charset=UTF-8");
  58. private static final CharSequence RESPONSE_TYPE_JSON = HttpHeaders.createOptimized("application/json");
  59. private static final String HELLO_WORLD = "Hello, world!";
  60. private static final Buffer HELLO_WORLD_BUFFER = Buffer.buffer(HELLO_WORLD);
  61. private static final CharSequence HEADER_SERVER = HttpHeaders.createOptimized("server");
  62. private static final CharSequence HEADER_DATE = HttpHeaders.createOptimized("date");
  63. private static final CharSequence HEADER_CONTENT_TYPE = HttpHeaders.createOptimized("content-type");
  64. private static final CharSequence HEADER_CONTENT_LENGTH = HttpHeaders.createOptimized("content-length");
  65. private static final CharSequence HELLO_WORLD_LENGTH = HttpHeaders.createOptimized("" + HELLO_WORLD.length());
  66. private static final CharSequence SERVER = HttpHeaders.createOptimized("vert.x");
  67. private static final String UPDATE_WORLD = "UPDATE world SET randomnumber=$1 WHERE id=$2";
  68. private static final String SELECT_WORLD = "SELECT id, randomnumber from WORLD where id=$1";
  69. private static final String SELECT_FORTUNE = "SELECT id, message from FORTUNE";
  70. private CharSequence dateString;
  71. private HttpServer server;
  72. private PgClient client;
  73. @Override
  74. public void start() throws Exception {
  75. int port = 8080;
  76. server = vertx.createHttpServer(new HttpServerOptions());
  77. server.requestHandler(App.this).listen(port);
  78. dateString = HttpHeaders.createOptimized(java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.format(java.time.ZonedDateTime.now()));
  79. JsonObject config = config();
  80. vertx.setPeriodic(1000, handler -> {
  81. dateString = HttpHeaders.createOptimized(java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.format(java.time.ZonedDateTime.now()));
  82. });
  83. PgPoolOptions options = new PgPoolOptions();
  84. options.setDatabase(config.getString("database"));
  85. options.setHost(config.getString("host"));
  86. options.setPort(config.getInteger("port", 5432));
  87. options.setUsername(config.getString("username"));
  88. options.setPassword(config.getString("password"));
  89. options.setCachePreparedStatements(true);
  90. options.setMaxSize(1);
  91. client = PgClient.pool(vertx, options);
  92. }
  93. @Override
  94. public void handle(HttpServerRequest request) {
  95. switch (request.path()) {
  96. case PATH_PLAINTEXT:
  97. handlePlainText(request);
  98. break;
  99. case PATH_JSON:
  100. handleJson(request);
  101. break;
  102. case PATH_DB:
  103. handleDb(request);
  104. break;
  105. case PATH_QUERIES:
  106. new Queries().handle(request);
  107. break;
  108. case PATH_UPDATES:
  109. new Update(request).handle();
  110. break;
  111. case PATH_FORTUNES:
  112. handleFortunes(request);
  113. break;
  114. default:
  115. request.response().setStatusCode(404);
  116. request.response().end();
  117. break;
  118. }
  119. }
  120. @Override
  121. public void stop() {
  122. if (server != null) server.close();
  123. }
  124. private void handlePlainText(HttpServerRequest request) {
  125. HttpServerResponse response = request.response();
  126. MultiMap headers = response.headers();
  127. headers
  128. .add(HEADER_CONTENT_TYPE, RESPONSE_TYPE_PLAIN)
  129. .add(HEADER_SERVER, SERVER)
  130. .add(HEADER_DATE, dateString)
  131. .add(HEADER_CONTENT_LENGTH, HELLO_WORLD_LENGTH);
  132. response.end(HELLO_WORLD_BUFFER);
  133. }
  134. private void handleJson(HttpServerRequest request) {
  135. HttpServerResponse response = request.response();
  136. MultiMap headers = response.headers();
  137. headers
  138. .add(HEADER_CONTENT_TYPE, RESPONSE_TYPE_JSON)
  139. .add(HEADER_SERVER, SERVER)
  140. .add(HEADER_DATE, dateString);
  141. response.end(new Message("Hello, World!").toBuffer());
  142. }
  143. /**
  144. * Returns a random integer that is a suitable value for both the {@code id}
  145. * and {@code randomNumber} properties of a world object.
  146. *
  147. * @return a random world number
  148. */
  149. private static int randomWorld() {
  150. return 1 + ThreadLocalRandom.current().nextInt(10000);
  151. }
  152. private void handleDb(HttpServerRequest req) {
  153. HttpServerResponse resp = req.response();
  154. client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), res -> {
  155. if (res.succeeded()) {
  156. PgIterator<Row> resultSet = res.result().iterator();
  157. if (!resultSet.hasNext()) {
  158. resp.setStatusCode(404).end();
  159. return;
  160. }
  161. Tuple row = resultSet.next();
  162. resp
  163. .putHeader(HttpHeaders.SERVER, SERVER)
  164. .putHeader(HttpHeaders.DATE, dateString)
  165. .putHeader(HttpHeaders.CONTENT_TYPE, RESPONSE_TYPE_JSON)
  166. .end(Json.encode(new World(row.getInteger(0), row.getInteger(1))));
  167. } else {
  168. logger.error(res.cause());
  169. resp.setStatusCode(500).end(res.cause().getMessage());
  170. }
  171. });
  172. }
  173. class Queries {
  174. boolean failed;
  175. JsonArray worlds = new JsonArray();
  176. private void handle(HttpServerRequest req) {
  177. HttpServerResponse resp = req.response();
  178. final int queries = getQueries(req);
  179. for (int i = 0; i < queries; i++) {
  180. client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), ar -> {
  181. if (!failed) {
  182. if (ar.failed()) {
  183. failed = true;
  184. resp.setStatusCode(500).end(ar.cause().getMessage());
  185. return;
  186. }
  187. // we need a final reference
  188. final Tuple row = ar.result().iterator().next();
  189. worlds.add(new JsonObject().put("id", "" + row.getInteger(0)).put("randomNumber", "" + row.getInteger(1)));
  190. // stop condition
  191. if (worlds.size() == queries) {
  192. resp
  193. .putHeader(HttpHeaders.SERVER, SERVER)
  194. .putHeader(HttpHeaders.DATE, dateString)
  195. .putHeader(HttpHeaders.CONTENT_TYPE, RESPONSE_TYPE_JSON)
  196. .end(worlds.encode());
  197. }
  198. }
  199. });
  200. }
  201. }
  202. }
  203. class Update {
  204. final HttpServerRequest req;
  205. boolean failed;
  206. int queryCount;
  207. final World[] worlds;
  208. public Update(HttpServerRequest req) {
  209. final int queries = getQueries(req);
  210. this.req = req;
  211. this.worlds = new World[queries];
  212. }
  213. private void handle() {
  214. for (int i = 0; i < worlds.length; i++) {
  215. int id = randomWorld();
  216. int index = i;
  217. client.preparedQuery(SELECT_WORLD, Tuple.of(id), ar -> {
  218. if (!failed) {
  219. if (ar.failed()) {
  220. failed = true;
  221. sendError(ar.cause());
  222. return;
  223. }
  224. worlds[index] = new World(ar.result().iterator().next().getInteger(0), randomWorld());
  225. if (++queryCount == worlds.length) {
  226. handleUpdates();
  227. }
  228. }
  229. });
  230. }
  231. }
  232. void handleUpdates() {
  233. Arrays.sort(worlds);
  234. List<Tuple> batch = new ArrayList<>();
  235. for (World world : worlds) {
  236. batch.add(Tuple.of(world.getRandomNumber(), world.getId()));
  237. }
  238. client.preparedBatch(UPDATE_WORLD, batch, ar -> {
  239. if (ar.failed()) {
  240. sendError(ar.cause());
  241. return;
  242. }
  243. JsonArray json = new JsonArray();
  244. for (World world : worlds) {
  245. json.add(new JsonObject().put("id", "" + world.getId()).put("randomNumber", "" + world.getRandomNumber()));
  246. }
  247. req.response()
  248. .putHeader(HttpHeaders.SERVER, SERVER)
  249. .putHeader(HttpHeaders.DATE, dateString)
  250. .putHeader(HttpHeaders.CONTENT_TYPE, RESPONSE_TYPE_JSON)
  251. .end(json.toBuffer());
  252. });
  253. }
  254. void sendError(Throwable err) {
  255. logger.error("", err);
  256. req.response().setStatusCode(500).end(err.getMessage());
  257. }
  258. }
  259. private void handleFortunes(HttpServerRequest req) {
  260. client.preparedQuery(SELECT_FORTUNE, ar -> {
  261. HttpServerResponse response = req.response();
  262. if (ar.succeeded()) {
  263. List<Fortune> fortunes = new ArrayList<>();
  264. PgIterator<Row> resultSet = ar.result().iterator();
  265. if (!resultSet.hasNext()) {
  266. response.setStatusCode(404).end("No results");
  267. return;
  268. }
  269. while (resultSet.hasNext()) {
  270. Tuple row = resultSet.next();
  271. fortunes.add(new Fortune(row.getInteger(0), row.getString(1)));
  272. }
  273. fortunes.add(new Fortune(0, "Additional fortune added at request time."));
  274. Collections.sort(fortunes);
  275. response
  276. .putHeader(HttpHeaders.SERVER, SERVER)
  277. .putHeader(HttpHeaders.DATE, dateString)
  278. .putHeader(HttpHeaders.CONTENT_TYPE, RESPONSE_TYPE_HTML)
  279. .end(FortunesTemplate.template(fortunes).render().toString());
  280. } else {
  281. Throwable err = ar.cause();
  282. logger.error("", err);
  283. response.setStatusCode(500).end(err.getMessage());
  284. }
  285. });
  286. }
  287. public static void main(String[] args) throws Exception {
  288. JsonObject config = new JsonObject(new String(Files.readAllBytes(new File(args[0]).toPath())));
  289. int procs = Runtime.getRuntime().availableProcessors();
  290. Vertx vertx = Vertx.vertx();
  291. vertx.exceptionHandler(err -> {
  292. err.printStackTrace();
  293. });
  294. vertx.deployVerticle(App.class.getName(),
  295. new DeploymentOptions().setInstances(procs * 2).setConfig(config), event -> {
  296. if (event.succeeded()) {
  297. logger.debug("Your Vert.x application is started!");
  298. } else {
  299. logger.error("Unable to start your application", event.cause());
  300. }
  301. });
  302. }
  303. }