Skip to content

响应

最后一步,获取响应。

php
public function onRequest($request, $response): void
{
    try {
        CoordinatorManager::until(Constants::WORKER_START)->yield();
        [$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
        $psr7Request = $this->coreMiddleware->dispatch($psr7Request);

        $this->option?->isEnableRequestLifecycle() && $this->event?->dispatch(new RequestReceived(
            request: $psr7Request,
            response: $psr7Response,
            server: $this->serverName
        ));

        /** @var Dispatched $dispatched */
        $dispatched = $psr7Request->getAttribute(Dispatched::class);
        $middlewares = $this->middlewares;

        $registeredMiddlewares = [];
        if ($dispatched->isFound()) {
            $registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
            $middlewares = array_merge($middlewares, $registeredMiddlewares);
        }

        if ($this->option?->isMustSortMiddlewares() || $registeredMiddlewares) {
            $middlewares = MiddlewareManager::sortMiddlewares($middlewares);
        }

        $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware); 
    } catch (Throwable $throwable) {
        // Delegate the exception to exception handler.
        $psr7Response = $this->container->get(SafeCaller::class)->call(function () use ($throwable) {
            return $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
        }, static function () {
            return (new Psr7Response())->withStatus(400);
        });
    } finally {
        if (isset($psr7Request) && $this->option?->isEnableRequestLifecycle()) {
            defer(fn () => $this->event?->dispatch(new RequestTerminated(
                request: $psr7Request,
                response: $psr7Response ?? null,
                exception: $throwable ?? null,
                server: $this->serverName
            )));

            $this->event?->dispatch(new RequestHandled(
                request: $psr7Request,
                response: $psr7Response ?? null,
                exception: $throwable ?? null,
                server: $this->serverName
            ));
        }

        // Send the Response to client.
        if (! isset($psr7Response) || ! $psr7Response instanceof ResponseInterface) {
            return;
        }
        if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
            $this->responseEmitter->emit($psr7Response, $response, false);
        } else {
            $this->responseEmitter->emit($psr7Response, $response);
        }
    }
}

$this->dispatcher调度器是在注册回调事件的时候实例化赋值得到的。

php
public function __construct(
    protected ContainerInterface $container,
    protected HttpDispatcher $dispatcher, 
    protected ExceptionHandlerDispatcher $exceptionHandlerDispatcher,
    protected ResponseEmitter $responseEmitter
) {
    if ($this->container->has(EventDispatcherInterface::class)) {
        $this->event = $this->container->get(EventDispatcherInterface::class);
    }
}

$this->coreMiddleware传入的是一个CoreMiddleware对象, $middlewares传入的是配置的中间件数组。

文件位置: /vendor/hyperf/dispatcher/src/HttpDispatcher.php

php
public function dispatch(...$params): ResponseInterface
{
    /**
     * 从参数中解析对应的三个变量(请求对象,中间件数组,核心
     * @param RequestInterface $request
     * @param array $middlewares
     * @param MiddlewareInterface $coreHandler
     */
    [$request, $middlewares, $coreHandler] = $params;
    // 实例化HttpRequestHandler处理器
    $requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);
    return $requestHandler->handle($request);
}

dispatch方法实例化HttpRequestHandler对象,并调用handle方法。

处理请求

php
$requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);

该类没有构造方法,在父类中定义了__construct方法,接收三个参数。

php
public function __construct(protected array $middlewares, protected $coreHandler, protected ContainerInterface $container)
{   // 将中间件数组的value取出
    $this->middlewares = array_values($this->middlewares);
}

实例化后调用handle方法。

php
public function handle(ServerRequestInterface $request): ResponseInterface
{
    return $this->handleRequest($request);
}

handleRequest方法,这里执行所有的中间件,全部执行完成之后,会执行CoreMiddleware对象中的process方法。

