server.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php declare(strict_types=1);
  2. include __DIR__ . '/vendor/autoload.php';
  3. use Ripple\Database\Interface\AbstractResultSet;
  4. use Ripple\Database\MySQL\Client;
  5. use Ripple\Net\Http\Server;
  6. use Ripple\Net\Http\Server\Request;
  7. use Ripple\Sync\Channel;
  8. use Ripple\Sync\WaitGroup;
  9. use Ripple\Worker;
  10. use Ripple\Worker\Manager;
  11. use Ripple\Net\Http;
  12. use function Co\go;
  13. use function Co\wait;
  14. class Setup
  15. {
  16. /**
  17. * @var string
  18. */
  19. public static string $dateFormatted;
  20. /**
  21. * @var null|callable
  22. */
  23. private static $fortunesRenderer = null;
  24. /**
  25. * @return void
  26. */
  27. public static function dateRefresh(): void
  28. {
  29. try {
  30. $date = new DateTime('now', new DateTimeZone('GMT'));
  31. } catch (Throwable) {
  32. return;
  33. }
  34. Setup::$dateFormatted = $date->format('D, d M Y H:i:s T');
  35. }
  36. /**
  37. * @return int
  38. */
  39. public static function randomInt(): int
  40. {
  41. try {
  42. return \random_int(1, 10000);
  43. } catch (Throwable $e) {
  44. return \mt_rand(1, 10000);
  45. }
  46. }
  47. /**
  48. * @param mixed $value
  49. * @return int
  50. */
  51. public static function clamp(mixed $value): int
  52. {
  53. if (!\is_numeric($value) || $value < 1) {
  54. return 1;
  55. }
  56. if ($value > 500) {
  57. return 500;
  58. }
  59. return \intval($value);
  60. }
  61. /**
  62. * @param array $rows
  63. * @return string
  64. */
  65. public static function renderFortunes(array $rows): string
  66. {
  67. if (self::$fortunesRenderer === null) {
  68. $code = \file_get_contents(__DIR__ . '/fortunes.php');
  69. if ($code === false) {
  70. return '';
  71. }
  72. $renderer = static function (array $scope) use ($code): string {
  73. foreach ($scope as $key => $value) {
  74. $$key = $value;
  75. }
  76. \ob_start();
  77. eval('?>' . $code);
  78. return (string) \ob_get_clean();
  79. };
  80. self::$fortunesRenderer = $renderer;
  81. }
  82. /** @var callable $renderer */
  83. $renderer = self::$fortunesRenderer;
  84. return $renderer(['rows' => $rows]);
  85. }
  86. }
  87. $manager = new Manager();
  88. $worker = new class () extends Worker {
  89. /**
  90. * @var Server
  91. */
  92. private Server $server;
  93. /**
  94. * @var Channel
  95. */
  96. private Channel $STM_queryWorldWhereID;
  97. /**
  98. * @var Channel
  99. */
  100. private Channel $STM_updateWorldRandomNumber;
  101. /**
  102. * @var Channel
  103. */
  104. private Channel $STM_queryFortune;
  105. /**
  106. * @return void
  107. */
  108. public function register(): void
  109. {
  110. $this->count = 32;
  111. $context = \stream_context_create([
  112. 'socket' => [
  113. 'so_reuseport' => 1,
  114. 'so_reuseaddr' => 1
  115. ]
  116. ]);
  117. $this->server = Http::server('http://0.0.0.0:8080', $context);
  118. }
  119. /**
  120. * @return void
  121. */
  122. public function boot(): void
  123. {
  124. Setup::dateRefresh();
  125. go(static function () {
  126. while (1) {
  127. \Co\sleep(1);
  128. Setup::dateRefresh();
  129. }
  130. });
  131. $this->STM_queryWorldWhereID = new Channel(10);
  132. $this->STM_updateWorldRandomNumber = new Channel(10);
  133. $this->STM_queryFortune = new Channel(10);
  134. for ($i = 0; $i < 10; $i++) {
  135. $client = new Client(
  136. 'mysql:host=tfb-database;port=3306;dbname=hello_world',
  137. 'benchmarkdbuser',
  138. 'benchmarkdbpass',
  139. );
  140. $this->STM_queryWorldWhereID->send($client->prepare('SELECT id, randomNumber FROM World WHERE id = ?'));
  141. $this->STM_updateWorldRandomNumber->send($client->prepare('UPDATE World SET randomNumber = ? WHERE id = ?'));
  142. $this->STM_queryFortune->send($client->prepare('SELECT * FROM `Fortune`'));
  143. }
  144. $this->server->onRequest = fn (Request $request) => $this->onRequest($request);
  145. $this->server->listen();
  146. }
  147. /**
  148. * @param Request $request
  149. * @return void
  150. */
  151. public function onRequest(Request $request): void
  152. {
  153. switch ($request->SERVER['REQUEST_URI']) {
  154. case '/json':
  155. {
  156. $request->respondJson(
  157. ['message' => 'Hello, World!'],
  158. ['Date' => Setup::$dateFormatted]
  159. );
  160. break;
  161. }
  162. case '/db':
  163. {
  164. $statement = $this->STM_queryWorldWhereID->receive();
  165. $statement->execute([Setup::randomInt()]);
  166. $request->respondJson($statement->fetch(AbstractResultSet::ASSOC_ARRAY), ['Date' => Setup::$dateFormatted]);
  167. $this->STM_queryWorldWhereID->send($statement);
  168. break;
  169. }
  170. case '/queries':
  171. {
  172. $queries = Setup::clamp($request->GET['queries'] ?? 1);
  173. $results = [];
  174. $waitGroup = new WaitGroup();
  175. $waitGroup->add($queries);
  176. while ($queries--) {
  177. go(function () use (&$results, $waitGroup) {
  178. $statement = $this->STM_queryWorldWhereID->receive();
  179. $statement->execute([Setup::randomInt()]);
  180. $results[] = $statement->fetch(AbstractResultSet::ASSOC_ARRAY);
  181. $this->STM_queryWorldWhereID->send($statement);
  182. $waitGroup->done();
  183. });
  184. }
  185. $waitGroup->wait();
  186. $request->respondJson($results, ['Date' => Setup::$dateFormatted]);
  187. break;
  188. }
  189. case '/fortunes':
  190. {
  191. $statement = $this->STM_queryFortune->receive();
  192. $statement->execute();
  193. $rows = $statement->fetchAll(AbstractResultSet::ASSOC_ARRAY);
  194. $this->STM_queryFortune->send($statement);
  195. $rows[] = ['id' => 0, 'message' => 'Additional fortune added at request time.'];
  196. \usort($rows, function ($a, $b) {
  197. return $a['message'] <=> $b['message'];
  198. });
  199. $request->respondHtml(
  200. Setup::renderFortunes($rows),
  201. [
  202. 'Date' => Setup::$dateFormatted,
  203. 'Content-Type' => 'text/html; charset=UTF-8'
  204. ]
  205. );
  206. break;
  207. }
  208. case '/updates':
  209. {
  210. $queries = Setup::clamp($request->GET['queries'] ?? 1);
  211. $results = [];
  212. $waitGroup = new WaitGroup();
  213. $waitGroup->add($queries);
  214. while ($queries--) {
  215. go(function () use (&$results, $waitGroup) {
  216. $statement = $this->STM_queryWorldWhereID->receive();
  217. $update = $this->STM_updateWorldRandomNumber->receive();
  218. $statement->execute([Setup::randomInt()]);
  219. $row = $statement->fetch(AbstractResultSet::ASSOC_ARRAY);
  220. $row['randomNumber'] = Setup::randomInt();
  221. $results[] = $row;
  222. $update->execute([$row['randomNumber'], $row['id']]);
  223. $this->STM_queryWorldWhereID->send($statement);
  224. $this->STM_updateWorldRandomNumber->send($update);
  225. $waitGroup->done();
  226. });
  227. }
  228. $waitGroup->wait();
  229. $request->respondJson($results, ['Date' => Setup::$dateFormatted]);
  230. break;
  231. }
  232. case '/plaintext':
  233. {
  234. $request->respond(
  235. 'Hello, World!',
  236. [
  237. 'Content-Type' => 'text/plain; charset=utf-8',
  238. 'Date' => Setup::$dateFormatted
  239. ]
  240. );
  241. break;
  242. }
  243. default:
  244. {
  245. $request->respond('Not Found', [], 404);
  246. }
  247. }
  248. }
  249. };
  250. $manager->add($worker);
  251. $manager->run();
  252. wait();