notfoundhttpexception in routecollection.php,生命周期 6--一个 request 是如何匹配到具体路由的源码分析...

导语

接下来的大致步骤

request是如何匹配具体路由。

如何根据request对象的信息获取路由和控制器中间件的。

如何穿透这些中间件的。

穿过后,是如何进入控制器与应用程序交互的(一般写程序只写这么一点。。)。

交互后,返回结果又是如何再次通过路由和控制器中间件和全局中间件的。

最后的得到response对象。

接下来的书写风格

限于时间关系,只说大致思路,不会深入太多。

不解释属性含义,代码中有,自行查阅即可。

不严格区分方法是从子类和父类执行,除非子类和父类中有相同方法。

能够通过注释讲清楚的,优先写注释。

方法所属命名空间,不影响理解的都会忽略,自行查阅或debug即可。

几个英文单词

route: 单个路由

routes: 多个路由(RouteCollection)

router: 路由器(放置routes的容器)

如何匹配路由

代码分析起点选择在vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:176,你我的可能略有差异,如下:

$this->router->dispatch($request);

上面这句之所以没有写上return,是因为限于篇幅和时间限制,本文远远分析不到执行return的地方。

dispatch方法如下:

/**

* Dispatch the request to the application.

*

* @param \Illuminate\Http\Request $request

* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse

*/

public function dispatch(Request $request)

{

//使用request对象设置kernel的currentRequest属性

$this->currentRequest = $request;

//将request发送到一个路由(哪个?)并返回response

return $this->dispatchToRoute($request);

}

dispatchToRoute方法如下:

/**

* Dispatch the request to a route and return the response.

*

* @param \Illuminate\Http\Request $request

* @return mixed

*/

public function dispatchToRoute(Request $request)

{

//通过request对象找到一个匹配的路由,并执行此路由并返回结果。

return $this->runRoute($request, $this->findRoute($request));

}

本文重点来了,如何找到路由

$this->findRoute($request)

findRoute方法如下:

/**

* Find the route matching a given request.

*

* @param \Illuminate\Http\Request $request

* @return \Illuminate\Routing\Route

*/

protected function findRoute($request)

{

//找到匹配request的第一个路由,如果找不到抛出NotFoundHttpException

$this->current = $route = $this->routes->match($request);

//设置route对象缓存

$this->container->instance(Route::class, $route);

//返回路由供后面run

return $route;

}

match方法如下:

/**

* Find the first route matching a given request.

*

* @param \Illuminate\Http\Request $request

* @return \Illuminate\Routing\Route

*

* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException

*/

public function match(Request $request)

{

//通过request的method来获取router中的该方法类型的routes

$routes = $this->get($request->getMethod());

// First, we will see if we can find a matching route for this current request

// method. If we can, great, we can just return it so that it can be called

// by the consumer. Otherwise we will check for routes with another verb.

//从上述的routes中,再通过route对象的matches方法来获取匹配

//request的具体路由route

$route = $this->matchAgainstRoutes($routes, $request);

//将获得的route绑定到request,并返回给findRoute方法。

if (! is_null($route)) {

return $route->bind($request);

}

// If no route was found we will now check if a matching route is specified by

// another HTTP verb. If it is we will need to throw a MethodNotAllowed and

// inform the user agent of which HTTP verb it should use for this route.

//如果上面没找到对应route,那么会检查其他的HTTP动词动词

$others = $this->checkForAlternateVerbs($request);

if (count($others) > 0) {

return $this->getRouteForMethods($request, $others);

}

//如果还找不到,就会抛出NotFoundHttpException

throw new NotFoundHttpException;

}

routes对象的matchAgainstRoutes方法就是神奇匹配的地方:

/**

* Determine if a route in the array matches the request.

*

* @param array $routes

* @param \Illuminate\Http\Request $request

* @param bool $includingMethod

* @return \Illuminate\Routing\Route|null

*/

protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)

{

//将传入的routes按照是否有Fallback路由分为两部分。

//注意是Fallback而非callback。。

[$fallbacks, $routes] = collect($routes)->partition(function ($route) {

return $route->isFallback;

});

//然后遍历合并后的routes,对每个route,都使用其matches方法来判断

//request和路由是否完全匹配!

return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {

return $value->matches($request, $includingMethod);

});

}

route对象的matches方法:

/**

* Determine if the route matches given request.

*

* @param \Illuminate\Http\Request $request

* @param bool $includingMethod

* @return bool

*/

public function matches(Request $request, $includingMethod = true)