php
protected function handleRequest($request)
{
    // 如果中间件不存在,则执行CoreMiddleware对象中的process方法
    if (! isset($this->middlewares[$this->offset])) {
        $handler = $this->coreHandler;
    } else {
        // 中间件存在,取出并实例化中间件
        $handler = $this->middlewares[$this->offset];
        is_string($handler) && $handler = $this->container->get($handler);
    }
    // 如果中间件不存在或者没有process方法,则抛出异常
    if (! $handler || ! method_exists($handler, 'process')) {
        throw new InvalidArgumentException('Invalid middleware, it has to provide a process() method.');
    }
    // 调用中间件的process方法
    return $handler->process($request, $this->next());
}

protected function next(): self
{
    ++$this->offset;
    return $this;
}

当执行完一个中间件后,最后会返回return $handler->handle($request);,而$handler参数依旧是HttpRequestHandler对象。所以会将中间件以此调用对应的process方法,直到配置的中间件全部执行完成,然后调用CoresMiddleware中间件的process方法。

参考中间件
php
class CorsMiddleware implements MiddlewareInterface
{
    public function __construct(protected ContainerInterface $container)
    {
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $response = Context::get(ResponseInterface::class);
        $response = $response->withHeader('Access-Control-Allow-Origin', '*')
            ->withHeader('Access-Control-Allow-Credentials', 'true')
            // Headers 可以根据实际情况进行改写。
            ->withHeader('Access-Control-Allow-Headers', 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization');

        Context::set(ResponseInterface::class, $response);

        if ($request->getMethod() == 'OPTIONS') {
            return $response;
        }
        return $handler->handle($request); 
    }
}

核心中间件的process方法

文件位置: /vendor/hyperf/http-server/src/CoreMiddleware.php

该方法根据路由信息,获取响应内容,

php
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    // 将请求对象设置到上下文中
    $request = RequestContext::set($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 = match ($dispatched->status) {
        Dispatcher::NOT_FOUND => $this->handleNotFound($request), // 会抛出NotFoundHttpException异常
        Dispatcher::METHOD_NOT_ALLOWED => $this->handleMethodNotAllowed($dispatched->params, $request), // 会抛出MethodNotAllowedHttpException异常
        Dispatcher::FOUND => $this->handleFound($dispatched, $request),
        default => null,
    };

    if (! $response instanceof ResponsePlusInterface) {
        $response = $this->transferToResponse($response, $request);
    }
    // 添加服务器信息
    return $response->addHeader('Server', 'Hyperf');
}
路由不存在

抛出NotFoundException异常。

php
protected function handleNotFound(ServerRequestInterface $request): mixed
{
    throw new NotFoundHttpException();
}
方法不允许

抛出MethodNotAllowedHttpException异常。

php
protected function handleMethodNotAllowed(array $methods, ServerRequestInterface $request): mixed
{
    throw new MethodNotAllowedHttpException('Allow: ' . implode(', ', $methods));
}
匹配成功

dispatched对象结构如下,

如果匹配成功,则会执行handleFound方法,去执行路由中定义的方法或者闭包。

php
protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request): mixed
{   
    // 闭包情况
    if ($dispatched->handler->callback instanceof Closure) {
        $parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
        $callback = $dispatched->handler->callback;
        $response = $callback(...$parameters);
    } else {
        // 控制器情况
        [$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
        $controllerInstance = $this->container->get($controller);
        // 控制器方法不存在抛异常
        if (! method_exists($controllerInstance, $action)) {
            throw new ServerErrorHttpException('Method of class does not exist.');
        }
        // 解析控制器方法参数
        $parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
        // 调用控制器方法
        $response = $controllerInstance->{$action}(...$parameters);
    }
    // 返回响应
    return $response;
}
控制器类型分析

prepareHandler方法解析出控制器类和控制器方法。根据解析方式,可以使用四种定义方式,

php
Router::get('/array', [LoginController::class, 'chunk']);
Router::get('/str', 'App\Controller\StrController@index');
Router::get('/str2', 'App\Controller\StrController::index');
Router::get('/invoke', 'App\Controller\StrController');
php
protected function prepareHandler(string|array $handler): array
{
    if (is_string($handler)) {
        if (str_contains($handler, '@')) {
            return explode('@', $handler);
        }
        if (str_contains($handler, '::')) {
            return explode('::', $handler);
        }
        return [$handler, '__invoke'];
    }
    if (is_array($handler) && isset($handler[0], $handler[1])) {
        return $handler;
    }
    throw new RuntimeException('Handler not exist.');
}

解析出控制器实例,执行控制器方法。

php
$controllerInstance = $this->container->get($controller);
$parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
// 调用控制器方法
$response = $controllerInstance->{$action}(...$parameters)
闭包类型分析

解析闭包参数,调用闭包,返回响应。

php
$parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
$callback = $dispatched->handler->callback;
$response = $callback(...$parameters);

handleFound方法走完,获取到响应信息。

转换响应对象

如果响应对象不是ResponsePlusInterface接口的实现类,需要进行转换。

php
if (! $response instanceof ResponsePlusInterface) {
    $response = $this->transferToResponse($response, $request);
}

protected function transferToResponse($response, ServerRequestInterface $request): ResponsePlusInterface
{
    if (is_string($response)) {
        return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream($response));
    }

    if ($response instanceof ResponseInterface) {
        return new ResponsePlusProxy($response);
    }

    if (is_array($response) || $response instanceof Arrayable) {
        return $this->response()
            ->addHeader('content-type', 'application/json')
            ->setBody(new SwooleStream(Json::encode($response)));
    }

    if ($response instanceof Jsonable) {
        return $this->response()
            ->addHeader('content-type', 'application/json')
            ->setBody(new SwooleStream((string) $response));
    }

    if ($this->response()->hasHeader('content-type')) {
        return $this->response()->setBody(new SwooleStream((string) $response));
    }

    return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream((string) $response));
}
  1. 如果是字符串,则转换为文本类型的响应对象,并设置相应的头部信息和响应体
  2. 如果是ResponseInterface的实现类,则返回ResponsePlusProxy对象
  3. 如果是数组或者Arrayable的实现类,则转换为JSON类型的响应对象,并设置相应的头部信息和响应体
  4. 如果是实现了Jsonable的接口,则转换为JSON类型的响应对象,并设置相应的头部信息和响应体
  5. 如果响应已经设置了content-type头部信息,则将其转换为对应的响应对象
  6. 如果以上条件都不满足,则将其视为文本类型的响应对象

