Server.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. namespace GuzzleHttp\Tests\Ring\Client;
  3. use GuzzleHttp\Ring\Client\StreamHandler;
  4. use GuzzleHttp\Ring\Core;
  5. /**
  6. * Class uses to control the test webserver.
  7. *
  8. * Queued responses will be served to requests using a FIFO order. All requests
  9. * received by the server are stored on the node.js server and can be retrieved
  10. * by calling {@see Server::received()}.
  11. *
  12. * Mock responses that don't require data to be transmitted over HTTP a great
  13. * for testing. Mock response, however, cannot test the actual sending of an
  14. * HTTP request using cURL. This test server allows the simulation of any
  15. * number of HTTP request response transactions to test the actual sending of
  16. * requests over the wire without having to leave an internal network.
  17. */
  18. class Server
  19. {
  20. public static $started;
  21. public static $url = 'http://127.0.0.1:8125/';
  22. public static $host = '127.0.0.1:8125';
  23. public static $port = 8125;
  24. /**
  25. * Flush the received requests from the server
  26. * @throws \RuntimeException
  27. */
  28. public static function flush()
  29. {
  30. self::send('DELETE', '/guzzle-server/requests');
  31. }
  32. /**
  33. * Queue an array of responses or a single response on the server.
  34. *
  35. * Any currently queued responses will be overwritten. Subsequent requests
  36. * on the server will return queued responses in FIFO order.
  37. *
  38. * @param array $responses An array of responses. The shape of a response
  39. * is the shape described in the RingPHP spec.
  40. * @throws \Exception
  41. */
  42. public static function enqueue(array $responses)
  43. {
  44. $data = [];
  45. foreach ($responses as $response) {
  46. if (!is_array($response)) {
  47. throw new \Exception('Each response must be an array');
  48. }
  49. if (isset($response['body'])) {
  50. $response['body'] = base64_encode($response['body']);
  51. }
  52. $response += ['headers' => [], 'reason' => '', 'body' => ''];
  53. $data[] = $response;
  54. }
  55. self::send('PUT', '/guzzle-server/responses', json_encode($data));
  56. }
  57. /**
  58. * Get all of the received requests as a RingPHP request structure.
  59. *
  60. * @return array
  61. * @throws \RuntimeException
  62. */
  63. public static function received()
  64. {
  65. if (!self::$started) {
  66. return [];
  67. }
  68. $response = self::send('GET', '/guzzle-server/requests');
  69. $body = Core::body($response);
  70. $result = json_decode($body, true);
  71. if ($result === false) {
  72. throw new \RuntimeException('Error decoding response: '
  73. . json_last_error());
  74. }
  75. foreach ($result as &$res) {
  76. if (isset($res['uri'])) {
  77. $res['resource'] = $res['uri'];
  78. }
  79. if (isset($res['query_string'])) {
  80. $res['resource'] .= '?' . $res['query_string'];
  81. }
  82. if (!isset($res['resource'])) {
  83. $res['resource'] = '';
  84. }
  85. // Ensure that headers are all arrays
  86. if (isset($res['headers'])) {
  87. foreach ($res['headers'] as &$h) {
  88. $h = (array) $h;
  89. }
  90. unset($h);
  91. }
  92. }
  93. unset($res);
  94. return $result;
  95. }
  96. /**
  97. * Stop running the node.js server
  98. */
  99. public static function stop()
  100. {
  101. if (self::$started) {
  102. self::send('DELETE', '/guzzle-server');
  103. }
  104. self::$started = false;
  105. }
  106. public static function wait($maxTries = 20)
  107. {
  108. $tries = 0;
  109. while (!self::isListening() && ++$tries < $maxTries) {
  110. usleep(100000);
  111. }
  112. if (!self::isListening()) {
  113. throw new \RuntimeException('Unable to contact node.js server');
  114. }
  115. }
  116. public static function start()
  117. {
  118. if (self::$started) {
  119. return;
  120. }
  121. try {
  122. self::wait();
  123. } catch (\Exception $e) {
  124. exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR . 'server.js '
  125. . self::$port . ' >> /tmp/server.log 2>&1 &');
  126. self::wait();
  127. }
  128. self::$started = true;
  129. }
  130. private static function isListening()
  131. {
  132. $response = self::send('GET', '/guzzle-server/perf', null, [
  133. 'connect_timeout' => 1,
  134. 'timeout' => 1
  135. ]);
  136. return !isset($response['error']);
  137. }
  138. private static function send(
  139. $method,
  140. $path,
  141. $body = null,
  142. array $client = []
  143. ) {
  144. $handler = new StreamHandler();
  145. $request = [
  146. 'http_method' => $method,
  147. 'uri' => $path,
  148. 'request_port' => 8125,
  149. 'headers' => ['host' => ['127.0.0.1:8125']],
  150. 'body' => $body,
  151. 'client' => $client,
  152. ];
  153. if ($body) {
  154. $request['headers']['content-length'] = [strlen($body)];
  155. }
  156. return $handler($request);
  157. }
  158. }