123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- <?php
- declare(strict_types=1);
- /**
- * This file is part of Hyperf.
- *
- * @link https://www.hyperf.io
- * @document https://hyperf.wiki
- * @contact group@hyperf.io
- * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
- */
- namespace Hyperf\HttpServer;
- use Closure;
- use FastRoute\Dispatcher;
- use Hyperf\Contract\NormalizerInterface;
- use Hyperf\Di\ClosureDefinitionCollectorInterface;
- use Hyperf\Di\MethodDefinitionCollectorInterface;
- use Hyperf\HttpMessage\Exception\MethodNotAllowedHttpException;
- use Hyperf\HttpMessage\Exception\NotFoundHttpException;
- use Hyperf\HttpMessage\Exception\ServerErrorHttpException;
- use Hyperf\HttpMessage\Stream\SwooleStream;
- use Hyperf\HttpServer\Contract\CoreMiddlewareInterface;
- use Hyperf\HttpServer\Router\Dispatched;
- use Hyperf\HttpServer\Router\DispatcherFactory;
- use Hyperf\Server\Exception\ServerException;
- use Hyperf\Utils\Codec\Json;
- use Hyperf\Utils\Context;
- use Hyperf\Utils\Contracts\Arrayable;
- use Hyperf\Utils\Contracts\Jsonable;
- use Psr\Container\ContainerInterface;
- use Psr\Http\Message\ResponseInterface;
- use Psr\Http\Message\ServerRequestInterface;
- use Psr\Http\Server\RequestHandlerInterface;
- /**
- * Core middleware of Hyperf, main responsibility is use to handle route info
- * and then delegate to the specified handler (which is Controller) to handle the request,
- * generate a response object and delegate to next middleware (Because this middleware is the
- * core middleware, then the next middleware also means it's the previous middlewares object) .
- */
- class CoreMiddleware implements CoreMiddlewareInterface
- {
- /**
- * @var Dispatcher
- */
- protected $dispatcher;
- /**
- * @var ContainerInterface
- */
- protected $container;
- /**
- * @var MethodDefinitionCollectorInterface
- */
- private $methodDefinitionCollector;
- /**
- * @var null|ClosureDefinitionCollectorInterface
- */
- private $closureDefinitionCollector;
- /**
- * @var NormalizerInterface
- */
- private $normalizer;
- public function __construct(ContainerInterface $container, string $serverName)
- {
- $this->container = $container;
- $this->dispatcher = $this->createDispatcher($serverName);
- $this->normalizer = $this->container->get(NormalizerInterface::class);
- $this->methodDefinitionCollector = $this->container->get(MethodDefinitionCollectorInterface::class);
- if ($this->container->has(ClosureDefinitionCollectorInterface::class)) {
- $this->closureDefinitionCollector = $this->container->get(ClosureDefinitionCollectorInterface::class);
- }
- }
- public function dispatch(ServerRequestInterface $request): ServerRequestInterface
- {
- $routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath());
- $dispatched = new Dispatched($routes);
- return Context::set(ServerRequestInterface::class, $request->withAttribute(Dispatched::class, $dispatched));
- }
- /**
- * Process an incoming server request and return a response, optionally delegating
- * response creation to a handler.
- */
- public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
- {
- $request = Context::set(ServerRequestInterface::class, $request);
- /** @var Dispatched $dispatched */
- $dispatched = $request->getAttribute(Dispatched::class);
- if (! $dispatched instanceof Dispatched) {
- throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
- }
- $response = null;
- switch ($dispatched->status) {
- case Dispatcher::NOT_FOUND:
- $response = $this->handleNotFound($request);
- break;
- case Dispatcher::METHOD_NOT_ALLOWED:
- $response = $this->handleMethodNotAllowed($dispatched->params, $request);
- break;
- case Dispatcher::FOUND:
- $response = $this->handleFound($dispatched, $request);
- break;
- }
- if (! $response instanceof ResponseInterface) {
- $response = $this->transferToResponse($response, $request);
- }
- return $response->withAddedHeader('Server', 'Hyperf');
- }
- public function getMethodDefinitionCollector(): MethodDefinitionCollectorInterface
- {
- return $this->methodDefinitionCollector;
- }
- public function getClosureDefinitionCollector(): ClosureDefinitionCollectorInterface
- {
- return $this->closureDefinitionCollector;
- }
- public function getNormalizer(): NormalizerInterface
- {
- return $this->normalizer;
- }
- protected function createDispatcher(string $serverName): Dispatcher
- {
- $factory = $this->container->get(DispatcherFactory::class);
- return $factory->getDispatcher($serverName);
- }
- /**
- * Handle the response when found.
- *
- * @return array|Arrayable|mixed|ResponseInterface|string
- */
- protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request)
- {
- if ($dispatched->handler->callback instanceof Closure) {
- $parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
- $response = call($dispatched->handler->callback, $parameters);
- } else {
- [$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
- $controllerInstance = $this->container->get($controller);
- if (! method_exists($controllerInstance, $action)) {
- // Route found, but the handler does not exist.
- throw new ServerErrorHttpException('Method of class does not exist.');
- }
- $parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
- $response = $controllerInstance->{$action}(...$parameters);
- }
- return $response;
- }
- /**
- * Handle the response when cannot found any routes.
- *
- * @return array|Arrayable|mixed|ResponseInterface|string
- */
- protected function handleNotFound(ServerRequestInterface $request)
- {
- throw new NotFoundHttpException();
- }
- /**
- * Handle the response when the routes found but doesn't match any available methods.
- *
- * @return array|Arrayable|mixed|ResponseInterface|string
- */
- protected function handleMethodNotAllowed(array $methods, ServerRequestInterface $request)
- {
- throw new MethodNotAllowedHttpException('Allow: ' . implode(', ', $methods));
- }
- /**
- * @param array|string $handler
- */
- protected function prepareHandler($handler): array
- {
- if (is_string($handler)) {
- if (strpos($handler, '@') !== false) {
- return explode('@', $handler);
- }
- return explode('::', $handler);
- }
- if (is_array($handler) && isset($handler[0], $handler[1])) {
- return $handler;
- }
- throw new \RuntimeException('Handler not exist.');
- }
- /**
- * Transfer the non-standard response content to a standard response object.
- *
- * @param null|array|Arrayable|Jsonable|string $response
- */
- protected function transferToResponse($response, ServerRequestInterface $request): ResponseInterface
- {
- if (is_string($response)) {
- return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream($response));
- }
- if (is_array($response) || $response instanceof Arrayable) {
- return $this->response()
- ->withAddedHeader('content-type', 'application/json')
- ->withBody(new SwooleStream(Json::encode($response)));
- }
- if ($response instanceof Jsonable) {
- return $this->response()
- ->withAddedHeader('content-type', 'application/json')
- ->withBody(new SwooleStream((string) $response));
- }
- return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream((string) $response));
- }
- /**
- * Get response instance from context.
- */
- protected function response(): ResponseInterface
- {
- return Context::get(ResponseInterface::class);
- }
- /**
- * Parse the parameters of method definitions, and then bind the specified arguments or
- * get the value from DI container, combine to a argument array that should be injected
- * and return the array.
- */
- protected function parseMethodParameters(string $controller, string $action, array $arguments): array
- {
- $definitions = $this->getMethodDefinitionCollector()->getParameters($controller, $action);
- return $this->getInjections($definitions, "{$controller}::{$action}", $arguments);
- }
- /**
- * Parse the parameters of closure definitions, and then bind the specified arguments or
- * get the value from DI container, combine to a argument array that should be injected
- * and return the array.
- */
- protected function parseClosureParameters(Closure $closure, array $arguments): array
- {
- if (! $this->container->has(ClosureDefinitionCollectorInterface::class)) {
- return [];
- }
- $definitions = $this->getClosureDefinitionCollector()->getParameters($closure);
- return $this->getInjections($definitions, 'Closure', $arguments);
- }
- private function getInjections(array $definitions, string $callableName, array $arguments): array
- {
- $injections = [];
- foreach ($definitions ?? [] as $pos => $definition) {
- $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? null;
- if ($value === null) {
- if ($definition->getMeta('defaultValueAvailable')) {
- $injections[] = $definition->getMeta('defaultValue');
- } elseif ($definition->allowsNull()) {
- $injections[] = null;
- } elseif ($this->container->has($definition->getName())) {
- $injections[] = $this->container->get($definition->getName());
- } else {
- throw new \InvalidArgumentException("Parameter '{$definition->getMeta('name')}' "
- . "of {$callableName} should not be null");
- }
- } else {
- $injections[] = $this->getNormalizer()->denormalize($value, $definition->getName());
- }
- }
- return $injections;
- }
- }
|