返回响应对象。

发送响应到客户端

前面已经完成对请求的处理工作,接下来需要将响应信息返回给客户端。

php
if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
    $this->responseEmitter->emit($psr7Response, $response, false);
} else {
    $this->responseEmitter->emit($psr7Response, $response);
}

emit方法

发送响应到客户端。

php
public function emit(ResponseInterface $response, mixed $connection, bool $withContent = true): void
{
  try {
      // 检查连接对象的 Upgrade 头部信息是否为 websocket,如果是,则直接返回,不发送响应内容
      if (strtolower($connection->header['Upgrade'] ?? '') === 'websocket') {
          return;
      }
      // 将响应对象转换为 Swoole 响应对象
      $this->buildSwooleResponse($connection, $response);
      // 获取响应对象的主体内容
      $content = $response->getBody();
      // 如果主体内容是文件,则使用 sendfile 方法发送文件内容
      if ($content instanceof FileInterface) {
          $connection->sendfile($content->getFilename());
          return;
      }
      // 是否发送响应内容
      if ($withContent) {
          $connection->end((string) $content);
      } else {
          $connection->end();
      }
  } catch (Throwable $exception) {
      $this->logger?->critical((string) $exception);
  }
}
将响应转换为swoole的响应对象
php
protected function buildSwooleResponse(Response $swooleResponse, ResponseInterface $response): void
{
    // Headers
    foreach ($response->getHeaders() as $key => $value) {
        $swooleResponse->header($key, $value);
    }

    // Cookies
    // This part maybe only supports of hyperf/http-message component.
    if (method_exists($response, 'getCookies')) {
        foreach ((array) $response->getCookies() as $domain => $paths) {
            foreach ($paths ?? [] as $path => $item) {
                foreach ($item ?? [] as $name => $cookie) {
                    if ($this->isMethodsExists($cookie, [
                        'isRaw', 'getValue', 'getName', 'getExpiresTime', 'getPath', 'getDomain', 'isSecure', 'isHttpOnly', 'getSameSite',
                    ])) {
                        $value = $cookie->isRaw() ? $cookie->getValue() : rawurlencode($cookie->getValue());
                        $swooleResponse->rawcookie($cookie->getName(), $value, $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly(), (string) $cookie->getSameSite());
                    }
                }
            }
        }
    }

    // Trailers
    if (method_exists($response, 'getTrailers') && method_exists($swooleResponse, 'trailer')) {
        foreach ($response->getTrailers() ?? [] as $key => $value) {
            $swooleResponse->trailer($key, $value);
        }
    }

    // Status code
    $swooleResponse->status($response->getStatusCode(), $response->getReasonPhrase());
}

