Swoft的HttpServer啓動及請求工做流程(四)--onRequest中的調度(請求的處理及返回)

前文講到當收到請求後,swoft將swoole原生的Request及Response對象封裝成適合swoft框架內部調用的Swoft\Http\Message\Request以及Swoft\Http\Message\Response.
接下來,本章將跟隨方法$this->dispatcher->dispatch($psrRequest, $psrResponse)逐步分析請求到來後的框架的調度過程.php

先看Swoft\Http\Server\HttpDispatcher的實現:segmentfault

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):swoole

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)):cookie

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代碼:dom

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方法返回數據給客戶端:ui

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;
}

總結:this

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事件,以後本次請求正式結束,當前請求協程的生命週期也結束了.
相關文章
相關標籤/搜索