生命周期 11-江南app体育官方入口
在dingo api视频中老师有一句:“dingo接管了api路由”,让我思索了许久。。今天终于想清楚了。。
要明白这句话,首先得对laravel生命周期有一定的理解。
如果不熟悉生命周期,建议阅读我小结的如下系列文章(并点赞 :smile:)。
- 生命周期1 app对象解析
- 生命周期2
- 生命周期3
- 生命周期4
- 生命周期5 管道流分析
- 生命周期6
- 生命周期7
- 生命周期8
- 生命周期9
- 生命周期10
我们知道,访问laravel应用程序有两种方式:
- 通过浏览器地址栏(普通web路由)
- 通过api接口(api路由)
dingo接管的路由仅仅是api路由,并非全部路由。
下面简要分析一下这两种方式的差别,从而看清楚"dingo是如何接管了api的路由"的。
先通过一个表格来对比。
特性 | 通过浏览器 | 通过api |
---|---|---|
访问url | (user-token中id=1) | |
路由定义所在文件 | routes/web.php(对user使用了资源路由) | routes/api.php(路由直接手写定义) |
引入composer自动加载类 | 引入composerloader类 | 相同 |
创建app对象 | 创建服务容器,并设置全局路径,绑定基础类,注册基本服务,设置核心类的别名 | 相同 |
创建apphttpkernel对象 | 通过服务容器,创建apphttpkernel对象及其依赖的其他对象 | 相同 |
创建request对象 | 以symfonyrequest对象为模板创建一个illuminaterequest对象 | 相同 |
apphttpkernel的bootstrap流程 | 载入环境变量,读取配置文件,设置异常处理,引入aliasloader类,设置facade别名,注册各种服务,启动各种服务 | 相同 |
第一个管道流 | illuminaterequest对象按正常顺序通过全局中间件过滤后,到达illuminaterouter路由器 | 有差异 |
第二个管道流前 | 中间件:从illuminate路由上绑定的中间件(来自app/http/kernel.php中的web中间件组)和从控制器上设置的中间件。 | 有差异 |
第二个管道流 | 穿过上面的路由&控制器中间件 | 有差异 |
与应用程序交互 | 执行控制器方法,返回待处理结果 | 类似 |
准备response对象 | 根据返回数据类型,自动选择处理逻辑 | 类似 |
返回response对象--第二个管道流 | 进行后置中间件的一些处理后返回 | 类似 |
返回response对象--两个管道流之间 | 没有处理 | 有差异 |
返回response对象--第一个管道流 | 中间件没有后置处理代码,直接返回 | 相同 |
第一个管道流
在第一个全局中间件\dingo\api\http\middleware\request
的handle方法中,就被接管了。
public function handle($request, closure $next)
{
try {
if ($this->validator->validaterequest($request)) {
$this->app->singleton(laravelexceptionhandler::class, function ($app) {
return $app[exceptionhandler::class];
});
$request = $this->app->make(requestcontract::class)->createfromilluminate($request);
$this->events->fire(new requestwasmatched($request, $this->app));
return $this->sendrequestthroughrouter($request);
}
} catch (exception $exception) {
$this->exception->report($exception);
return $this->exception->handle($exception);
}
return $next($request);
}
if ($this->validator->validaterequest($request)) {
这句就进行了判断,如果是api接口调用,那么就会转到dingo上去执行了,所有的一切变化从此开始。
可以看到,根据illuminaterequest对象创建了dingorequest对象,并代替illuminaterequest对象,进入新的管道流过滤。
新的第一个管道流:
protected function sendrequestthroughrouter(httprequest $request)
{
$this->app->instance('request', $request);
return (new pipeline($this->app))->send($request)->through($this->middleware)->then(function ($request) {
return $this->router->dispatch($request);
});
}
此时通过的中间件是在\dingo\api\provider\laravelserviceprovider
的boot方法中读取apphttpkernel的全局中间件数组,并绑定到\dingo\api\http\middleware\request
对象中,然后被读取到的。
vendor/dingo/api/src/provider/laravelserviceprovider.php:22
public function boot()
{
...
$kernel = $this->app->make(kernel::class);
$this->app[request::class]->mergemiddlewares(
$this->gatherappmiddleware($kernel)
);
此时创建了\dingo\api\http\middleware\request
对象,并调用其mergemiddlewares将apphttpkernel的全局中间件数组提取处理附加到自己身上。
第二个管道流前
在\dingo\api\http\middleware\request::sendrequestthroughrouter
,
return $this->router->dispatch($request);
在\dingo\api\routing\router::dispatch
中:
public function dispatch(request $request)
{
...
try {
$response = $this->adapter->dispatch($request, $request->version());
} catch (exception $exception) {
...
}
return $this->prepareresponse($response, $request, $request->format());
}
在\dingo\api\routing\adapter\laravel::dispatch
中:
public function dispatch(request $request, $version)
{
if (! isset($this->routes[$version])) {
throw new unknownversionexception;
}
$routes = $this->mergeoldroutes($version);
$this->router->setroutes($routes);
$router = clone $this->router;
$response = $router->dispatch($request);
unset($router);
return $response;
}
路由器已经变成了dingorouter,request
变成了dingorequest对象。
在路由器上还增加了一个适配器(adapter),通过适配器来发送request到路由。路由还有了版本的区别。
将illuminaterouter中的所有路由和api的路由进行合并得到新的路由集合。并将此集合赋给illuminaterouter。
克隆一个新的illuminaterouter,然后由它去发送dingorequest对象,从而进入了第二个管道流。也就是说第二个管道流是通过illuminaterouter来完成的。
第二个管道流
找到request的匹配路由都跟第一种方式一致。
但在收集中间件时有明细差异,原因在于此时路由的中间件没有通过web中间件组,而是通过了api中间件组(且api中间件组非app/http/kernel.php中的,而是定义在routes/api.php中的)。
代码见\illuminate\routing\router::runroutewithinstack
...
$middleware = $shouldskipmiddleware ? [] : $this->gatherroutemiddleware($route);
...
这里的$this->gatherroutemiddleware($route)
是通过此路由去找附在其上的中间件。
\illuminate\routing\router::gatherroutemiddleware
public function gatherroutemiddleware(route $route)
{
$middleware = collect($route->gathermiddleware())->map(function ($name) {
return (array) middlewarenameresolver::resolve($name, $this->middleware, $this->middlewaregroups);
})->flatten();
return $this->sortmiddleware($middleware);
}
\illuminate\routing\route::gathermiddleware
public function gathermiddleware()
{
...
return $this->computedmiddleware = array_unique(array_merge(
$this->middleware(), $this->controllermiddleware()
), sort_regular);
}
关键在于$this->middleware()
是什么?
\illuminate\routing\route::middleware
public function middleware($middleware = null)
{
if (is_null($middleware)) {
return (array) ($this->action['middleware'] ?? []);
}
...
return $this;
}
由于传入的参数$middleware
为null,因此,会返回$this->action['middleware']
,也就是说当前匹配路由的action属性中的middlware键值,此值对最后收集到的中间件有巨大影响。
下面分两种情况具体说明一下。
浏览器调用
如果当前url是""。
那么这个值是在调用\app\providers\routeserviceprovider
的boot方法时,调用\app\providers\routeserviceprovider::mapwebroutes
,最终调用了\illuminate\routing\router::createroute
附加上去的。
...
$route = $this->newroute(
$methods, $this->prefix($uri), $action
);
//由于此url属于web中间件组
if ($this->hasgroupstack()) {
$this->mergegroupattributesintoroute($route);
}
因此,$this->action['middleware']
有web
,而通过middlewarenameresolver::resolve
,就会将web
转换为数个全局中间件的类全名。这样,就得到了一个全类名中间件集合,用于第二个管道流过滤,内容为:
array (
0 => 'app\\http\\middleware\\encryptcookies',
1 => 'illuminate\\routing\\middleware\\substitutebindings',
2 => 'illuminate\\cookie\\middleware\\addqueuedcookiestoresponse',
3 => 'illuminate\\session\\middleware\\startsession',
4 => 'illuminate\\view\\middleware\\shareerrorsfromsession',
5 => 'app\\http\\middleware\\verifycsrftoken',
6 => 'app\\http\\middleware\\recordlastactivedtime',
7 => 'app\\http\\middleware\\redirectifauthenticated',
)
api接口调用
如果当前url是"。
那么这个值是在调用\app\providers\routeserviceprovider
的boot方法时,调用\app\providers\routeserviceprovider::mapapiroutes
,然后在定义$api->get('user', 'userscontroller@me')
时,调用了\dingo\api\routing\router::addroute
,然后调用\dingo\api\routing\adapter\laravel::addroute
附加上去的。
public function addroute($methods, $uri, $action)
{
...
//获得routes/api.php中定义的嵌套group路由的中间件
$action = $this->mergelastgroupattributes($action);
//添加一个preparation controller中间件
$action = $this->addcontrollermiddlewaretorouteaction($action);
...
return $this->adapter->addroute((array) $methods, $action['version'], $uri, $action);
}
public function addroute(array $methods, array $versions, $uri, $action)
{
$this->createroutecollections($versions);
$route = new route($methods, $uri, $action);
$route->where($action['where']);
foreach ($versions as $version) {
$this->routes[$version]->add($route);
}
return $route;
}
这样,也得到了一个全类名中间件集合,用于第二个管道流过滤,内容为:
array (
0 => 'dingo\\api\\http\\middleware\\preparecontroller',
1 => 'liyu\\dingo\\serializerswitch:array',
2 => 'illuminate\\routing\\middleware\\substitutebindings',
3 => 'dingo\\api\\http\\middleware\\ratelimit',
4 => 'dingo\\api\\http\\middleware\\auth',
)
结论
由此可见,路由的中间件都是在定义路由的时候就已经绑定好了。
返回对象
返回对象在第一个管道流和第二管道流之间时,会通过 dingorouter 对返回的dingoresponse对象,dingorequest对象进行处理。\dingo\api\routing\router::dispatch
public function dispatch(request $request)
{
...
try {
$response = $this->adapter->dispatch($request, $request->version());
} catch (exception $exception) {
...
}
return $this->prepareresponse($response, $request, $request->format());
}
\dingo\api\routing\router::prepareresponse
protected function prepareresponse($response, request $request, $format)
{
if ($response instanceof illuminateresponse) {
$response = response::makefromexisting($response);
} elseif ($response instanceof jsonresponse) {
$response = response::makefromjson($response);
}
if ($response instanceof response) {
// if we try and get a formatter that does not exist we'll let the exception
// handler deal with it. at worst we'll get a generic json response that
// a consumer can hopefully deal with. ideally they won't be using
// an unsupported format.
try {
$response->getformatter($format)->setresponse($response)->setrequest($request);
} catch (notacceptablehttpexception $exception) {
return $this->exception->handle($exception);
}
$response = $response->morph($format);
}
if ($response->issuccessful() && $this->requestisconditional()) {
if (! $response->headers->has('etag')) {
$response->setetag(sha1($response->getcontent()));
}
$response->isnotmodified($request);
}
return $response;
}
注意: $response = $response->morph($format);
如果在控制器中使用了transformer,那么会在这个地方进行处理,从而可以控制response的输出格式。
- vendor/laravel/framework/src/illuminate/routing/router.php:471
- vendor/barryvdh/laravel-debugbar/src/serviceprovider.php:103
- vendor/dingo/api/src/provider/laravelserviceprovider.php:30
- vendor/dingo/api/src/provider/laravelserviceprovider.php:34
- vendor/dingo/api/src/http/middleware/request.php:91
- vendor/dingo/api/src/routing/router.php:503
- vendor/dingo/api/src/routing/adapter/laravel.php:69
从上面分析,我们可以得知dingo主要是通过自已的路由器,改变了request的走向,从而影响了整个程序的运行过程。
本作品采用《cc 协议》,转载必须注明作者和本文链接
我觉得讲的有点问题
这句话 我加了断点,非api的端点也进入了,我理解的是,dingo接管了所有的路由
置于再何时分叉,还要继续加断点
这个我怎么说呢?你运行的url是什么,点开源码看看就明白了。生命周期走一遍,不同的方式都走一遍你就明白了。
****一会debug看看, 谢谢楼主的分享!
@hustnzj
我运行的url是
/
然后
$this->validator->validaterequest($request)
发现可以通过的/
我这里是直接不通过的。你配置文件里是怎么配的?验证代码都在
\dingo\api\http\requestvalidator::validaterequest
你可以再debug看看是哪里出了问题?
我是
5.7
版本的,我这边确实是通过的,而且不一定是出了问题我也是5.7版本,看看自己的配置文件吧,不一定出了问题?那你debug过程写出来?
环境配置,一个变量都会有影响,差别很大,你要不debug,如何证明不一定出了问题?
@hustnzj 大神,我就是debug的,否则我怎么知道走了那个函数呢?
过程和配置文件。。
@hustnzj
配置文件真不太方便,我回头找一下原因,再发帖通知你
幸好老师这里刚说明了,供参考:
估计你还是没有点进去看如何验证的。你要是把验证代码看完了,就知道我说的配置文件的哪些内容了。验证就是两部分,域名和前缀。