server.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. import "dart:core";
  2. import "dart:io";
  3. import 'dart:async' show Future;
  4. import 'dart:convert';
  5. import 'dart:math' show Random;
  6. import "package:start/start.dart";
  7. import "package:args/args.dart";
  8. import 'package:postgresql/postgresql.dart' as pg;
  9. import 'package:postgresql/postgresql_pool.dart' as pgpool;
  10. import 'package:yaml/yaml.dart' as yaml;
  11. import 'package:mustache/mustache.dart' as mustache;
  12. import 'package:mongo_dart/mongo_dart.dart';
  13. import 'package:crypto/crypto.dart';
  14. // Postgres connection pool
  15. var _connectionPool;
  16. // Fortunes mustache template
  17. var _fortunesTemplate;
  18. // MongoDB connection
  19. var _mongoDb;
  20. // World Collection
  21. var _worldCollection;
  22. // Fortunes Collection
  23. var _fortuneCollection;
  24. // World table size
  25. const _WORLD_TABLE_SIZE = 10000;
  26. // Fortune table size used only for generation of data
  27. const _FORTUNE_TABLE_SIZE = 100;
  28. final _RANDOM = new Random();
  29. class Fortune implements Comparable<Fortune> {
  30. int id;
  31. String message;
  32. Fortune(this.id, this.message);
  33. compareTo(Fortune other) => message.compareTo(other.message);
  34. }
  35. class World {
  36. int id;
  37. int randomnumber;
  38. World(this.id, this.randomnumber);
  39. toJson() => { "id": id, "randomNumber": randomnumber };
  40. }
  41. main(List<String> args) {
  42. var parser = new ArgParser();
  43. parser.addOption('address', abbr: 'a', defaultsTo: '0.0.0.0');
  44. parser.addOption('port', abbr: 'p', defaultsTo: '8080');
  45. parser.addOption('dbconnections', abbr: 'd', defaultsTo: '256');
  46. var arguments = parser.parse(args);
  47. Future.wait([
  48. new File("postgresql.yaml").readAsString().then((config){
  49. _connectionPool = new pgpool.Pool(
  50. new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
  51. min: int.parse(arguments["dbconnections"]),
  52. max: int.parse(arguments["dbconnections"])
  53. );
  54. return _connectionPool.start();
  55. }),
  56. new File("mongodb.yaml").readAsString().then((config) {
  57. var mongoConfig = yaml.loadYaml(config);
  58. _mongoDb = new Db("mongodb://${mongoConfig["host"]}/${mongoConfig["database"]}");
  59. return _mongoDb.open().then((_) {
  60. _worldCollection = _mongoDb.collection("World");
  61. _fortuneCollection = _mongoDb.collection("Fortune");
  62. });
  63. }),
  64. new File('fortunes.mustache').readAsString().then((template) {
  65. _fortunesTemplate = mustache.parse(template);
  66. })
  67. ]).then((_) {
  68. start(host: arguments["address"], public: 'web', port: int.parse(arguments["port"]))
  69. .then((Server app) {
  70. // JSON test
  71. app.get('/json').listen((request) {
  72. var helloWorld = {
  73. "message": "Hello, World!"
  74. };
  75. _setJsonHeaders(request.response);
  76. request.response.send(JSON.encode(helloWorld));
  77. });
  78. // Query test
  79. app.get("/db").listen((request) {
  80. _setJsonHeaders(request.response);
  81. _query().then((data) {
  82. request.response.send(JSON.encode(data));
  83. });
  84. });
  85. // Queries test
  86. app.get("/queries").listen((request) {
  87. var queries = _parseQueriesParam(request.param("queries"));
  88. _setJsonHeaders(request.response);
  89. Future.wait(
  90. new List.generate(
  91. queries,
  92. (_) => _query(),
  93. growable: false
  94. )
  95. )
  96. .then((response) => request.response.send(JSON.encode(response)));
  97. });
  98. // Fortunes test
  99. app.get("/fortunes").listen((request) {
  100. _setHtmlHeaders(request.response);
  101. _connectionPool.connect().then((connection) {
  102. return connection.query('SELECT id, message FROM fortune;')
  103. .map((row) => new Fortune(row[0], row[1]))
  104. .toList()
  105. .whenComplete(() { connection.close(); });
  106. }).then((fortunes) {
  107. fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
  108. fortunes.sort();
  109. request.response.send(_fortunesTemplate.renderString({
  110. "fortunes": fortunes.map((fortune) => {
  111. "id": fortune.id,
  112. "message": fortune.message
  113. }).toList()
  114. }));
  115. });
  116. });
  117. // Updates test
  118. app.get("/updates").listen((request) {
  119. var queries = _parseQueriesParam(request.param("queries"));
  120. _setJsonHeaders(request.response);
  121. Future.wait(new List.generate(queries, (_) {
  122. return _query()
  123. .then((world) {
  124. world.randomnumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
  125. return _connectionPool.connect()
  126. .then((connection) {
  127. return connection.execute(
  128. 'UPDATE world SET randomnumber = @randomnumber WHERE id = @id;',
  129. {
  130. 'randomnumber': world.randomnumber,
  131. 'id': world.id
  132. }
  133. )
  134. .whenComplete(() { connection.close(); });
  135. })
  136. .then((_) => world);
  137. });
  138. }, growable: false))
  139. .then((worlds) => request.response.send(JSON.encode(worlds)));
  140. });
  141. // Plain text test
  142. app.get("/plaintext").listen((request) {
  143. _setPlainHeaders(request.response);
  144. request.response.send("Hello, World!");
  145. });
  146. // Mongo World dev data generation
  147. app.get("/generate-world").listen((request) {
  148. _worldCollection.drop()
  149. .then((_) {
  150. var collectionData = new List.generate(_WORLD_TABLE_SIZE, (index) {
  151. return {
  152. "_id": index + 1,
  153. "randomNumber": _RANDOM.nextInt(_WORLD_TABLE_SIZE)
  154. };
  155. });
  156. return _worldCollection.insertAll(collectionData);
  157. })
  158. .then((_) {
  159. request.response.send("Generated");
  160. });
  161. });
  162. // Mongo Fortune dev data generation
  163. app.get("/generate-fortune").listen((request) {
  164. _fortuneCollection.drop()
  165. .then((_) {
  166. var collectionData = new List.generate(_FORTUNE_TABLE_SIZE, (index) {
  167. var hash = new MD5();
  168. hash.add(_RANDOM.nextInt(_FORTUNE_TABLE_SIZE).toString().codeUnits);
  169. return {
  170. "_id": index + 1,
  171. "message": CryptoUtils.bytesToHex(hash.close())
  172. };
  173. });
  174. return _fortuneCollection.insertAll(collectionData);
  175. })
  176. .then((_) {
  177. request.response.send("Generated");
  178. });
  179. });
  180. // Mongo query test
  181. app.get("/db-mongo").listen((request) {
  182. _setJsonHeaders(request.response);
  183. _mongoQuery().then((data) {
  184. request.response.json({
  185. "id": data["_id"],
  186. "randomNumber": data["randomNumber"]
  187. });
  188. });
  189. });
  190. // Mongo queries test
  191. app.get("/queries-mongo").listen((request) {
  192. var queries = _parseQueriesParam(request.param("queries"));
  193. _setJsonHeaders(request.response);
  194. Future.wait(
  195. new List.generate(
  196. queries,
  197. (_) => _mongoQuery(),
  198. growable: false
  199. )
  200. )
  201. .then((response) {
  202. var results = response.map((world) {
  203. return {
  204. "id": world["_id"],
  205. "randomNumber": world["randomNumber"]
  206. };
  207. });
  208. request.response.send(JSON.encode(results.toList()));
  209. });
  210. });
  211. // Mongo updates test
  212. app.get("/updates-mongo").listen((request) {
  213. var queries = _parseQueriesParam(request.param("queries"));
  214. _setJsonHeaders(request.response);
  215. Future.wait(new List.generate(queries, (index) {
  216. return _mongoQuery()
  217. .then((world) {
  218. world["randomNumber"] = _RANDOM.nextInt(_WORLD_TABLE_SIZE);
  219. return _worldCollection.update( { "_id": world["_id"] }, world)
  220. .then((_) => world);
  221. });
  222. }, growable: false))
  223. .then((worlds) {
  224. var result = worlds.map((world) {
  225. return {
  226. "id": world["_id"],
  227. "randomNumber": world["randomNumber"]
  228. };
  229. });
  230. request.response.send(JSON.encode(result.toList()));
  231. });
  232. });
  233. // Mongo fortunes test
  234. app.get("/fortunes-mongo").listen((request) {
  235. _setHtmlHeaders(request.response);
  236. _fortuneCollection.find().toList().then((fortunes) {
  237. fortunes = fortunes.map((fortune) {
  238. return new Fortune(fortune["_id"], fortune["message"]);
  239. }).toList();
  240. fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
  241. fortunes.sort();
  242. request.response.send(_fortunesTemplate.renderString({
  243. "fortunes": fortunes.map((fortune) => {
  244. "id": fortune.id,
  245. "message": fortune.message
  246. }).toList()
  247. }));
  248. });
  249. });
  250. });
  251. });
  252. }
  253. // set JSON headers
  254. _setJsonHeaders(response) {
  255. _setHeaders(response);
  256. response
  257. .header(HttpHeaders.CONTENT_TYPE, 'application/json; charset=UTF-8');
  258. }
  259. // set plain text headers
  260. _setPlainHeaders(response) {
  261. _setHeaders(response);
  262. response
  263. .header(HttpHeaders.CONTENT_TYPE, 'text/plain; charset=UTF-8');
  264. }
  265. // set HTML headers
  266. _setHtmlHeaders(response) {
  267. _setHeaders(response);
  268. response
  269. .header(HttpHeaders.CONTENT_TYPE, 'text/html; charset=UTF-8');
  270. }
  271. // set common headers
  272. _setHeaders(response) {
  273. // disable gzip encoding
  274. response.header(HttpHeaders.CONTENT_ENCODING, "")
  275. ..header(HttpHeaders.DATE, new DateTime.now());
  276. }
  277. // parse queries param
  278. _parseQueriesParam(param) {
  279. return param.isEmpty ? 1 : int.parse(param, radix: 10, onError: (_) => 1).clamp(1, 500);
  280. }
  281. // runs a query and returns a promise
  282. _query() {
  283. return _connectionPool.connect().then((connection) {
  284. return connection
  285. .query('SELECT id, randomnumber FROM world WHERE id = @id;', { 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1 })
  286. .single
  287. .then((row) =>new World(row[0], row[1]))
  288. .whenComplete(() {
  289. connection.close();
  290. });
  291. });
  292. }
  293. // runs a mongo query and returns a promise
  294. _mongoQuery() {
  295. return _worldCollection.findOne({
  296. "_id": _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1
  297. });
  298. }