异常捕获

当在代码中出现异常时,会被catch住,然后通过SafeCaller类来调用配置的异常处理器。

php
try {
   CoordinatorManager::until(Constants::WORKER_START)->yield();
   [$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
   $psr7Request = $this->coreMiddleware->dispatch($psr7Request);

   $this->option?->isEnableRequestLifecycle() && $this->event?->dispatch(new RequestReceived(
       request: $psr7Request,
       response: $psr7Response,
       server: $this->serverName
   ));

   /** @var Dispatched $dispatched */
   $dispatched = $psr7Request->getAttribute(Dispatched::class);
   $middlewares = $this->middlewares;

   $registeredMiddlewares = [];
   if ($dispatched->isFound()) {
       $registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
       $middlewares = array_merge($middlewares, $registeredMiddlewares);
   }

   if ($this->option?->isMustSortMiddlewares() || $registeredMiddlewares) {
       $middlewares = MiddlewareManager::sortMiddlewares($middlewares);
   }

   $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
} catch (Throwable $throwable) { 
   // Delegate the exception to exception handler.
   $psr7Response = $this->container->get(SafeCaller::class)->call(function () use ($throwable) {
       return $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
   }, static function () {
       return (new Psr7Response())->withStatus(400);
   });
} finally {
   if (isset($psr7Request) && $this->option?->isEnableRequestLifecycle()) {
       defer(fn () => $this->event?->dispatch(new RequestTerminated(
           request: $psr7Request,
           response: $psr7Response ?? null,
           exception: $throwable ?? null,
           server: $this->serverName
       )));

       $this->event?->dispatch(new RequestHandled(
           request: $psr7Request,
           response: $psr7Response ?? null,
           exception: $throwable ?? null,
           server: $this->serverName
       ));
   }

   // Send the Response to client.
   if (! isset($psr7Response) || ! $psr7Response instanceof ResponseInterface) {
       return;
   }
   if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
       $this->responseEmitter->emit($psr7Response, $response, false);
   } else {
       $this->responseEmitter->emit($psr7Response, $response);
   }
}

call方法会执行闭包里的内容,如果执行闭包发生错误,则捕获异常并输出日志,返回默认400状态码。

php

public function call(Closure $closure, ?Closure $default = null, string $level = LogLevel::CRITICAL): mixed
{
    try {
        return $closure();
    } catch (Throwable $exception) {
        if ($this->container->has(StdoutLoggerInterface::class) && $logger = $this->container->get(StdoutLoggerInterface::class)) {
            $logger->log($level, (string) $exception);
        }
    }

    return value($default);
}