当前位置: 首页 > 后端技术 > PHP

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

时间:2023-03-29 19:38:00 PHP

上面提到,swoft在收到请求时,将swoole原有的Request和Response对象封装成Swoft\Http\Message\Request和Swoft\Http\Message\Response适合swoft框架内部调用。接下来本章将按照方法$this->dispatcher->dispatch($psrRequest,$psrResponse)逐步分析请求到达后框架的调度过程。先看Swoft\Http\Server\HttpDispatcher的实现:publicfunctiondispatch(...$params):void{/***@varRequest$request*@varResponse$response*/[$request,$response]=$参数;$response=$this->configResponse($response);/*@varRequestHandler$requestHandler*/$requestHandler=Swoft::getBean(RequestHandler::class);try{//初始化中间件$requestHandler->initialize($this->requestMiddlewares,$this->defaultMiddleware);//新建一个HttpContext并设置为Context中//业务逻辑中获取的Context就是这里设置的HttpContext//后面附上该方法的源码调用//请求前$this->beforeRequest($request,$响应);//触发BEFORE_REQUEST事件//在处理事件之前触发Swoft::trigger(HttpServerEvent::BEFORE_REQUEST,null,$request,$response);//匹配路由,将路由信息绑定到新的Request对象上,返回//匹配路由并处理$request=$this->matchRouter($request);//调用handle处理请求其实就是在处理中间件//controller的执行也是在中间件中执行的//Swoft\Http\Server\Middleware\DefaultMiddleware$response=$requestHandler->handle($request);}catch(Throwable$e){//如果在处理请求的过程中发送了异常,这里会被系统捕获//然后调用HttpErrorDispatcher来处理相应的异常//我们在业务中注册的异常处理类在这里执行/**@varHttpErrorDispatcher$errDispatcher*/$errDispatcher=Swoft::getSingleton(HttpErrorDispatcher::class);//处理请求错误$response=$errDispatcher->run($e,$response);}try{//调用格式化处理对象格式化响应//格式化响应内容类型$response=$this->acceptFormatter->format($response);//触发AFTER_REQUEST事件Item//请求后触发Swoft::trigger(HttpServerEvent::AFTER_REQUEST,null,$response);//返回内容给客户端//触发协程COROUTINE_DEFER和COROUTINE_COMPLETE事件,后面是代码//Afterrequest$this->afterRequest($response);}catch(Throwable$e){//如果这一步出错,说明无法正常返回内容给客户端//需要在错误级别写入错误,控制台会打印errorcontent//如果是协程环境(请求周期内是协程环境)//也会写入日志Error::log('responseerror=%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):privatefunctionbeforeRequest(Request$request,Response$response):void{$httpContext=HttpContext::new($request,$response);//添加日志数据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)):publicstaticfunction新(请求$request,响应$response):self{$instance=self::__instance();$实例->请求=$请求;$instance->response=$response;return$instance;}路径由配对代码:privatefunctionmatchRouter(Request$request):Request{$method=$request->getMethod();$uriPath=$request->getUriPath();/**@varRouter$router*/$router=Swoft::getSingleton('httpRouter');$result=$router->match($uriPath,$method);//保存匹配的路由数据到请求$request=$request->withAttribute(Request::ROUTER_ATTRIBUTE,$result);context()->setRequest($request);返回n$request;}afterRequestcode:privatefunctionafterRequest(Response$response):void{//附加代码$response->send();//延迟Swoft::trigger(SwoftEvent::COROUTINE_DEFER);//销毁Swoft::trigger(SwoftEvent::COROUTINE_COMPLETE);}发送代码:publicfunctionsend():void{//是否发送文件//是否发送文件if($this->filePath){//改变发送状态为真$this->sent=true;//writeheader//WriteHeaderstocoresponseforeach($this->getHeaders()as$key=>$value){$headerLine=implode(';',$value);}如果($key!==ContentType::KEY){$this->coResponse->header($key,$headerLine);}}//发送文件$this->coResponse->header(ContentType::KEY,$this->fileType);//发送文件$this->coResponse->sendfile($this->filePath);返回;}//格式化返回内容,并发送//P修复发送$this->quickSend($this->prepare());}quickSend代码,该方法主要作用是将Swoft的Response对象通过请求处理得到的业务数据重置回swoole的原始Response对象,以及调用原生Response对象的end方法返回数据给客户端:publicfunctionquickSend(Response$response=null):void{$response=$response?:$this;//获取swoole原生的Response对象//后续的设置和返回都是通过原生的Response对象完成的//确保coResponse正确$coResponse=$response->getCoResponse();//设置返回的标头//将标头写入coresponseforeach($response->getHeaders()as$key=>$value){$headerLine=implode(';',$value);如果($key===ContentType::KEY){$headerLine.=';字符集='。$response->getCharset();$coResponse->header($key,$headerLine,$this->headerUcWords);}else{$coResponse->header($key,$headerLine,$this->headerUcWords);}}//设置返回的COOKIES//写cookiesforeach($response->cookiesas$n=>$c){$coResponse->cookie($n,$c['value'],$c['expires'],$c['path'],$c['domain'],$c['安全'],$c['httpOnly']);}//设置返回的状态码//设置状态码$coResponse->status($response->getStatusCode());//获取返回主体//设置主体$content=$response->getBody()->getContents();//调用swoole的Response对象的end方法,向客户端发送数据$coResponse->end($content);//修改发送状态为true//该属性在创建Response对象时初始化为false//确保发送$this->sent=true;}总结:1.请求的业务逻辑在系统Swoft中注册\Http\Server\Middleware\DefaultMiddleware在中间件中执行。2.请求的业务逻辑被包裹在一个try/catch块中并被执行。如果发生异常,将调用系统和用户注册的异常处理handler。handler会返回一个Response对象,正常情况下执行的中间件返回的Response对象会被丢弃。这也是swoft业务逻辑出现异常后跨域中间件无法正常设置header的原因。请参考我之前的文章【swoftmid-spanDomain设置问题】(https://segmentfault.com/a/1190000038411563)3.swoft返回数据的方式是重新设置headers、cookies、body等内容。将业务返回的Swoft\Http\Message\Response进行返回给swoole原生的Response对象,再调用原生的Response对象返回回到业务数据。4、执行完发送动作后,swoft会触发协程的deffer和finish事件,然后本次请求正式结束,当前请求协程的生命周期也结束。