当前位置:网站首页>Analysis of the startup source code of hyperf (2) - how the request reaches the controller
Analysis of the startup source code of hyperf (2) - how the request reaches the controller
2022-07-31 13:55:00 【Adriano huang a. a.】
今天又看了一下 hyperf 的源码?focus on one bin/hyperf.php 的最后一行 , 到底在做什么
从上面的代码中, 我们可以知道 Hyperf\Contract\ApplicationInterface::class 在容器中是ApplicationFactory::class的键名, 从 ApplicationFactiory::classin the code we can see
hyperf 的代码中, 如果通过 容器container- >get(…) 得到的对象, will call the corresponding number __invoke()的方法
<?php
declare(strict_types=1);
/** * This file is part of Hyperf. * * @link https://www.hyperf.io * @document https://hyperf.wiki * @contact [email protected] * @license https://github.com/hyperf/hyperf/blob/master/LICENSE */
namespace Hyperf\Framework;
use Hyperf\Command\Annotation\Command;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Framework\Event\BootApplication;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Application;
class ApplicationFactory
{
public function __invoke(ContainerInterface $container)
{
if ($container->has(EventDispatcherInterface::class)) {
$eventDispatcher = $container->get(EventDispatcherInterface::class);
$eventDispatcher->dispatch(new BootApplication());
}
//这里是通过 Hyperf\Config\ConfigFactory 中的 invoke方法
$config = $container->get(ConfigInterface::class);
$commands = $config->get('commands', []);
// Append commands that defined by annotation.
$annotationCommands = [];
if (class_exists(AnnotationCollector::class) && class_exists(Command::class)) {
$annotationCommands = AnnotationCollector::getClassesByAnnotation(Command::class);
$annotationCommands = array_keys($annotationCommands);
}
//Annotated here command 和 configspecified in the document commands一起合并起来
$commands = array_unique(array_merge($commands, $annotationCommands));
//这个application is a command line tool class
$application = new Application();
if (isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) {
$application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher));
}
foreach ($commands as $command) {
$application->add($container->get($command));
}
//Here is the final return application In fact, it is a command line management tool class.
//在它的 commands Added a lot to the properties command
return $application;
}
}
所以, $application 其实就是一个 Symfony\Component\Console\Application的实例
此时, 我们进入 run() 方法, (注意,phpStorm The point is not to directly, 我们要找到 ApplicationClass that can be seenrun方法)
方法有很多, 我们这里主要看 run的方法
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if (\function_exists('putenv')) {
....
}
//这里的 ArgvInput Objects can receive commands from the command line 参数, 如 php bin/hyperf.php start 这个$input We can get our parameter isstart
//如果我们输入 php bin/hyperf.php gohome Although this command will report an error, but also receive gohome 的参数, Just no order
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
//error handling here,暂时不看
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
$this->renderThrowable($e, $output->getErrorOutput());
} else {
$this->renderThrowable($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
.......
}
//Here is the configuration input and output,也暂时不管
$this->configureIO($input, $output);
try {
//here is the key code, 我们进入 doRun
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
........
} finally {
...........
return $exitCode;
}
我们进入 doRun()方法
public function doRun(InputInterface $input, OutputInterface $output)
{
........
$name = $this->getCommandName($input);
//var_dump($name); When starting this $name 的值为 start; That is, the parameters we pass when we start php bin/hyperf.php start
........
//Some parameters are combined here
if (!$name) {
$name = $this->defaultCommand;
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
[
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
]
));
}
try {
$this->runningCommand = null;
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Throwable $e) {
........
}
if ($command instanceof LazyCommand) {
$command = $command->getCommand();
}
//经过上面的代码, 我们可以看到 $command 现在是一个 startServer类
//var_dump(get_class($command)); Hyperf\Server\Command\StartServer
$this->runningCommand = $command;
//最终走到了 doRunCommand方法
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
}
我们进入 doRunCommand 方法
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
//var_dump(get_class($command)); Hyperf\Server\Command\StartServer
............
//var_dump($this->dispatcher); at the time of startup $this->dispatcher不为null,So go below try
if (null === $this->dispatcher) {
return $command->run($input, $output);
}
// bind before the console.command event, so the listeners have access to input options/arguments
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
$event = new ConsoleCommandEvent($command, $input, $output);
$e = null;
try {
$this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
//var_dump($event->commandShouldRun()); 这里返回的是true
if ($event->commandShouldRun()) {
//var_dump(get_class($command)); Hyperf\Server\Command\StartServer
//因为 $command是 startServer类了, 所以我们要看 startServer 的 run方法了
$exitCode = $command->run($input, $output);
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
} catch (\Throwable $e) {
$event = new ConsoleErrorEvent($input, $output, $e, $command);
........
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
if (null !== $e) {
throw $e;
}
return $event->getExitCode();
}
找到 Hyperf\Server\Command\StartServer 类 并找到它的 run 方法, startServer没有run方法, So I found its parent class, symfony\console\command\command.php 的 run 方法
public function run(InputInterface $input, OutputInterface $output)
{
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
..............
if ($this->code) {
$statusCode = ($this->code)($input, $output);
} else {
//a bunch of operations above,Are configured, 这里是主要的,可以看到, it's time to execute again, execute方法
//于是我们到 Hyperf\Server\Command\StartServer 中,又找到 execute 方法
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
So here we are again startServer 的 execute方法
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->checkEnvironment($output);
$serverFactory = $this->container->get(ServerFactory::class)
->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
->setLogger($this->container->get(StdoutLoggerInterface::class));
//这里从 config server.phpread the configuration file in
$serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
if (! $serverConfig) {
throw new InvalidArgumentException('At least one server should be defined.');
}
//we're going into this configure Check it out in the method,
$serverFactory->configure($serverConfig);
This is a key programs of the association constants of code Note that this is just a constant, 在bin/hyperf.php 文件中已经定义了, 它的值是 SWOOLE_HOOK_ALL
Coroutine::set(['hook_flags' => swoole_hook_flags()]);
//走到这里, 进入serverFactory中, start方法
$serverFactory->start();
return 0;
}
public function configure(array $config)
{
$this->config = new ServerConfig($config);
//Here it goes again init 方法
$this->getServer()->init($this->config);
}
public function init(ServerConfig $config): ServerInterface
{
//进入init , 又走到了 initServers 方法
$this->initServers($config);
return $this;
}
在这里的 initServers方法中, 才是创建 swoole服务器, 并且添加 where the callback function
protected function initServers(ServerConfig $config)
{
$servers = $this->sortServers($config->getServers());
//这里使用的 foreach 函数,也就是说, Multiple servers can be configured in the configuration file 都可以创建
//比如 you can have one HTTPServer Can also be combined with a tcpServer 都是可以了
foreach ($servers as $server) {
//Here is the corresponding configuration in the configuration file
$name = $server->getName();
$type = $server->getType();
$host = $server->getHost();
$port = $server->getPort();
$sockType = $server->getSockType();
$callbacks = $server->getCallbacks();
if (! $this->server instanceof SwooleServer) {
//This line createshttpserver 服务器
$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
//got all here on 事件方法, Here is the method to do a merge
$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
//Here is the event method Bind the corresponding function 此时, 就有了 onWorkStart onRequest Execution function of other methods
$this->registerSwooleEvents($this->server, $callbacks, $name);
$this->server->set(array_replace($config->getSettings(), $server->getSettings()));
ServerManager::add($name, [$type, current($this->server->ports)]);
if (class_exists(BeforeMainServerStart::class)) {
// Trigger BeforeMainServerStart event, this event only trigger once before main server start.
$this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
}
} else {
/** @var bool|\Swoole\Server\Port $slaveServer */
$slaveServer = $this->server->addlistener($host, $port, $sockType);
if (! $slaveServer) {
throw new \RuntimeException("Failed to listen server port [{
$host}:{
$port}]");
}
$server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
$this->registerSwooleEvents($slaveServer, $callbacks, $name);
ServerManager::add($name, [$type, $slaveServer]);
}
// Trigger beforeStart event.
if (isset($callbacks[Event::ON_BEFORE_START])) {
[$class, $method] = $callbacks[Event::ON_BEFORE_START];
if ($this->container->has($class)) {
$this->container->get($class)->{
$method}();
}
}
if (class_exists(BeforeServerStart::class)) {
// Trigger BeforeServerStart event.
$this->eventDispatcher->dispatch(new BeforeServerStart($name));
}
}
}
The above methods are all done, swoole The server is also created, Let's go back to the original code,Leaves the server open command start() 了
public function start()
{
return $this->getServer()->start();
}
Here is to start the service
那么, Our routing is how to get in to the corresponding controller methods, This is to look at,我们的 onRequest Events have done
上面的 initServers 方法中, 绑定了 onRequest ,onWorkstart 的事件处理方法, We find from the inside
打印出 $callbacks
array(7) {
["start"]=>
array(2) {
[0]=>
string(40) "Hyperf\Framework\Bootstrap\StartCallback"
[1]=>
string(7) "onStart"
}
["managerStart"]=>
array(2) {
[0]=>
string(47) "Hyperf\Framework\Bootstrap\ManagerStartCallback"
[1]=>
string(14) "onManagerStart"
}
["workerStart"]=>
array(2) {
[0]=>
string(46) "Hyperf\Framework\Bootstrap\WorkerStartCallback"
[1]=>
string(13) "onWorkerStart"
}
["workerStop"]=>
array(2) {
[0]=>
string(45) "Hyperf\Framework\Bootstrap\WorkerStopCallback"
[1]=>
string(12) "onWorkerStop"
}
["workerExit"]=>
array(2) {
[0]=>
string(45) "Hyperf\Framework\Bootstrap\WorkerExitCallback"
[1]=>
string(12) "onWorkerExit"
}
["pipeMessage"]=>
array(2) {
[0]=>
string(46) "Hyperf\Framework\Bootstrap\PipeMessageCallback"
[1]=>
string(13) "onPipeMessage"
}
["request"]=>
array(2) {
[0]=>
string(24) "Hyperf\HttpServer\Server"
[1]=>
string(9) "onRequest"
}
}
可以看到 onRequest 对应的是 Hyperf\HttpServer\Server 的类
进入 Hyperf\HttpServer\Server 类, 找到 onRequest方法
public function onRequest($request, $response): void
{
try {
CoordinatorManager::until(Constants::WORKER_START)->yield();
[$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response);
//这里可以看到, hyperf 把$request 和 $response 又封装了一下
//Take a look inside, Why do you want to package it like this?, 大至的意思就是, 把这个新的 $psrRequest和新的$psrResponse 对象, 加入到了 上下文变量 Context中,
//Here we can also explain, 一个问题, 在中间件中, 我们要把 $request中添加一个变量, 传送到 控制器中, why not use $request 了, Because but into the controller is a $psrRequest对象
$psr7Request = $this->coreMiddleware->dispatch($psr7Request);
/** @var Dispatched $dispatched */
$dispatched = $psr7Request->getAttribute(Dispatched::class);
$middlewares = $this->middlewares;
if ($dispatched->isFound()) {
$registeredMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod());
$middlewares = array_merge($middlewares, $registeredMiddlewares);
}
//这里是 分发请求, We enter this function
$psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware);
} catch (Throwable $throwable) {
// Delegate the exception to exception handler.
$psr7Response = $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers);
} finally {
// Send the Response to client.
if (! isset($psr7Response)) {
return;
}
if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
$this->responseEmitter->emit($psr7Response, $response, false);
} else {
$this->responseEmitter->emit($psr7Response, $response, true);
}
}
}
public function dispatch(...$params): ResponseInterface
{
/** * @param RequestInterface $request * @param array $middlewares * @param MiddlewareInterface $coreHandler */
[$request, $middlewares, $coreHandler] = $params;
$requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);
//Creates an object handle, 并调用了 handle 处理方法, 我们再进入 handle
return $requestHandler->handle($request);
}
class HttpRequestHandler extends AbstractRequestHandler implements RequestHandlerInterface
{
/** * Handles a request and produces a response. * May call other collaborating code to generate the response. */
public function handle(ServerRequestInterface $request): ResponseInterface
{
//没说的, 我们再进入 handleRequest 方法中
return $this->handleRequest($request);
}
}
protected function handleRequest($request)
{
//从前面的代码中,我们可以发现, hyperf Our approach to perform directory, 放在了 中间件数组 middlewares The final implementation, 所以 下面的
if (! isset($this->middlewares[$this->offset]) && ! empty($this->coreHandler)) {
$handler = $this->coreHandler;
} else {
//这个$handler 其实就是我们要 Executed by the controller 目标方法
$handler = $this->middlewares[$this->offset];
is_string($handler) && $handler = $this->container->get($handler);
}
if (! method_exists($handler, 'process')) {
throw new InvalidArgumentException(sprintf('Invalid middleware, it has to provide a process() method.'));
}
//This is still equivalent to the usage of middleware,But it does what we want to use Controller's target method
return $handler->process($request, $this->next());
}
我们继续进入 process 的方法中
code in this 有点像 php的 fastRoute 插件的代码, 有兴趣的可以了解一下 nikic/fast-route
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
//这个地方 $request 就是之前的 $psrRequest 对象
$request = Context::set(ServerRequestInterface::class, $request);
/** @var Dispatched $dispatched */
$dispatched = $request->getAttribute(Dispatched::class);
if (! $dispatched instanceof Dispatched) {
throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
}
$response = null;
switch ($dispatched->status) {
//If no route is found
case Dispatcher::NOT_FOUND:
$response = $this->handleNotFound($request);
break;
//if method is not allowed
case Dispatcher::METHOD_NOT_ALLOWED:
$response = $this->handleMethodNotAllowed($dispatched->params, $request);
break;
//如果找到就执行 handleFound方法
case Dispatcher::FOUND:
$response = $this->handleFound($dispatched, $request);
break;
}
if (! $response instanceof ResponseInterface) {
$response = $this->transferToResponse($response, $request);
}
return $response->withAddedHeader('Server', 'Hyperf');
}
那么 handleFound 方法,the goal of execution controller method
protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request)
{
if ($dispatched->handler->callback instanceof Closure) {
$parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
$response = call($dispatched->handler->callback, $parameters);
} else {
//这里得到了 控制器 和 要执行的方法
[$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
//From the container to get here controller 这是swoole read into memory, 不会发生变化的,除非重启, Now that's permanent memory, 快的原因
$controllerInstance = $this->container->get($controller);
if (! method_exists($controllerInstance, $action)) {
// Route found, but the handler does not exist.
throw new ServerErrorHttpException('Method of class does not exist.');
}
$parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
//here is the controller, 调用方法的位置, And parameters
$response = $controllerInstance->{
$action}(...$parameters);
}
return $response;
}
走到这里, we can see a request, The specific steps to reach the controller
边栏推荐
- Text similarity calculation (Chinese and English) detailed explanation of actual combat
- Buffer 与 拥塞控制
- ML、DL、CV常见的问题整理
- Spark学习:为Spark Sql添加自定义优化规则
- [Niu Ke brush questions - SQL big factory interview questions] NO3. E-commerce scene (some east mall)
- 动作捕捉系统用于柔性机械臂的末端定位控制
- 推荐系统-召回阶段-2013:DSSM(双塔模型)【Embedding(语义向量)召回】【微软】
- 为什么 wireguard-go 高尚而 boringtun 孬种
- ICML2022 | 面向自监督图表示学习的全粒度自语义传播
- IDEA找不到Database解决方法
猜你喜欢
20.nn.Module
深度剖析 Apache EventMesh 云原生分布式事件驱动架构
STM32的CAN过滤器
Reasons and solutions for Invalid bound statement (not found)
Open Inventor 10.12 Major Improvements - Harmony Edition
已解决(pymysqL连接数据库报错)pymysqL.err.ProgrammingError: (1146,“Table ‘test.students‘ doesn‘t exist“)
CLion用于STM32开发
「面经分享」西北大学 | 字节 生活服务 | 一面二面三面 HR 面
IDEA can't find the Database solution
Miller_Rabin Miller Rabin probability sieve [template]
随机推荐
MySQL has played to such a degree, no wonder the big manufacturers are rushing to ask for it!
The pre-sale of the new Hyundai Paristi is open, and safety and comfort are not lost
golang-gin-优雅重启
el-tooltip的使用
技能大赛dhcp服务训练题
Node version switching management using NVM
Solution for browser hijacking by hao360
Network layer key protocol - IP protocol
The latest complete code: Incremental training using the word2vec pre-training model (two loading methods corresponding to two saving methods) applicable to various versions of gensim
CodeIgniter 打开错误日志
MySQL玩到这种程度,难怪大厂抢着要!
The Selenium IDE of the Selenium test automation
JSP中如何借助response对象实现页面跳转呢?
使用CompletableFuture进行异步处理业务
Batch大小不一定是2的n次幂!ML资深学者最新结论
LeetCode只出现一次的数字
Linux bash: redis-server: 未找到命令
selenium被反爬了怎么办?
numpy矩阵和向量的保存与加载,以及使用保存的向量进行相似度计算
leetcode:2032. Values that appear in at least two arrays