FastRoute
在官方文档提到 默认情况下路由由 nikic/fast-route 提供支持,并由 hyperf/http-server 组件负责接入到 Hyperf 中,RPC 路由由对应的 hyperf/rpc-server 组件负责。
路由收集
路由收集在服务启动,初始化服务注册服务事件的时候,在Hyperf 启动章节中有提到。在初始化事件回调类的时候,通过 createDispatcher
方法初始化路由。在HttpServer的initCoreMiddleware
有
$this->routerDispatcher = $this->createDispatcher($serverName);
这个语句就是点对路由收集。
public function __construct()
{
$this->initAnnotationRoute(AnnotationCollector::list());
$this->initConfigRoute();
}
注解路由
拿到所有的 AutoController 和 Controller 注解类,同时获取该类的中间返回给到HttpServer
调度。
protected function initAnnotationRoute(array $collector): void
{
foreach ($collector as $className => $metadata) {
if (isset($metadata['_c'][AutoController::class])) {
if ($this->hasControllerAnnotation($metadata['_c'])) {
$message = sprintf('AutoController annotation can't use with Controller annotation at the same time in %s.', $className);
throw new ConflictAnnotationException($message);
}
$middlewares = $this->handleMiddleware($metadata['_c']);
$this->handleAutoController($className, $metadata['_c'][AutoController::class], $middlewares, $metadata['_m'] ?? []);
}
if (isset($metadata['_c'][Controller::class])) {
$middlewares = $this->handleMiddleware($metadata['_c']);
$this->handleController($className, $metadata['_c'][Controller::class], $metadata['_m'] ?? [], $middlewares);
}
}
}
AutoController
- 获取注解中路由前缀$prefix
- $router 本服务的路由收集对象 RouteCollector
- 自动将控制器方法注册成路由,默认请求方法是
['GET', 'POST', 'HEAD']
- 读取方法中间件
- 默认路由:是 $defaultAction = "/index",也就是控制器中的index方法会默认注册空路由。
protected function handleAutoController(string $className, AutoController $annotation, array $middlewares = [], array $methodMetadata = []): void
{
$class = ReflectionManager::reflectClass($className);
$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);
$prefix = $this->getPrefix($className, $annotation->prefix);
$router = $this->getRouter($annotation->server);
$autoMethods = ['GET', 'POST', 'HEAD'];
$defaultAction = '/index';
foreach ($methods as $method) {
$options = $annotation->options;
$path = $this->parsePath($prefix, $method);
$methodName = $method->getName();
if (substr($methodName, 0, 2) === '__') {
continue;
}
$methodMiddlewares = $middlewares;
// Handle method level middlewares.
if (isset($methodMetadata[$methodName])) {
$methodMiddlewares = array_merge($methodMiddlewares, $this->handleMiddleware($methodMetadata[$methodName]));
}
// Rewrite by annotation @Middleware for Controller.
$options['middleware'] = array_unique($methodMiddlewares);
$router->addRoute($autoMethods, $path, [$className, $methodName], $options);
if (Str::endsWith($path, $defaultAction)) {
$path = Str::replaceLast($defaultAction, '', $path);
$router->addRoute($autoMethods, $path, [$className, $methodName], $options);
}
}
}
Controller
- 获取注解中路由前缀$prefix
- $router 本服务的路由收集对象 RouteCollector
- 遍历控制器中有注解的方法,获取方法中的注解信息,例如Mapping和Middleware注解。
- 合并控制器的中间件、方法注解中间件和mapping中options定义的中间件
- 拿到mapping 中的 $path 作为路由
protected function handleController(string $className, Controller $annotation, array $methodMetadata, array $middlewares = []): void
{
if (! $methodMetadata) {
return;
}
$prefix = $this->getPrefix($className, $annotation->prefix);
$router = $this->getRouter($annotation->server);
$mappingAnnotations = [
RequestMapping::class,
GetMapping::class,
PostMapping::class,
PutMapping::class,
PatchMapping::class,
DeleteMapping::class,
];
foreach ($methodMetadata as $methodName => $values) {
$options = $annotation->options;
$methodMiddlewares = $middlewares;
// Handle method level middlewares.
if (isset($values)) {
$methodMiddlewares = array_merge($methodMiddlewares, $this->handleMiddleware($values));
}
// Rewrite by annotation @Middleware for Controller.
$options['middleware'] = array_unique($methodMiddlewares);
foreach ($mappingAnnotations as $mappingAnnotation) {
/** @var Mapping $mapping */
if ($mapping = $values[$mappingAnnotation] ?? null) {
if (! isset($mapping->path) || ! isset($mapping->methods) || ! isset($mapping->options)) {
continue;
}
$methodOptions = Arr::merge($options, $mapping->options);
// Rewrite by annotation @Middleware for method.
$methodOptions['middleware'] = $options['middleware'];
$path = $mapping->path;
if ($path === '') {
$path = $prefix;
} elseif ($path[0] !== '/') {
$path = $prefix . '/' . $path;
}
$router->addRoute($mapping->methods, $path, [$className, $methodName], $methodOptions);
}
}
}
}
配置路由
- 通过 require_once 引入配置文件
protected $routes = [BASE_PATH . '/config/routes.php'];
- 在配置文件中,通过
HyperfHttpServerRouterRouter
一系列静态方法进行处理。- 用一个静态变量(static::$factory)存储 DispatcherFactory
- 通过魔术方法 __callStatic 拿到可以收集路由的 $router 对象
$router = static::$factory->getRouter(static::$serverName);
public function initConfigRoute()
{
Router::init($this);
foreach ($this->routes as $route) {
if (file_exists($route)) {
require_once $route;
}
}
}
代码语言:javascript复制class Router
{
/**
* @var string
*/
protected static $serverName = 'http';
/**
* @var DispatcherFactory
*/
protected static $factory;
public static function __callStatic($name, $arguments)
{
$router = static::$factory->getRouter(static::$serverName);
return $router->{$name}(...$arguments);
}
public static function addServer(string $serverName, callable $callback)
{
static::$serverName = $serverName;
call($callback);
static::$serverName = 'http';
}
public static function init(DispatcherFactory $factory)
{
static::$factory = $factory;
}
}
路由组成
查看每一个路由器的组成,找到 HyperfHttpServerRouterRouteCollector
中的 addRoute看出。主要由
- $httpMethod:请求方法,get、post等
- $route:路由路径,包含参数匹配规则。
- $handler:路由处理回调,例如控制器方法。
- $options:其他参数,在这里最重要是用来存储每个路由对应处理的中间件。
路由匹配
在 HttpServer 中有讲到,所有的http请求都是固定有一个核心中间件 CoreMiddleware 处理的,在中间件处理之前,会先执行中间件的调度器 dispatch
。
这个调度器就是将请求获得的请求方法,请求uri 通过路由调度器获得匹配的路由数组 $routes,
再实例化一个Hyperf定义的路由调度器。并将这个调度器挂载请求对象中。
代码语言:javascript复制public function dispatch(ServerRequestInterface $request): ServerRequestInterface
{
$routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath());
$dispatched = new Dispatched($routes);
return Context::set(ServerRequestInterface::class, $request->withAttribute(Dispatched::class, $dispatched));
}
FastRouteDispatcher 调度器
在 DispatcherFactory 处理类中,使用 GroupCountBased 作为服务路由调度器,GroupCountBased基础 RegexBasedAbstract 中的 dispatch 方法。所以这个 dispatch 方法就是将 请求方法,和请求Uri解析成指定对应路由的核心方法。最终解析结果就是 $routes 具体的格式就是
- NOT_FOUND 未匹配到合适的路由
- METHOD_NOT_ALLOWED 有路由,但是请求方法不合适
- FOUND 完全匹配到路由
- 路由处理方法(例如控制器业务)
- 参数数组
* [Dispatcher::NOT_FOUND]
* [Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
* [Dispatcher::FOUND, $handler, ['varName' => 'value', ...]]
Hyperf 定义的路由调度器
这个调度器其实只是做了一个封装,让后续的业务方法调用而已。
代码语言:javascript复制class Dispatched
{
/**
* @var int
*/
public $status;
/**
* @var null|Handler
*/
public $handler;
/**
* @var array
*/
public $params = [];
/**
* Dispatches against the provided HTTP method verb and URI.
*
* @param array $array with one of the following formats:
*
* [Dispatcher::NOT_FOUND]
* [Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
* [Dispatcher::FOUND, $handler, ['varName' => 'value', ...]]
*/
public function __construct(array $array)
{
$this->status = $array[0];
switch ($this->status) {
case Dispatcher::METHOD_NOT_ALLOWED:
$this->params = $array[1];
break;
case Dispatcher::FOUND:
$this->handler = $array[1];
$this->params = $array[2];
break;
}
}
public function isFound(): bool
{
return $this->status === Dispatcher::FOUND;
}
}