laravel请求响应的过程
本文主要介绍laravel处理请求到响应的详细过程。当在浏览器上输入了一个url之后都发生了什么。
我的服务器部署就是常用LNMP方式,首先看下我的Nginx配置,很简单:
server {
listen 8888;
root /home/work/admin/public;
location / {
try_files $uri /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/work/admin/public/$fastcgi_script_name;
include fastcgi_params;
}
}
从上面的配置也可以看到,当我访问url的时候,入口文件是index.php,是在/home/work/admin/public下的index.php.它是我们访问服务器的大门。那看一下index.php都发生了什么。
index.php 源码:
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
看代码,index.php主要干了这几件事:
1.require autoload.php。该语句会帮助我们自动加载所有我们通过composer方法安装的类;
2.创建一个Container实例app.实例化会进行一些重要的初始化,包括绑定服务providers等等。
Container确切的说是Service Container.容器是laravel很吊的一个概念了。它可以理解为一个大管家,我们可以把所有要依赖的类注入到容器中,然后在需要的地方使用它。注入的方法包括binding,singleton,instance,解析的方法就是make或者resovle。
3.绑定HTTP/Kernel,Console/Kernel,ExceptionHandler三个类。他们三个的作用分别是处理HTTP请求,Console命令,处理异常。
4.我们在上面绑定了类,那么下面就开始解析使用:
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 如果你仔细看,就会发现,我只需要make对应的interface酒醒,具体是哪个实现类,我们在使用时并不关心,而是在上面绑定的时候决定的。如果定义了多个实现类,那么就需要根据名称区分,这点和spring boot的Service接口定义了多个实现类,然后根据@Service(somename)是完全相同的。
容器让你根本不需要再显示地去实例化一个类。
5..构造request对象。
Illuminate\Http\Request::capture()。laravel会从php全局变量获取参数。包括$_SERVER,$_COOKIE,$_FILES,$_GET,$_POST等等。
6.开始调用和HTTP\Kernel的实现类的handle方法。到目前为止,index的迎宾工作就结束了,接下来的任务就交给业务人员来做了。
该类是基类
Illuminate\Foundation\Http;
下面的代码是实例的初始化,以及handle处理方法。
public function __construct(Application $app, Router $router)
{
$this->app = $app;
$this->router = $router;
$router->middlewarePriority = $this->middlewarePriority;
foreach ($this->middlewareGroups as $key => $middleware) {
$router->middlewareGroup($key, $middleware);
}
foreach ($this->routeMiddleware as $key => $middleware) {
$router->aliasMiddleware($key, $middleware);
}
}
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
初始化的作用主要包括加载路由中间件分组等等。
看看处理handle方法都干了什么:
1、最重要的就是这句
$response = $this->sendRequestThroughRouter($request);
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
它主要干了以下几件事:
1) app绑定request;
2) 执行bootstrap方法,这个相当重要了。包括加载环境变量(.env),加载配置(config),修改系统默认异常处理函数。注册Facades,注册Register所有配置的服务provider,最后是执行所有Service providers的boot方法。从这你也看到了。服务提供者的boot方法是再所有register之后才执行的。
其实,就是下面这些类(看名字):
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];
在处理器真正处理一个响应之前,是进行各种初始化,我觉得最重要的应该就是上面的bootstrap了。上面的每一个bootstraper对于laravel来讲都是非常重要的。
3)接下来,管道Pipeline该出场了。请求变身之后开始到时空隧道走一波了,这个管道里面设下层层关卡,必须一关一关通关后才可以迎娶梦中情人儿。
(1)准备工作:把请求对象request发送出去,并告知要通过哪些关卡(通过哪些中间件)。实现的方法就是 send和through方法。
(2)实现递归处理,最终执行处理响应逻辑。这步比较复杂,该步骤执行的方法是then。它的原型:
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
先概括地说,就是首先请求先经过中间件的处理,然后才轮到最后的响应方法,也就是上述的destination,该参数是一个闭包,说白了就是一个匿名函数,原型如下:
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
我们研究一下管道是怎么实现中间件的处理的。
其实看array_reduce就明白了,对数组进行迭代。此时的数组是一段段管道,管道就是封装的中间件。回调函数carry的原型是:
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_callable($pipe)) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);
// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);
$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}
return method_exists($pipe, $this->method)
? $pipe->{$this->method}(...$parameters)
: $pipe(...$parameters);
};
};
}
passables就是传递的对象,这里是request。对于每一个管道,最终都会调用该管道的handle方法,也就是每个中间件的方法。每个分片处理完了都会返回一个处理后的response。
中间件处理完之后,就会到达目的方法,就是上面的闭包函数:dispatchToRouter()。
不过我上面只是说了大概,还没有说怎么执行的。为什么在then方法中要用array_reverse,执行顺序是什么样呢?
其实上array_reduce实现了一个层层迭代的闭包,初始值是我们最重要执行的闭包。当我们要执行这个迭代的闭包时,就会从外往里去执行,就像一个洋葱一样,一层一层去拨。这也就是解释了当时为什么要用个array_reverse操作了,中间件的执行是有顺序的,先定义的就要先执行,那么我们就必须做一个倒叙才可以实现这种场景。
有一篇文章对这一块解释的非常好,可参考: laravel的PipeLine reduce
好了,到了最终的闭包dispatchRouter,最终要执行router的dispatch:
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
处理请求首先要根据request去match对应的路由,路由服务时在app初始化的时候i加载的,然或在bootstrap()方法进行register的。
关于路由这块,比较好的文章: laravel的路由 laravel路由原理
找到目的路由,再匹配对应的action,然后进行响应处理返回响应。
到目前为止,已经返回了处理后的response.
再到index.php,之后服务器会返回处理后的响应。
send执行完毕后,Http Kernel会执行
terminate
方法,该方法调用terminate中间件里的
terminate
方法,从而结束整个应用生命周期。
微信分享/微信扫码阅读