server.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import 'dart:async' show Future;
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:isolate';
  5. import 'dart:math' show Random, max;
  6. import 'package:mustache/mustache.dart' as mustache;
  7. import 'package:postgresql/postgresql.dart' as pg;
  8. import 'package:postgresql/postgresql_pool.dart' as pgpool;
  9. import 'package:system_info/system_info.dart';
  10. import 'package:yaml/yaml.dart' as yaml;
  11. final _NUM_PROCESSORS = SysInfo.processors.length;
  12. final _encoder = new JsonUtf8Encoder();
  13. void main(List<String> args) {
  14. ReceivePort errorPort = new ReceivePort();
  15. errorPort.listen((e) => print(e));
  16. for (int i = 0; i < _NUM_PROCESSORS; i++) {
  17. Isolate.spawn(
  18. startInIsolate,
  19. [],
  20. onError: errorPort.sendPort);
  21. }
  22. }
  23. void startInIsolate(List args) {
  24. _startServer();
  25. }
  26. /// The entity used in the database query and update tests.
  27. class World {
  28. int id;
  29. int randomnumber;
  30. World(this.id, this.randomnumber);
  31. toJson() => {'id': id, 'randomNumber': randomnumber};
  32. }
  33. /// The entity used in the fortunes test.
  34. class Fortune implements Comparable<Fortune> {
  35. int id;
  36. String message;
  37. Fortune(this.id, this.message);
  38. int compareTo(Fortune other) => message.compareTo(other.message);
  39. }
  40. /// The number of rows in the world entity table.
  41. const _WORLD_TABLE_SIZE = 10000;
  42. /// A random number generator.
  43. final _RANDOM = new Random();
  44. /// The PostgreSQL connection pool used by all the tests that require database
  45. /// connectivity.
  46. pgpool.Pool _connectionPool;
  47. /// The mustache template which is rendered in the fortunes test.
  48. mustache.Template _fortunesTemplate;
  49. void _startServer() {
  50. var dbConnections = max(1, (256 / _NUM_PROCESSORS).floor());
  51. Future.wait([
  52. HttpServer.bind("0.0.0.0", 8080, shared: true),
  53. new File('postgresql.yaml').readAsString().then((config) {
  54. _connectionPool = new pgpool.Pool(
  55. new pg.Settings.fromMap(yaml.loadYaml(config)).toUri(),
  56. min: dbConnections, max: dbConnections);
  57. return _connectionPool.start();
  58. }),
  59. new File('fortunes.mustache').readAsString().then((template) {
  60. _fortunesTemplate = mustache.parse(template);
  61. })
  62. ]).then((List waitResults) {
  63. var server = waitResults[0];
  64. server.defaultResponseHeaders.clear();
  65. server.serverHeader = 'dart';
  66. server.listen((request) {
  67. switch (request.uri.path) {
  68. case '/json':
  69. _jsonTest(request);
  70. break;
  71. case '/db':
  72. _dbTest(request);
  73. break;
  74. case '/queries':
  75. _queriesTest(request);
  76. break;
  77. case '/fortunes':
  78. _fortunesTest(request);
  79. break;
  80. case '/updates':
  81. _updatesTest(request);
  82. break;
  83. case '/plaintext':
  84. _plaintextTest(request);
  85. break;
  86. default:
  87. _sendResponse(request, HttpStatus.NOT_FOUND);
  88. break;
  89. }
  90. });
  91. });
  92. }
  93. /// Returns the given [text] parsed as a base 10 integer. If the text is null
  94. /// or is an otherwise invalid representation of a base 10 integer, zero is
  95. /// returned.
  96. int _parseInt(String text) =>
  97. (text == null) ? 0 : int.parse(text, radix: 10, onError: ((_) => 0));
  98. /// Completes the given [request] by writing the [response] with the given
  99. /// [statusCode] and [type].
  100. void _sendResponse(HttpRequest request, int statusCode,
  101. {ContentType type, List<int> response}) {
  102. request.response.statusCode = statusCode;
  103. request.response.headers.date = new DateTime.now();
  104. if (type != null) {
  105. request.response.headers.contentType = type;
  106. }
  107. if (response != null) {
  108. request.response.contentLength = response.length;
  109. request.response.add(response);
  110. } else {
  111. request.response.contentLength = 0;
  112. }
  113. request.response.close();
  114. }
  115. /// Completes the given [request] by writing the [response] as HTML.
  116. void _sendHtml(HttpRequest request, String response) {
  117. _sendResponse(request, HttpStatus.OK,
  118. type: ContentType.HTML, response: UTF8.encode(response));
  119. }
  120. /// Completes the given [request] by writing the [response] as JSON.
  121. void _sendJson(HttpRequest request, Object response) {
  122. _sendResponse(request, HttpStatus.OK,
  123. type: ContentType.JSON, response: _encoder.convert(response));
  124. }
  125. /// Completes the given [request] by writing the [response] as plain text.
  126. void _sendText(HttpRequest request, String response) {
  127. _sendResponse(request, HttpStatus.OK,
  128. type: ContentType.TEXT, response: UTF8.encode(response));
  129. }
  130. /// Responds with the JSON test to the [request].
  131. void _jsonTest(HttpRequest request) {
  132. _sendJson(request, {'message': 'Hello, World!'});
  133. }
  134. Future<World> _queryRandom() {
  135. return _connectionPool.connect().then((connection) {
  136. return connection.query(
  137. 'SELECT id, randomnumber FROM world WHERE id = @id;', {
  138. 'id': _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1
  139. })
  140. //
  141. // The benchmark's constraints tell us there is exactly one row.
  142. //
  143. .single.then((row) => new World(row[0], row[1])).whenComplete(() {
  144. connection.close();
  145. });
  146. });
  147. }
  148. /// Responds with the database query test to the [request].
  149. void _dbTest(HttpRequest request) {
  150. _queryRandom().then((response) => _sendJson(request, response));
  151. }
  152. /// Responds with the database queries test to the [request].
  153. void _queriesTest(HttpRequest request) {
  154. var queries = _parseInt(request.uri.queryParameters['queries']).clamp(1, 500);
  155. Future
  156. .wait(new List.generate(queries, (_) => _queryRandom(), growable: false))
  157. .then((response) => _sendJson(request, response));
  158. }
  159. /// Responds with the fortunes test to the [request].
  160. void _fortunesTest(HttpRequest request) {
  161. _connectionPool.connect().then((connection) {
  162. return connection
  163. .query('SELECT id, message FROM fortune;')
  164. .map((row) => new Fortune(row[0], row[1]))
  165. .toList()
  166. .whenComplete(() {
  167. connection.close();
  168. });
  169. }).then((fortunes) {
  170. fortunes.add(new Fortune(0, 'Additional fortune added at request time.'));
  171. fortunes.sort();
  172. _sendHtml(request, _fortunesTemplate.renderString({
  173. 'fortunes': fortunes
  174. .map((fortune) => {'id': fortune.id, 'message': fortune.message})
  175. .toList()
  176. }));
  177. });
  178. }
  179. /// Responds with the updates test to the [request].
  180. void _updatesTest(HttpRequest request) {
  181. var queries = _parseInt(request.uri.queryParameters['queries']).clamp(1, 500);
  182. Future.wait(new List.generate(queries, (_) {
  183. return _queryRandom().then((world) {
  184. world.randomnumber = _RANDOM.nextInt(_WORLD_TABLE_SIZE) + 1;
  185. return _connectionPool.connect().then((connection) {
  186. return connection
  187. .execute(
  188. 'UPDATE world SET randomnumber = @randomnumber WHERE id = @id;',
  189. {'randomnumber': world.randomnumber, 'id': world.id})
  190. .whenComplete(() {
  191. connection.close();
  192. });
  193. }).then((_) => world);
  194. });
  195. }, growable: false)).then((worlds) => _sendJson(request, worlds));
  196. }
  197. /// Responds with the plaintext test to the [request].
  198. void _plaintextTest(HttpRequest request) {
  199. _sendText(request, 'Hello, World!');
  200. }