server.dart 8.6 KB

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