Swoft的HttpServer启动及请求工作流程(四)--onRequest中的调度(请求的处理及返回)

马尔科夫尼可夫

前文讲到当收到请求后,swoft将swoole原生的Request及Response对象封装成适合swoft框架内部调用的Swoft\Http\Message\Request以及Swoft\Http\Message\Response.
接下来,本章将跟随方法$this->dispatcher->dispatch($psrRequest, $psrResponse)逐步分析请求到来后的框架的调度过程.

先看Swoft\Http\Server\HttpDispatcher的实现:

public function dispatch(...$params): void
{
     /**
     * @var Request  $request
     * @var Response $response
     */ [$request, $response] = $params;
     $response = $this->configResponse($response);
     /* @var RequestHandler $requestHandler */
     $requestHandler = Swoft::getBean(RequestHandler::class);
     try {
        //初始化中间件
        $requestHandler->initialize($this->requestMiddlewares, $this->defaultMiddleware);
        
         // 创建新的HttpContext并设置到Context中
         // 在业务逻辑中获取到的Context就是这里设置的HttpContext
         // 后面附上此方法的源码调用
         // Before request
         $this->beforeRequest($request, $response);
         
         // 触发BEFORE_REQUEST事件
         // Trigger before handle event
         Swoft::trigger(HttpServerEvent::BEFORE_REQUEST, null, $request, $response);
         
         // 匹配路由,将路由信息绑定在新的Request对象上,返回
         // Match router and handle
         $request = $this->matchRouter($request);
         
         // 调用handle处理请求,实际上就是处理中间件
         // 控制器的执行也是放在中间件中执行的
         // Swoft\Http\Server\Middleware\DefaultMiddleware
         $response = $requestHandler->handle($request);
     } catch (Throwable $e) {
         // 在处理请求时若发送异常会被系统在此处捕获
         // 然后调用HttpErrorDispatcher去处理对应的异常
         // 我们在业务中注册的异常处理类就是在此处得到执行
         /** @var HttpErrorDispatcher $errDispatcher */
         $errDispatcher = Swoft::getSingleton(HttpErrorDispatcher::class);
         // Handle request error
         $response = $errDispatcher->run($e, $response);
     }
     try {
         // 调用格式化处理对象来格式化得到的response
         // Format response content type
         $response = $this->acceptFormatter->format($response);
         
         // 触发AFTER_REQUEST事件
         // Trigger after request
         Swoft::trigger(HttpServerEvent::AFTER_REQUEST, null, $response);
         
         // 返回内容给客户端
         // 触发协程COROUTINE_DEFER和COROUTINE_COMPLETE事件,后面附代码
         // After request
         $this->afterRequest($response);
     } catch (Throwable $e) {
         // 此步骤出现错误,则表示未能将内容正常返回给客户端
         // 需要写入error级别的错误,控制台会有error内容打印
         // 如果是协程环境(request周期内就是协程环境)
         // 还会写入日志
         Error::log('response error=%s(%d) at %s:%d', $e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
     }
}

方法使用的是php中的动态参数传递方式,前文说过,此方法当前获取的request及response变量是Swoft\Http\Message\Request以及Swoft\Http\Message\Response的实例.

beforeRequest源码(创建HttpContext):

private function beforeRequest(Request $request, Response $response): void
{
     $httpContext = HttpContext::new($request, $response);
     // Add log data
     if ($this->logger->isEnable()) {
         $data = [
             'event' => SwooleEvent::REQUEST,
             'uri' => $request->getRequestTarget(),
             'requestTime' => $request->getRequestTime(),
         ];
         $httpContext->setMulti($data);
     }
     Context::set($httpContext);
}

HttpContext::new源码(self::__instance()实际上是获取的bean对象,bean的注解是@Bean(scope=Bean::PROTOTYPE)):

public static function new(Request $request, Response $response): self
{
     $instance = self::__instance();
     $instance->request = $request;
     $instance->response = $response;
     return $instance;
}

路由匹配代码:

private function matchRouter(Request $request): Request
{
     $method = $request->getMethod();
     $uriPath = $request->getUriPath();
     /** @var Router $router */
     $router = Swoft::getSingleton('httpRouter');
     $result = $router->match($uriPath, $method);
     // Save matched route data to request
     $request = $request->withAttribute(Request::ROUTER_ATTRIBUTE, $result);
     context()->setRequest($request);
     return $request;
}

afterRequest代码:

private function afterRequest(Response $response): void
{
     // 后附代码
     $response->send();
     // Defer
     Swoft::trigger(SwoftEvent::COROUTINE_DEFER);
     // Destroy
     Swoft::trigger(SwoftEvent::COROUTINE_COMPLETE);
}

send代码:

public function send(): void
{
     // 是否发送文件
     // Is send file
     if ($this->filePath) {
         // 修改发送状态为true
         $this->sent = true;
         // 写入header
         // Write Headers to co response
         foreach ($this->getHeaders() as $key => $value) {
             $headerLine = implode(';', $value);
             if ($key !== ContentType::KEY) {
                $this->coResponse->header($key, $headerLine);
             }
         }
         
         // Do send file
         $this->coResponse->header(ContentType::KEY, $this->fileType);
         // 发送文件
         $this->coResponse->sendfile($this->filePath);
         return; 
     }
     
     // 格式化返回内容,并发送
     // Prepare and send
     $this->quickSend($this->prepare());
}

quickSend代码,此方法主要功能是将Swoft的Response对象通过请求处理完成获得的业务数据重新设置回swoole原生的Response对象,并调用原生Response对象的end方法返回数据给客户端:

public function quickSend(Response $response = null): void
{
     $response = $response ?: $this;
     
     // 获取swoole原生Response对象
     // 后续的设置和返回都是通过原生Response对象完成
     // Ensure coResponse is right
     $coResponse = $response->getCoResponse();
     
     // 设置返回的headers
     // Write Headers to co response
     foreach ($response->getHeaders() as $key => $value) {
         $headerLine = implode(';', $value);
         if ($key === ContentType::KEY) {
            $headerLine .= '; charset=' . $response->getCharset();
            $coResponse->header($key, $headerLine, $this->headerUcWords);
         } else {
            $coResponse->header($key, $headerLine, $this->headerUcWords);
         }
     }
     
     // 设置返回的COOKIES
     // Write cookies
     foreach ($response->cookies as $n => $c) {
        $coResponse->cookie($n, $c['value'], $c['expires'], $c['path'], $c['domain'], $c['secure'], $c['httpOnly']);
     }
     
     // 设置返回的状态码
     // Set status code
     $coResponse->status($response->getStatusCode());
     
     // 获取返回的body
     // Set body
     $content = $response->getBody()->getContents();
     
     // 调用swoole的Response对象的end方法,发送数据给客户端
     $coResponse->end($content);
     
     // 修改发送状态为true
     // 此属性是在创建Response对象是初始化为false的
     // Ensure sent
     $this->sent = true;
}

总结:

1.请求的业务逻辑是在系统注册的Swoft\Http\Server\Middleware\DefaultMiddleware中间件中得到执行的.
2.请求的业务逻辑是包裹在try/catch块中执行的,出现异常会调用系统和用户注册的异常处理handler.
  该handler会返回一个Response对象,正常执行的中间件返回的Response对象将会被丢弃.
  这也是swoft业务逻辑中出现异常后,跨域中间件无法正常设置header的原因.
  关于此点的处理方式请参考本人之前的文章[swoft中跨域设置的问题](https://segmentfault.com/a/1190000038411563)
3.swoft返回数据的方式是将业务返回的Swoft\Http\Message\Response上携带的像headers,cookies,body等内容重新设置回swoole原生Response对象上,然后调用原生Response对象返回业务数据.
4.在执行完发送动作后,swoft会触发协程的deffer和finish事件,之后本次请求正式结束,当前请求协程的生命周期也结束了.
阅读 370

酷白发,小酒窝,主角标配的帅小伙~

7 声望
2 粉丝
0 条评论
你知道吗?

酷白发,小酒窝,主角标配的帅小伙~

7 声望
2 粉丝
宣传栏