Client.php 9.1 KB

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