CoreMiddleware.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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\HttpServer;
  12. use Closure;
  13. use FastRoute\Dispatcher;
  14. use Hyperf\Contract\NormalizerInterface;
  15. use Hyperf\Di\ClosureDefinitionCollectorInterface;
  16. use Hyperf\Di\MethodDefinitionCollectorInterface;
  17. use Hyperf\HttpMessage\Exception\MethodNotAllowedHttpException;
  18. use Hyperf\HttpMessage\Exception\NotFoundHttpException;
  19. use Hyperf\HttpMessage\Exception\ServerErrorHttpException;
  20. use Hyperf\HttpMessage\Stream\SwooleStream;
  21. use Hyperf\HttpServer\Contract\CoreMiddlewareInterface;
  22. use Hyperf\HttpServer\Router\Dispatched;
  23. use Hyperf\HttpServer\Router\DispatcherFactory;
  24. use Hyperf\Server\Exception\ServerException;
  25. use Hyperf\Utils\Codec\Json;
  26. use Hyperf\Utils\Context;
  27. use Hyperf\Utils\Contracts\Arrayable;
  28. use Hyperf\Utils\Contracts\Jsonable;
  29. use Psr\Container\ContainerInterface;
  30. use Psr\Http\Message\ResponseInterface;
  31. use Psr\Http\Message\ServerRequestInterface;
  32. use Psr\Http\Server\RequestHandlerInterface;
  33. /**
  34. * Core middleware of Hyperf, main responsibility is use to handle route info
  35. * and then delegate to the specified handler (which is Controller) to handle the request,
  36. * generate a response object and delegate to next middleware (Because this middleware is the
  37. * core middleware, then the next middleware also means it's the previous middlewares object) .
  38. */
  39. class CoreMiddleware implements CoreMiddlewareInterface
  40. {
  41. /**
  42. * @var Dispatcher
  43. */
  44. protected $dispatcher;
  45. /**
  46. * @var ContainerInterface
  47. */
  48. protected $container;
  49. /**
  50. * @var MethodDefinitionCollectorInterface
  51. */
  52. private $methodDefinitionCollector;
  53. /**
  54. * @var null|ClosureDefinitionCollectorInterface
  55. */
  56. private $closureDefinitionCollector;
  57. /**
  58. * @var NormalizerInterface
  59. */
  60. private $normalizer;
  61. public function __construct(ContainerInterface $container, string $serverName)
  62. {
  63. $this->container = $container;
  64. $this->dispatcher = $this->createDispatcher($serverName);
  65. $this->normalizer = $this->container->get(NormalizerInterface::class);
  66. $this->methodDefinitionCollector = $this->container->get(MethodDefinitionCollectorInterface::class);
  67. if ($this->container->has(ClosureDefinitionCollectorInterface::class)) {
  68. $this->closureDefinitionCollector = $this->container->get(ClosureDefinitionCollectorInterface::class);
  69. }
  70. }
  71. public function dispatch(ServerRequestInterface $request): ServerRequestInterface
  72. {
  73. $routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath());
  74. $dispatched = new Dispatched($routes);
  75. return Context::set(ServerRequestInterface::class, $request->withAttribute(Dispatched::class, $dispatched));
  76. }
  77. /**
  78. * Process an incoming server request and return a response, optionally delegating
  79. * response creation to a handler.
  80. */
  81. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  82. {
  83. $request = Context::set(ServerRequestInterface::class, $request);
  84. /** @var Dispatched $dispatched */
  85. $dispatched = $request->getAttribute(Dispatched::class);
  86. if (! $dispatched instanceof Dispatched) {
  87. throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
  88. }
  89. $response = null;
  90. switch ($dispatched->status) {
  91. case Dispatcher::NOT_FOUND:
  92. $response = $this->handleNotFound($request);
  93. break;
  94. case Dispatcher::METHOD_NOT_ALLOWED:
  95. $response = $this->handleMethodNotAllowed($dispatched->params, $request);
  96. break;
  97. case Dispatcher::FOUND:
  98. $response = $this->handleFound($dispatched, $request);
  99. break;
  100. }
  101. if (! $response instanceof ResponseInterface) {
  102. $response = $this->transferToResponse($response, $request);
  103. }
  104. return $response->withAddedHeader('Server', 'Hyperf');
  105. }
  106. public function getMethodDefinitionCollector(): MethodDefinitionCollectorInterface
  107. {
  108. return $this->methodDefinitionCollector;
  109. }
  110. public function getClosureDefinitionCollector(): ClosureDefinitionCollectorInterface
  111. {
  112. return $this->closureDefinitionCollector;
  113. }
  114. public function getNormalizer(): NormalizerInterface
  115. {
  116. return $this->normalizer;
  117. }
  118. protected function createDispatcher(string $serverName): Dispatcher
  119. {
  120. $factory = $this->container->get(DispatcherFactory::class);
  121. return $factory->getDispatcher($serverName);
  122. }
  123. /**
  124. * Handle the response when found.
  125. *
  126. * @return array|Arrayable|mixed|ResponseInterface|string
  127. */
  128. protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request)
  129. {
  130. if ($dispatched->handler->callback instanceof Closure) {
  131. $parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
  132. $response = call($dispatched->handler->callback, $parameters);
  133. } else {
  134. [$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
  135. $controllerInstance = $this->container->get($controller);
  136. if (! method_exists($controllerInstance, $action)) {
  137. // Route found, but the handler does not exist.
  138. throw new ServerErrorHttpException('Method of class does not exist.');
  139. }
  140. $parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
  141. $response = $controllerInstance->{$action}(...$parameters);
  142. }
  143. return $response;
  144. }
  145. /**
  146. * Handle the response when cannot found any routes.
  147. *
  148. * @return array|Arrayable|mixed|ResponseInterface|string
  149. */
  150. protected function handleNotFound(ServerRequestInterface $request)
  151. {
  152. throw new NotFoundHttpException();
  153. }
  154. /**
  155. * Handle the response when the routes found but doesn't match any available methods.
  156. *
  157. * @return array|Arrayable|mixed|ResponseInterface|string
  158. */
  159. protected function handleMethodNotAllowed(array $methods, ServerRequestInterface $request)
  160. {
  161. throw new MethodNotAllowedHttpException('Allow: ' . implode(', ', $methods));
  162. }
  163. /**
  164. * @param array|string $handler
  165. */
  166. protected function prepareHandler($handler): array
  167. {
  168. if (is_string($handler)) {
  169. if (strpos($handler, '@') !== false) {
  170. return explode('@', $handler);
  171. }
  172. return explode('::', $handler);
  173. }
  174. if (is_array($handler) && isset($handler[0], $handler[1])) {
  175. return $handler;
  176. }
  177. throw new \RuntimeException('Handler not exist.');
  178. }
  179. /**
  180. * Transfer the non-standard response content to a standard response object.
  181. *
  182. * @param null|array|Arrayable|Jsonable|string $response
  183. */
  184. protected function transferToResponse($response, ServerRequestInterface $request): ResponseInterface
  185. {
  186. if (is_string($response)) {
  187. return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream($response));
  188. }
  189. if (is_array($response) || $response instanceof Arrayable) {
  190. return $this->response()
  191. ->withAddedHeader('content-type', 'application/json')
  192. ->withBody(new SwooleStream(Json::encode($response)));
  193. }
  194. if ($response instanceof Jsonable) {
  195. return $this->response()
  196. ->withAddedHeader('content-type', 'application/json')
  197. ->withBody(new SwooleStream((string) $response));
  198. }
  199. return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream((string) $response));
  200. }
  201. /**
  202. * Get response instance from context.
  203. */
  204. protected function response(): ResponseInterface
  205. {
  206. return Context::get(ResponseInterface::class);
  207. }
  208. /**
  209. * Parse the parameters of method definitions, and then bind the specified arguments or
  210. * get the value from DI container, combine to a argument array that should be injected
  211. * and return the array.
  212. */
  213. protected function parseMethodParameters(string $controller, string $action, array $arguments): array
  214. {
  215. $definitions = $this->getMethodDefinitionCollector()->getParameters($controller, $action);
  216. return $this->getInjections($definitions, "{$controller}::{$action}", $arguments);
  217. }
  218. /**
  219. * Parse the parameters of closure definitions, and then bind the specified arguments or
  220. * get the value from DI container, combine to a argument array that should be injected
  221. * and return the array.
  222. */
  223. protected function parseClosureParameters(Closure $closure, array $arguments): array
  224. {
  225. if (! $this->container->has(ClosureDefinitionCollectorInterface::class)) {
  226. return [];
  227. }
  228. $definitions = $this->getClosureDefinitionCollector()->getParameters($closure);
  229. return $this->getInjections($definitions, 'Closure', $arguments);
  230. }
  231. private function getInjections(array $definitions, string $callableName, array $arguments): array
  232. {
  233. $injections = [];
  234. foreach ($definitions ?? [] as $pos => $definition) {
  235. $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? null;
  236. if ($value === null) {
  237. if ($definition->getMeta('defaultValueAvailable')) {
  238. $injections[] = $definition->getMeta('defaultValue');
  239. } elseif ($definition->allowsNull()) {
  240. $injections[] = null;
  241. } elseif ($this->container->has($definition->getName())) {
  242. $injections[] = $this->container->get($definition->getName());
  243. } else {
  244. throw new \InvalidArgumentException("Parameter '{$definition->getMeta('name')}' "
  245. . "of {$callableName} should not be null");
  246. }
  247. } else {
  248. $injections[] = $this->getNormalizer()->denormalize($value, $definition->getName());
  249. }
  250. }
  251. return $injections;
  252. }
  253. }