前文讲到当收到请求后,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事件,以后本次请求正式结束,当前请求协程的生命周期也结束了.