{

//将当前路由(route对象)编译为一个Symfony CompiledRoute instance

//并赋值给route对象的compiled属性,后面会多次用到这个属性。

$this->compileRoute();

//然后是获取当前路由的validators,默认有4个。

//UriValidator, MethodValidator,

//SchemeValidator, HostValidator,

//然后遍历验证器数组,每个验证器处理一个点。

foreach ($this->getValidators() as $validator) {

if (! $includingMethod && $validator instanceof MethodValidator) {

continue;

}

//只要有一个验证器没有通过,返回false到前面的match方法中

if (! $validator->matches($this, $request)) {

return false;

}

}

//全部通过,返回true

return true;

}

最后,将找到的route对象返回到dispatchToRoute方法中。

$this->runRoute($request, $this->findRoute($request))

下一步,似乎就可以runRoute了。

runRoute方法

/**

* Return the response for the given route.

*

* @param \Illuminate\Http\Request $request

* @param \Illuminate\Routing\Route $route

* @return mixed

*/

protected function runRoute(Request $request, Route $route)

{

//设置request对象的路由解析器

$request->setRouteResolver(function () use ($route) {

return $route;

});

//触发注册过的路由匹配事件!

$this->events->dispatch(new Events\RouteMatched($route, $request));

return $this->prepareResponse($request,

$this->runRouteWithinStack($route, $request)

);

}

触发注册过的路由匹配事件!

$this->events->dispatch(new Events\RouteMatched($route, $request))

默认是没有注册这个事件的,但可以在appServiceProvide的boot方法或自己的serviceProvider中的boot方法中自定义,比如在vendor/hieu-le/active/src/ActiveServiceProvider.php中。

public function boot()

{

// Update the instances each time a request is resolved and a route is matched

$instance = app('active');

if (version_compare(Application::VERSION, '5.2.0', '>=')) {

app('router')->matched( //就是这里

function (RouteMatched $event) use ($instance) {

$instance->updateInstances($event->route, $event->request);

}

);

} else {

app('router')->matched(

function ($route, $request) use ($instance) {

$instance->updateInstances($route, $request);

}

);

}

}

然后就是如何根据request对象的信息获取路由和控制器中间件的(下一篇文章再写)

小结

serviceProvider的register方法都是用来注册类到serviceContainer中的或者起别名的。

其他工作比如添加中间件,添加事件监听器,加载配置信息,环境变量,视图文件等等就放到boot方法中。毕竟有了类,才能使用其中的方法来干事情。

serviceContainer和各个组件之间往往通过互为属性的关系来达到数据共享。有时也可以通过明确引用传递参数的方式。

以上代码分析都比较浅,如果深入研究下去,可以点亮更多技能,是否有用另说。

闭包回调非常重要,几乎所有的难点都集中在这上面。

看源码就是在学PHP的各种高级用法,开拓思路,同时理解了框架原理,以后用起来会得心应手。

读源码时,先看接口方法,再看实现类中的属性和方法,然后再看执行源码过程。执行源码时,先看是哪个对象,然后执行哪个方法或者赋值给哪个属性。

本作品采用《CC 协议》,转载必须注明作者和本文链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于你提供的 URL 模式 `http://localhost/video/{uuid}/{file}.{type}`,你可以通过 ASP.NET MVC5 的路由配置来实现。 首先,在 `RouteConfig.cs` 文件中进行路由配置。你可以使用以下代码来添加一个自定义的路由规则: ```csharp public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "VideoRoute", url: "video/{uuid}/{file}.{type}", defaults: new { controller = "Video", action = "Play" } ); // 其他路由配置... routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } ``` 在上述代码中,我们添加了一个名为 "VideoRoute" 的路由规则,它匹配了 `video/{uuid}/{file}.{type}` 这样的 URL,并将请求发送到 `VideoController` 的 `Play` 方法。 接下来,在控制器中创建一个名为 `VideoController` 的控制器,并在其中添加一个名为 `Play` 的方法: ```csharp public class VideoController : Controller { public ActionResult Play(string uuid, string file, string type) { // 在这里处理视频播放逻辑,可以使用 uuid、file 和 type 参数 return View(); } } ``` 在上述代码中,我们在 `VideoController` 中创建了一个名为 `Play` 的方法,并将 `uuid`、`file` 和 `type` 作为参数。你可以在此方法中处理视频播放逻辑,并使用这些参数。 现在,当你访问类似于 `http://localhost/video/12345678/video.mp4` 的 URL 时,它将匹配到 `VideoController` 的 `Play` 方法,并将参数 `uuid` 设置为 `12345678`,参数 `file` 设置为 `video`,参数 `type` 设置为 `mp4`。 请确保在路由配置时将自定义的路由规则放在一般规则之前,以确保正确匹配

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值