Client.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Testing\Http;
  12. use Hyperf\Collection\Arr;
  13. use Hyperf\Context\Context;
  14. use Hyperf\Contract\ConfigInterface;
  15. use Hyperf\Contract\PackerInterface;
  16. use Hyperf\Dispatcher\HttpDispatcher;
  17. use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher;
  18. use Hyperf\HttpMessage\Server\Request as Psr7Request;
  19. use Hyperf\HttpMessage\Server\Response as Psr7Response;
  20. use Hyperf\HttpMessage\Stream\SwooleStream;
  21. use Hyperf\HttpMessage\Uri\Uri;
  22. use Hyperf\HttpServer\MiddlewareManager;
  23. use Hyperf\HttpServer\ResponseEmitter;
  24. use Hyperf\HttpServer\Router\Dispatched;
  25. use Hyperf\HttpServer\Server;
  26. use Hyperf\Support\Filesystem\Filesystem;
  27. use Hyperf\Testing\HttpMessage\Upload\UploadedFile;
  28. use Psr\Container\ContainerInterface;
  29. use Psr\Http\Message\ResponseInterface;
  30. use Psr\Http\Message\ServerRequestInterface;
  31. use Throwable;
  32. use function Hyperf\Collection\data_get;
  33. use function Hyperf\Coroutine\wait;
  34. class Client extends Server
  35. {
  36. protected PackerInterface $packer;
  37. protected float $waitTimeout = 10.0;
  38. protected string $baseUri = 'http://127.0.0.1/';
  39. public function __construct(ContainerInterface $container, $server = 'http')
  40. {
  41. parent::__construct(
  42. $container,
  43. $container->get(HttpDispatcher::class),
  44. $container->get(ExceptionHandlerDispatcher::class),
  45. $container->get(ResponseEmitter::class)
  46. );
  47. $this->initCoreMiddleware($server);
  48. $this->initBaseUri($server);
  49. }
  50. public function get(string $uri, array $data = [], array $headers = [])
  51. {
  52. return $this->request('GET', $uri, [
  53. 'headers' => $headers,
  54. 'query' => $data,
  55. ]);
  56. }
  57. public function post(string $uri, array $data = [], array $headers = [])
  58. {
  59. return $this->request('POST', $uri, [
  60. 'headers' => $headers,
  61. 'form_params' => $data,
  62. ]);
  63. }
  64. public function put(string $uri, array $data = [], array $headers = [])
  65. {
  66. return $this->request('PUT', $uri, [
  67. 'headers' => $headers,
  68. 'form_params' => $data,
  69. ]);
  70. }
  71. public function delete(string $uri, array $data = [], array $headers = [])
  72. {
  73. return $this->request('DELETE', $uri, [
  74. 'headers' => $headers,
  75. 'query' => $data,
  76. ]);
  77. }
  78. public function json(string $uri, array $data = [], array $headers = [])
  79. {
  80. $headers['Content-Type'] = 'application/json';
  81. return $this->request('POST', $uri, [
  82. 'headers' => $headers,
  83. 'json' => $data,
  84. ]);
  85. }
  86. public function file(string $uri, array $data = [], array $headers = [])
  87. {
  88. $multipart = [];
  89. if (Arr::isAssoc($data)) {
  90. $data = [$data];
  91. }
  92. foreach ($data as $item) {
  93. $name = $item['name'];
  94. $file = $item['file'];
  95. $multipart[] = [
  96. 'name' => $name,
  97. 'contents' => fopen($file, 'r'),
  98. 'filename' => basename($file),
  99. ];
  100. }
  101. return $this->request('POST', $uri, [
  102. 'headers' => $headers,
  103. 'multipart' => $multipart,
  104. ]);
  105. }
  106. public function request(string $method, string $path, array $options = [])
  107. {
  108. return wait(function () use ($method, $path, $options) {
  109. return $this->execute($this->initRequest($method, $path, $options));
  110. }, $this->waitTimeout);
  111. }
  112. public function sendRequest(ServerRequestInterface $psr7Request): ResponseInterface
  113. {
  114. return wait(function () use ($psr7Request) {
  115. return $this->execute($psr7Request);
  116. }, $this->waitTimeout);
  117. }
  118. public function initRequest(string $method, string $path, array $options = []): ServerRequestInterface
  119. {
  120. $query = $options['query'] ?? [];
  121. $params = $options['form_params'] ?? [];
  122. $json = $options['json'] ?? [];
  123. $headers = $options['headers'] ?? [];
  124. $multipart = $options['multipart'] ?? [];
  125. $parsePath = parse_url($path);
  126. $path = $parsePath['path'];
  127. $uriPathQuery = $parsePath['query'] ?? [];
  128. if (! empty($uriPathQuery)) {
  129. parse_str($uriPathQuery, $pathQuery);
  130. $query = array_merge($pathQuery, $query);
  131. }
  132. $data = $params;
  133. // Initialize PSR-7 Request and Response objects.
  134. $uri = (new Uri($this->baseUri . ltrim($path, '/')))->withQuery(http_build_query($query));
  135. $content = http_build_query($params);
  136. if ($method == 'POST' && data_get($headers, 'Content-Type') == 'application/json') {
  137. $content = json_encode($json, JSON_UNESCAPED_UNICODE);
  138. $data = $json;
  139. }
  140. $body = new SwooleStream($content);
  141. $request = new Psr7Request($method, $uri, $headers, $body);
  142. return $request->withQueryParams($query)
  143. ->withParsedBody($data)
  144. ->withUploadedFiles($this->normalizeFiles($multipart));
  145. }
  146. protected function execute(ServerRequestInterface $psr7Request): ResponseInterface
  147. {
  148. $this->persistToContext($psr7Request, new Psr7Response());
  149. $psr7Request = $this->coreMiddleware->dispatch($psr7Request);
  150. /** @var Dispatched $dispatched */
  151. $dispatched = $psr7Request->getAttribute(Dispatched::class);
  152. $middlewares = $this->middlewares;
  153. if ($dispatched->isFound()) {
  154. $registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
  155. $middlewares = array_merge($middlewares, $registeredMiddlewares);
  156. }
  157. try {
  158. $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
  159. } catch (Throwable $throwable) {
  160. // Delegate the exception to exception handler.
  161. $psr7Response = $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
  162. }
  163. return $psr7Response;
  164. }
  165. protected function persistToContext(ServerRequestInterface $request, ResponseInterface $response)
  166. {
  167. Context::set(ServerRequestInterface::class, $request);
  168. Context::set(ResponseInterface::class, $response);
  169. }
  170. protected function initBaseUri(string $server): void
  171. {
  172. if ($this->container->has(ConfigInterface::class)) {
  173. $config = $this->container->get(ConfigInterface::class);
  174. $servers = $config->get('server.servers', []);
  175. foreach ($servers as $item) {
  176. if ($item['name'] == $server) {
  177. $this->baseUri = sprintf('http://127.0.0.1:%d/', (int) $item['port']);
  178. break;
  179. }
  180. }
  181. }
  182. }
  183. protected function normalizeFiles(array $multipart): array
  184. {
  185. $files = [];
  186. $fileSystem = $this->container->get(Filesystem::class);
  187. foreach ($multipart as $item) {
  188. if (isset($item['name'], $item['contents'], $item['filename'])) {
  189. $name = $item['name'];
  190. $contents = $item['contents'];
  191. $filename = $item['filename'];
  192. $dir = BASE_PATH . '/runtime/uploads';
  193. $tmpName = $dir . '/' . $filename;
  194. if (! is_dir($dir)) {
  195. $fileSystem->makeDirectory($dir);
  196. }
  197. $fileSystem->put($tmpName, $contents);
  198. $stats = fstat($contents);
  199. $files[$name] = new UploadedFile(
  200. $tmpName,
  201. $stats['size'],
  202. 0,
  203. $name
  204. );
  205. }
  206. }
  207. return $files;
  208. }
  209. protected function getStream(string $resource)
  210. {
  211. $stream = fopen('php://temp', 'r+');
  212. if ($resource !== '') {
  213. fwrite($stream, $resource);
  214. fseek($stream, 0);
  215. }
  216. return $stream;
  217. }
  218. }