diff --git a/.vitepress/sidebar.ts b/.vitepress/sidebar.ts index 3d05004..03ed7b0 100644 --- a/.vitepress/sidebar.ts +++ b/.vitepress/sidebar.ts @@ -12,7 +12,7 @@ export default [ { text: '初始化依赖注入 (DI) 容器', link: hyperf + 'DI' }, { text: '初始化容器类', link: hyperf + 'container' }, { text: '启动服务', link: hyperf + '启动服务' }, - { text: '请求', link: hyperf + '请求' }, + { text: '接收请求', link: hyperf + '接收请求' }, { text: '路由寻址', link: hyperf + '路由寻址' }, { text: '中间件', link: hyperf + '中间件' }, { text: '响应', link: hyperf + '响应' }, @@ -20,6 +20,7 @@ export default [ { text: '附录2 注解命令获取', link: hyperf + '附录2 注解命令获取' }, { text: '附录3 命令注册Application', link: hyperf + '附录3 命令注册Application' }, { text: '附录4 Application Run方法', link: hyperf + '附录4 Application_Run' }, + { text: '附录5 初始化中间件', link: hyperf + '附录5 初始化中间件' }, ] }, { diff --git a/src/hyperf/启动服务.md b/src/hyperf/启动服务.md index fdddd21..44e670d 100644 --- a/src/hyperf/启动服务.md +++ b/src/hyperf/启动服务.md @@ -527,6 +527,7 @@ class ServerFactory ::: details 点我查看`ServerConfig` ::: code-group + ```php [ServerConfig] namespace Hyperf\Server; @@ -630,6 +631,7 @@ class ServerConfig implements Arrayable } } ``` + ```php [Server build] namespace Hyperf\Server; @@ -771,6 +773,7 @@ class Port } } ``` + ::: 2. 根据`Server`配置的服务类型,实例化对应服务,调用服务的`init`方法 ::: tip @@ -786,9 +789,11 @@ class Port // $this->getServer()返回的是一个Hyperf/Server/Server类实例 $this->getServer()->init($this->config); ``` + > 文件位置:/vendor/hyperf/server/src/Server.php ::: code-group + ```php [init方法] public function init(ServerConfig $config): ServerInterface { @@ -797,6 +802,7 @@ public function init(ServerConfig $config): ServerInterface return $this; } ``` + ```php [initServers方法] protected function initServers(ServerConfig $config) { @@ -848,6 +854,7 @@ protected function initServers(ServerConfig $config) } } ``` + ```php [sortServer方法] protected function sortServers(array $servers): array { @@ -875,6 +882,7 @@ protected function sortServers(array $servers): array return $sortServers; } ``` + ```php [Server类源码] namespace Hyperf\Server; @@ -1072,85 +1080,286 @@ class Server implements ServerInterface } } ``` + ::: -分析`sortServicers`方法, +分析`sortServicers`方法, 对`Server`进行排序,确保 `HTTP` 服务器和 `WebSocket` 服务器按特定顺序排列 ```php -// $config->getServers()返回的是一个Hyperf\Server\Port类对象数组 $servers = $this->sortServers($config->getServers()); ``` 这里返回排好序的`Port`类对象数组。 之后循环遍历`$servers`数组,每个`value`是一个`Port`对象,走`if` +::: code-group -```php -foreach ($servers as $server) { - $name = $server->getName(); - $type = $server->getType(); - $host = $server->getHost(); - $port = $server->getPort(); - $sockType = $server->getSockType(); - $callbacks = $server->getCallbacks(); +```php [启动Server] +foreach ($servers as $server) { // [!code focus] + // 获取服务名 + $name = $server->getName(); + // 获取服务类型(http or websocket) + $type = $server->getType(); + // 获取绑定的主机地址 + $host = $server->getHost(); + // 获取端口 + $port = $server->getPort(); + // 获取套接字类型 + $sockType = $server->getSockType(); + // 获取回调信息 + $callbacks = $server->getCallbacks(); - if (! $this->server instanceof SwooleServer) { - // 分析makeServer方法 - // 这里返回的是一个Swoole\Server对象 - $this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType); - // 获取所有回调事件,进行合并 - $callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks); - // 注册回调事件 - $this->registerSwooleEvents($this->server, $callbacks, $name); - // 设置运行参数 - $this->server->set(array_replace($config->getSettings(), $server->getSettings())); - // 将服务以键值对的形式添加到ServerManager中 - ServerManager::add($name, [$type, current($this->server->ports)]); - // 如果类存在,则触发BeforeMainServerStart事件 - 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]); - } + if (! $this->server instanceof SwooleServer) { // [!code focus:39] + // 分析makeServer方法 + // 这里返回的是一个Swoole\Server对象 + $this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType); + // 获取所有回调事件,进行合并 + $callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks); + // 注册回调事件 + $this->registerSwooleEvents($this->server, $callbacks, $name); + // 设置运行参数 + $this->server->set(array_replace($config->getSettings(), $server->getSettings())); + // 将服务以键值对的形式添加到ServerManager中 + ServerManager::add($name, [$type, current($this->server->ports)]); + // 如果类存在,则触发BeforeMainServerStart事件 + 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}(); - } - } + // 如果设置了ON_BEFORE_START回调,则运行该回调 + 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)) { + // 触发BeforeServerStart事件 + $this->eventDispatcher->dispatch(new BeforeServerStart($name)); + } +} // [!code focus] +``` - if (class_exists(BeforeServerStart::class)) { - // Trigger BeforeServerStart event. - $this->eventDispatcher->dispatch(new BeforeServerStart($name)); - } +```php [Port类] +namespace Hyperf\Server; + +class Port +{ + protected string $name = 'http'; + + protected int $type = ServerInterface::SERVER_HTTP; + + protected string $host = '0.0.0.0'; + + protected int $port = 9501; + + protected int $sockType = 0; + + protected array $callbacks = []; + + protected array $settings = []; + + protected ?Option $options = null; + + public static function build(array $config): static + { + $config = self::filter($config); + + $port = new static(); + isset($config['name']) && $port->setName($config['name']); + isset($config['type']) && $port->setType($config['type']); + isset($config['host']) && $port->setHost($config['host']); + isset($config['port']) && $port->setPort($config['port']); + isset($config['sock_type']) && $port->setSockType($config['sock_type']); + isset($config['callbacks']) && $port->setCallbacks($config['callbacks']); + isset($config['settings']) && $port->setSettings($config['settings']); + isset($config['options']) && $port->setOptions(Option::make($config['options'])); + + return $port; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + return $this; + } + + public function getType(): int + { + return $this->type; + } + + public function setType(int $type): static + { + $this->type = $type; + return $this; + } + + public function getHost(): string + { + return $this->host; + } + + public function setHost(string $host): static + { + $this->host = $host; + return $this; + } + + public function getPort(): int + { + return $this->port; + } + + public function setPort(int $port): static + { + $this->port = $port; + return $this; + } + + public function getSockType(): int + { + return $this->sockType; + } + + public function setSockType(int $sockType): static + { + $this->sockType = $sockType; + return $this; + } + + public function getCallbacks(): array + { + return $this->callbacks; + } + + public function setCallbacks(array $callbacks): static + { + $this->callbacks = $callbacks; + return $this; + } + + public function getSettings(): array + { + return $this->settings; + } + + public function setSettings(array $settings): static + { + $this->settings = $settings; + return $this; + } + + public function getOptions(): ?Option + { + return $this->options; + } + + public function setOptions(Option $options): static + { + $this->options = $options; + return $this; + } + + private static function filter(array $config): array + { + if ((int) $config['type'] === ServerInterface::SERVER_BASE) { + $default = [ + 'open_http2_protocol' => false, + 'open_http_protocol' => false, + ]; + + $config['settings'] = array_merge($default, $config['settings'] ?? []); + } + + return $config; + } } ``` -###### makeServer方法 +```php [config/server配置文件] +return [ + 'mode' => SWOOLE_PROCESS, + 'servers' => [ + [ + 'name' => 'http', + 'type' => Server::SERVER_HTTP, + 'host' => '0.0.0.0', + 'port' => 9501, + 'sock_type' => SWOOLE_SOCK_TCP, + 'callbacks' => [ + Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'], + ], + 'options' => [ + // Whether to enable request lifecycle event + 'enable_request_lifecycle' => false, + ], + ], + ], + 'settings' => [ + Constant::OPTION_ENABLE_COROUTINE => true, + Constant::OPTION_WORKER_NUM => swoole_cpu_num(), + Constant::OPTION_PID_FILE => BASE_PATH . '/runtime/hyperf.pid', + Constant::OPTION_OPEN_TCP_NODELAY => true, + Constant::OPTION_MAX_COROUTINE => 100000, + Constant::OPTION_OPEN_HTTP2_PROTOCOL => true, + Constant::OPTION_MAX_REQUEST => 100000, + Constant::OPTION_SOCKET_BUFFER_SIZE => 2 * 1024 * 1024, + Constant::OPTION_BUFFER_OUTPUT_SIZE => 2 * 1024 * 1024, + // 将 public 替换为上传目录 + 'document_root' => BASE_PATH . '/public', + 'enable_static_handler' => true, + ], + 'callbacks' => [ + Event::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'], + Event::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'], + Event::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'], + ], +]; +``` -分析可知,`makeServer`方法的作用就是根据`$type`参数,返回对应的`Server`类型。 -根据配置文件中的`type`参数可知,返回的是一个`Swoole\Server`对象实例。 +::: -```php +##### initServers + +分析可知,`makeServer`方法的作用就是根据`$type`参数,默认返回对应的`Server`类型。 +根据配置文件中的`type`参数可知,返回的是一个`Swoole\Http\Server`对象实例。 +::: code-group + +```php [makeServer] +$this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType); +``` + +```php [具体实现] +use Swoole\Http\Server as SwooleHttpServer; use Swoole\Server as SwooleServer; +use Swoole\WebSocket\Server as SwooleWebSocketServer; protected function makeServer(int $type, string $host, int $port, int $mode, int $sockType): SwooleServer { switch ($type) { + // HTTP类型 (默认) case ServerInterface::SERVER_HTTP: return new SwooleHttpServer($host, $port, $mode, $sockType); + // WebSocket类型 (默认) case ServerInterface::SERVER_WEBSOCKET: return new SwooleWebSocketServer($host, $port, $mode, $sockType); + // 基础服务TCP类型 case ServerInterface::SERVER_BASE: return new SwooleServer($host, $port, $mode, $sockType); } @@ -1159,146 +1368,267 @@ protected function makeServer(int $type, string $host, int $port, int $mode, int } ``` -###### registerSwooleEvents方法 +::: -看这一行, +接下来将回调合并, 合并顺序: 默认回调 -> 配置回调 -> 传递的回调, `server`数组中的配置最大,可以覆盖`Server`、默认回调的配置。 +::: code-group + +```php +$callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks); +``` + +```php [具体实现] +protected function defaultCallbacks() +{ + $hasCallback = class_exists(Bootstrap\StartCallback::class) + && class_exists(Bootstrap\ManagerStartCallback::class) + && class_exists(Bootstrap\WorkerStartCallback::class); + + if ($hasCallback) { + $callbacks = [ + Event::ON_MANAGER_START => [Bootstrap\ManagerStartCallback::class, 'onManagerStart'], + Event::ON_WORKER_START => [Bootstrap\WorkerStartCallback::class, 'onWorkerStart'], + Event::ON_WORKER_STOP => [Bootstrap\WorkerStopCallback::class, 'onWorkerStop'], + Event::ON_WORKER_EXIT => [Bootstrap\WorkerExitCallback::class, 'onWorkerExit'], + ]; + if ($this->server->mode === SWOOLE_BASE) { + return $callbacks; + } + + return array_merge([ + Event::ON_START => [Bootstrap\StartCallback::class, 'onStart'], + ], $callbacks); + } + + return [ + Event::ON_WORKER_START => function (SwooleServer $server, int $workerId) { + printf('Worker %d started.' . PHP_EOL, $workerId); + }, + ]; +} +``` + +::: + +根据服务名注册回调, +::: details 默认回调列表 + +```php +Array +( + [start] => Array + ( + [0] => Hyperf\Framework\Bootstrap\StartCallback + [1] => onStart + ) + + [managerStart] => Array + ( + [0] => Hyperf\Framework\Bootstrap\ManagerStartCallback + [1] => onManagerStart + ) + + [workerStart] => Array + ( + [0] => Hyperf\Framework\Bootstrap\WorkerStartCallback + [1] => onWorkerStart + ) + + [workerStop] => Array + ( + [0] => Hyperf\Framework\Bootstrap\WorkerStopCallback + [1] => onWorkerStop + ) + + [workerExit] => Array + ( + [0] => Hyperf\Framework\Bootstrap\WorkerExitCallback + [1] => onWorkerExit + ) + + [pipeMessage] => Array + ( + [0] => Hyperf\Framework\Bootstrap\PipeMessageCallback + [1] => onPipeMessage + ) + + [request] => Array + ( + [0] => Hyperf\HttpServer\Server + [1] => onRequest + ) + +) +``` + +::: +::: code-group ```php $this->registerSwooleEvents($this->server, $callbacks, $name); +``` +```php [具体实现] protected function registerSwooleEvents(SwoolePort|SwooleServer $server, array $events, string $serverName): void - { - foreach ($events as $event => $callback) { - // beforeStart不是swoole回调事件,会跳过 - if (! Event::isSwooleEvent($event)) { - continue; - } - if (is_array($callback)) { - // 解构出类名和方法名 - [$className, $method] = $callback; - // 如果类名+方法名已经存在,追加到控制台输出警告信息 - if (array_key_exists($className . $method, $this->onRequestCallbacks)) { - $this->logger->warning(sprintf('%s will be replaced by %s. Each server should have its own onRequest callback. Please check your configs.', $this->onRequestCallbacks[$className . $method], $serverName)); - } - // 追加到onRequestCallbacks数组中 - $this->onRequestCallbacks[$className . $method] = $serverName; - // 从容器中解析出回调事件对象实例 - $class = $this->container->get($className); - if (method_exists($class, 'setServerName')) { - // Override the server name. - $class->setServerName($serverName); - } - // 最后一个回调事件中,Hyperf\HttpServer\Server是MiddlewareInitializerInterface的实现类 - // 所以会进入该if - // 这里会初始化中间件 - if ($class instanceof MiddlewareInitializerInterface) { - $class->initCoreMiddleware($serverName); - } - $callback = [$class, $method]; - } - // 在server上注册回调事件 - $server->on($event, $callback); - } - } -``` - -这是所有的回调事件,如下图 -![](https://oss.xiaokeaii.top/2024/event.png) - -查看以下代码,这里进行配置和初始化中间件相关的内容 - -```php -$class->initCoreMiddleware($serverName); -``` - -> 文件位置:/vendor/hyperf/http-server/src/Server.php - -```php -public function initCoreMiddleware(string $serverName): void { - // http - $this->serverName = $serverName; - // 创建核心中间件对象实例 - $this->coreMiddleware = $this->createCoreMiddleware(); - - $config = $this->container->get(ConfigInterface::class); - // 获取配置的中间件 - $this->middlewares = $config->get('middlewares.' . $serverName, []); - // 设置异常处理器 - $this->exceptionHandlers = $config->get('exceptions.handler.' . $serverName, $this->getDefaultExceptionHandler()); - // 设置服务的option选项,对中间件进行排序 - $this->initOption(); -} -``` - -逐行分析, - -```php -protected function createCoreMiddleware(): CoreMiddlewareInterface -{ - return make(CoreMiddleware::class, [$this->container, $this->serverName]); -} -// make方法从容器中获取对象实例 -function make(string $name, array $parameters = []) -{ - if (ApplicationContext::hasContainer()) { - /** @var \Hyperf\Di\Container $container */ - $container = ApplicationContext::getContainer(); - if (method_exists($container, 'make')) { - return $container->make($name, $parameters); + foreach ($events as $event => $callback) { + if (! Event::isSwooleEvent($event)) { + continue; } + if (is_array($callback)) { + // 获取事件对应class和回调方法 + [$className, $method] = $callback; + if (array_key_exists($className . $method, $this->onRequestCallbacks)) { + $this->logger->warning(sprintf('%s will be replaced by %s. Each server should have its own onRequest callback. Please check your configs.', $this->onRequestCallbacks[$className . $method], $serverName)); + } + // 记录回调 + $this->onRequestCallbacks[$className . $method] = $serverName; + // 解析回调类 + $class = $this->container->get($className); + // 设置服务名 + if (method_exists($class, 'setServerName')) { + // Override the server name. + $class->setServerName($serverName); + } + // 如果类实现MiddlewareInitializerInterface接口,则调用初始化中间件方法 + if ($class instanceof MiddlewareInitializerInterface) { + $class->initCoreMiddleware($serverName); + } + $callback = [$class, $method]; + } + // 注册事件回调函数 + $server->on($event, $callback); } - $parameters = array_values($parameters); - return new $name(...$parameters); -} - -protected function initOption(): void -{ - $ports = $this->container->get(ServerFactory::class)->getConfig()?->getServers(); - if (! $ports) { - return; - } - - foreach ($ports as $port) { - if ($port->getName() === $this->serverName) { - $this->option = $port->getOptions(); - } - } - - $this->option ??= Option::make([]); - $this->option->setMustSortMiddlewaresByMiddlewares($this->middlewares); } ``` -###### ServerManager::add方法 +::: +::: tip +当`http`请求过来时,会触发`request`事件,从注册的事件列表中可知,会调用`Hyperf\HttpServer\Server`的`onRequest`方法。 +::: -上面分析完了,继续看下面的代码。 +:::warning 参考 +初始化中间件操作,请参考[附录5](./附录5%20初始化中间件){target="_blank"} +::: + +接下来设置运行时的一些参数,列表如下, +::: details 运行参数 ```php -// 设置sever运行时的参数 -$this->server->set(array_replace($config->getSettings(), $server->getSettings())); -// 添加到ServerManager中 -ServerManager::add($name, [$type, current($this->server->ports)]); +Array +( + [enable_coroutine] => 1 + [worker_num] => 4 + [pid_file] => /opt/www/runtime/hyperf.pid + [open_tcp_nodelay] => 1 + [max_coroutine] => 100000 + [open_http2_protocol] => 1 + [max_request] => 100000 + [socket_buffer_size] => 2097152 + [buffer_output_size] => 2097152 + [document_root] => /opt/www/public + [enable_static_handler] => 1 +) ``` +::: +同样的,合并`setting`,`server`配置会替换 `settings`下的某些配置。 + +`setting`的具体配置可参考[swoole官方文档](https://wiki.swoole.com/#/server/setting) + +```php +$this->server->set(array_replace($config->getSettings(), $server->getSettings())); +``` + +最后,添加服务信息到`ServerManager`,触发`BeforeMainServerStart`事件 + +```php +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())); +} +``` + +当存在多个`server`时,后续`server`启动,会通过`else`启动,同样的,设置运行参数,注册事件回调,添加信息到`ServerManager` + +```php +if (! $this->server instanceof SwooleServer) { + $this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType); + $callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks); + $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 { // [!code focus:13] + /** @var bool|SwoolePort $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]); +} +``` + +`initServers`方法执行完成,返回自身 + +```php +public function init(ServerConfig $config): ServerInterface +{ + $this->initServers($config); + + return $this; // [!code focus] +} +``` + +###### startServer + 至此,`Hyperf\Server\Server`的`init`方法走完,返回自身实例。 `Hyperf\Server\ServerFactory`工厂类`configure`方法完成。 回到`StartServer`类的`execute`方法。 - -该方法的`$serverFactory->configure($serverConfig);`这一行执行完成。 - ```php -// -Coroutine::set(['hook_flags' => swoole_hook_flags()]); -``` -##### start方法 +protected function execute(InputInterface $input, OutputInterface $output): int +{ + $this->checkEnvironment($output); + + $serverFactory = $this->container->get(ServerFactory::class) + ->setEventDispatcher($this->container->get(EventDispatcherInterface::class)) + ->setLogger($this->container->get(StdoutLoggerInterface::class)); + + $serverConfig = $this->container->get(ConfigInterface::class)->get('server', []); + if (! $serverConfig) { + throw new InvalidArgumentException('At least one server should be defined.'); + } + + $serverFactory->configure($serverConfig); + + Coroutine::set(['hook_flags' => swoole_hook_flags()]); + + $serverFactory->start(); // [!code focus] + + return 0; +} +``` +::: tip +该方法的`$serverFactory->configure($serverConfig);`这一行执行完成,返回`Hyperf\Server\Server`对象,然后调用`start`方法,启动服务。 +::: +#### start方法 终于可以启动服务了, > 文件位置:/vendor/hyperf/server/src/ServerFactory.php ```php -$serverFactory->start(); + public function start(): void { $this->getServer()->start(); @@ -1315,5 +1645,7 @@ public function start(): void } ``` +::: tip 其中`$this->server`对应的是`Swoole\Server`实例。调用`start`方法,实际就是启动`Swoole`的服务。 此时`server`启动,等待请求。 +::: diff --git a/src/hyperf/请求.md b/src/hyperf/接收请求.md similarity index 94% rename from src/hyperf/请求.md rename to src/hyperf/接收请求.md index cc96450..20259ad 100644 --- a/src/hyperf/请求.md +++ b/src/hyperf/接收请求.md @@ -1,9 +1,12 @@ --- -title: 请求 +title: 接收请求 --- +# 接收请求 由前一节可知,事件的注册在`Hyerf\Server\Server`类的`registerSwooleEvents`方法中完成。 + ![](https://oss.xiaokeaii.top/2024/event.png) + 注册的回调事件的执行顺序如下: | 所属类 | 事件 | 描述 | 触发时机 | 触发顺序 | | --- | --- | --- | --- | --- | @@ -14,14 +17,18 @@ title: 请求 | WorkerExitCallback | onWorkerExit | 当 worker 进程退出后触发 | 仅当 worker 进程退出后触发 | 5 | | Server | onRequest | 当接收到请求时触发 | 仅当 worker 进程接收到请求后触发 | 6 | | PipeMessageCallback | onPipeMessage | 当接收到消息时触发 | 仅当 worker 进程接收到消息后触发 | 7 | -> 要理解上面事件,请参考`Swoole`的运行流程。 +::: tip + 要理解上面事件,请参考`Swoole`的运行流程。 +::: ### Swoole流程 + 首先了解`Swoole`的运行流程,(图源网络) ![](https://oss.xiaokeaii.top/2024/swoole流程.jpg) 具体内容参考[swoole官网](https://wiki.swoole.com/) -当请求过来时,会触发`onRequest`事件,然后执行`onRequest`回调函数。 - +::: tip +当请求过来时,会触发`Request`事件,基于之前准备阶段注册的`swoole`事件可知,会调用`Hyperf\HttpServer\Server`类的`onRequest`函数。 +::: ### OnRequest方法 ```php diff --git a/src/hyperf/附录4 Application_Run.md b/src/hyperf/附录4 Application_Run copy.md similarity index 100% rename from src/hyperf/附录4 Application_Run.md rename to src/hyperf/附录4 Application_Run copy.md diff --git a/src/hyperf/附录5 初始化中间件.md b/src/hyperf/附录5 初始化中间件.md new file mode 100644 index 0000000..b8fd531 --- /dev/null +++ b/src/hyperf/附录5 初始化中间件.md @@ -0,0 +1,607 @@ +--- +title: 附录5 初始化中间件 +--- +# 初始化中间件 + +在注册服务,注册服务的事件时,有一个初始化中间件的操作。 + +```php +protected function registerSwooleEvents(SwoolePort|SwooleServer $server, array $events, string $serverName): void +{ + foreach ($events as $event => $callback) { + if (! Event::isSwooleEvent($event)) { + continue; + } + if (is_array($callback)) { + // 获取事件对应class和回调方法 + [$className, $method] = $callback; + if (array_key_exists($className . $method, $this->onRequestCallbacks)) { + $this->logger->warning(sprintf('%s will be replaced by %s. Each server should have its own onRequest callback. Please check your configs.', $this->onRequestCallbacks[$className . $method], $serverName)); + } + // 记录回调 + $this->onRequestCallbacks[$className . $method] = $serverName; + // 解析回调类 + $class = $this->container->get($className); + // 设置服务名 + if (method_exists($class, 'setServerName')) { + // Override the server name. + $class->setServerName($serverName); + } + // 如果类实现MiddlewareInitializerInterface接口,则调用初始化中间件方法 // [!code focus:4] + if ($class instanceof MiddlewareInitializerInterface) { + $class->initCoreMiddleware($serverName); + } + $callback = [$class, $method]; + } + // 注册事件回调函数 + $server->on($event, $callback); + } +} +``` + +其中,默认注册的回调事件有以下几种,我们看`request`回调,其中回调的类是`Hyperf\HttpServer\Server`类, +::: details 回调事件 + +```php +Array +( + [start] => Array + ( + [0] => Hyperf\Framework\Bootstrap\StartCallback + [1] => onStart + ) + + [managerStart] => Array + ( + [0] => Hyperf\Framework\Bootstrap\ManagerStartCallback + [1] => onManagerStart + ) + + [workerStart] => Array + ( + [0] => Hyperf\Framework\Bootstrap\WorkerStartCallback + [1] => onWorkerStart + ) + + [workerStop] => Array + ( + [0] => Hyperf\Framework\Bootstrap\WorkerStopCallback + [1] => onWorkerStop + ) + + [workerExit] => Array + ( + [0] => Hyperf\Framework\Bootstrap\WorkerExitCallback + [1] => onWorkerExit + ) + + [pipeMessage] => Array + ( + [0] => Hyperf\Framework\Bootstrap\PipeMessageCallback + [1] => onPipeMessage + ) + + [request] => Array + ( + [0] => Hyperf\HttpServer\Server + [1] => onRequest + ) + +) +``` + +::: +查看`Hyperf\HttpServer\Server`类, +::: details `Hyperf\HttpServer\Server` + +```php +namespace Hyperf\HttpServer; + +use FastRoute\Dispatcher; +use Hyperf\Context\RequestContext; +use Hyperf\Context\ResponseContext; +use Hyperf\Contract\ConfigInterface; +use Hyperf\Contract\MiddlewareInitializerInterface; +use Hyperf\Contract\OnRequestInterface; +use Hyperf\Coordinator\Constants; +use Hyperf\Coordinator\CoordinatorManager; +use Hyperf\Dispatcher\HttpDispatcher; +use Hyperf\Engine\Http\WritableConnection; +use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; +use Hyperf\HttpMessage\Server\Request as Psr7Request; +use Hyperf\HttpMessage\Server\Response as Psr7Response; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; +use Hyperf\HttpServer\Event\RequestHandled; +use Hyperf\HttpServer\Event\RequestReceived; +use Hyperf\HttpServer\Event\RequestTerminated; +use Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler; +use Hyperf\HttpServer\Router\Dispatched; +use Hyperf\HttpServer\Router\DispatcherFactory; +use Hyperf\Server\Option; +use Hyperf\Server\ServerFactory; +use Hyperf\Support\SafeCaller; +use Psr\Container\ContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Throwable; + +use function Hyperf\Coroutine\defer; +use function Hyperf\Support\make; + +//该类实现了MiddlewareInitializerInterface接口 // [!code focus:2] +class Server implements OnRequestInterface, MiddlewareInitializerInterface +{ + protected array $middlewares = []; + + protected ?CoreMiddlewareInterface $coreMiddleware = null; + + protected array $exceptionHandlers = []; + + protected ?string $serverName = null; + + protected ?EventDispatcherInterface $event = null; + + protected ?Option $option = null; + + public function __construct( + protected ContainerInterface $container, + protected HttpDispatcher $dispatcher, + protected ExceptionHandlerDispatcher $exceptionHandlerDispatcher, + protected ResponseEmitter $responseEmitter + ) { + if ($this->container->has(EventDispatcherInterface::class)) { + $this->event = $this->container->get(EventDispatcherInterface::class); + } + } + //.... +} + +``` + +::: +所以该类会调用`initCoreMiddleware`方法,进行中间件的初始化。 + +```php +$class->initCoreMiddleware($serverName); +``` + +> 文件位置:/vendor/hyperf/http-server/src/Server.php + +```php +public function initCoreMiddleware(string $serverName): void +{ + // http + $this->serverName = $serverName; + // 创建核心中间件对象实例 + $this->coreMiddleware = $this->createCoreMiddleware(); + + $config = $this->container->get(ConfigInterface::class); + // 获取配置的中间件 + $this->middlewares = $config->get('middlewares.' . $serverName, []); + // 设置异常处理器 + $this->exceptionHandlers = $config->get('exceptions.handler.' . $serverName, $this->getDefaultExceptionHandler()); + // 设置服务的option选项,对中间件进行排序 + $this->initOption(); +} +``` + +## 创建中间件对象实例 + +首先创建核心中间件对象实例,通过`make`方法创建。 + +```php +protected function createCoreMiddleware(): CoreMiddlewareInterface // [!code focus:4] +{ + return make(CoreMiddleware::class, [$this->container, $this->serverName]); +} + + +function make(string $name, array $parameters = []) +{ + if (ApplicationContext::hasContainer()) { + /** @var \Hyperf\Di\Container $container */ + $container = ApplicationContext::getContainer(); + if (method_exists($container, 'make')) { + return $container->make($name, $parameters); + } + } + $parameters = array_values($parameters); + return new $name(...$parameters); +} + +protected function initOption(): void +{ + $ports = $this->container->get(ServerFactory::class)->getConfig()?->getServers(); + if (! $ports) { + return; + } + + foreach ($ports as $port) { + if ($port->getName() === $this->serverName) { + $this->option = $port->getOptions(); + } + } + + $this->option ??= Option::make([]); + $this->option->setMustSortMiddlewaresByMiddlewares($this->middlewares); +} +``` + +实际上`make`方法还是从依赖注入容器中使用`make`方法创建对象。 + +### CoreMiddleware实例化过程 + +::: code-group +```php [构造函数] +public function __construct(protected ContainerInterface $container, private string $serverName) +{ + // 创建调度器 + $this->dispatcher = $this->createDispatcher($serverName); + // 获取标准化接口实例 + $this->normalizer = $this->container->get(NormalizerInterface::class); + // 获取方法定义收集器实例 + $this->methodDefinitionCollector = $this->container->get(MethodDefinitionCollectorInterface::class); + // 如果存在,获取闭包定义收集器实例 + if ($this->container->has(ClosureDefinitionCollectorInterface::class)) { + $this->closureDefinitionCollector = $this->container->get(ClosureDefinitionCollectorInterface::class); + } +} + + +protected function createDispatcher(string $serverName): Dispatcher +{ + // 从容器中获取DispatcherFactory实例 + $factory = $this->container->get(DispatcherFactory::class); + // 获取调度器实例 + return $factory->getDispatcher($serverName); +} +``` +```php [CoreMiddleware类源码] +namespace Hyperf\HttpServer; + +use Closure; +use FastRoute\Dispatcher; +use Hyperf\Codec\Json; +use Hyperf\Context\Context; +use Hyperf\Context\RequestContext; +use Hyperf\Context\ResponseContext; +use Hyperf\Contract\Arrayable; +use Hyperf\Contract\Jsonable; +use Hyperf\Contract\NormalizerInterface; +use Hyperf\Di\ClosureDefinitionCollectorInterface; +use Hyperf\Di\MethodDefinitionCollectorInterface; +use Hyperf\Di\ReflectionType; +use Hyperf\HttpMessage\Exception\MethodNotAllowedHttpException; +use Hyperf\HttpMessage\Exception\NotFoundHttpException; +use Hyperf\HttpMessage\Exception\ServerErrorHttpException; +use Hyperf\HttpMessage\Server\ResponsePlusProxy; +use Hyperf\HttpMessage\Stream\SwooleStream; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; +use Hyperf\HttpServer\Router\Dispatched; +use Hyperf\HttpServer\Router\DispatcherFactory; +use Hyperf\Server\Exception\ServerException; +use InvalidArgumentException; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\RequestHandlerInterface; +use RuntimeException; +use Swow\Psr7\Message\ResponsePlusInterface; + +class CoreMiddleware implements CoreMiddlewareInterface +{ + protected Dispatcher $dispatcher; + + private MethodDefinitionCollectorInterface $methodDefinitionCollector; + + private ?ClosureDefinitionCollectorInterface $closureDefinitionCollector = null; + + private NormalizerInterface $normalizer; + + public function __construct(protected ContainerInterface $container, private string $serverName) + { + $this->dispatcher = $this->createDispatcher($serverName); + $this->normalizer = $this->container->get(NormalizerInterface::class); + $this->methodDefinitionCollector = $this->container->get(MethodDefinitionCollectorInterface::class); + if ($this->container->has(ClosureDefinitionCollectorInterface::class)) { + $this->closureDefinitionCollector = $this->container->get(ClosureDefinitionCollectorInterface::class); + } + } + + public function dispatch(ServerRequestInterface $request): ServerRequestInterface + { + $routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); + + $dispatched = new Dispatched($routes, $this->serverName); + + return RequestContext::set($request)->setAttribute(Dispatched::class, $dispatched); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $request = RequestContext::set($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 = match ($dispatched->status) { + Dispatcher::NOT_FOUND => $this->handleNotFound($request), + Dispatcher::METHOD_NOT_ALLOWED => $this->handleMethodNotAllowed($dispatched->params, $request), + Dispatcher::FOUND => $this->handleFound($dispatched, $request), + default => null, + }; + + if (! $response instanceof ResponsePlusInterface) { + $response = $this->transferToResponse($response, $request); + } + + return $response->addHeader('Server', 'Hyperf'); + } + + public function getMethodDefinitionCollector(): MethodDefinitionCollectorInterface + { + return $this->methodDefinitionCollector; + } + + public function getClosureDefinitionCollector(): ClosureDefinitionCollectorInterface + { + return $this->closureDefinitionCollector; + } + + public function getNormalizer(): NormalizerInterface + { + return $this->normalizer; + } + + protected function createDispatcher(string $serverName): Dispatcher + { + $factory = $this->container->get(DispatcherFactory::class); + return $factory->getDispatcher($serverName); + } + + /** + * Handle the response when found. + * + * @return array|Arrayable|mixed|ResponseInterface|string + */ + protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request): mixed + { + if ($dispatched->handler->callback instanceof Closure) { + $parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params); + $callback = $dispatched->handler->callback; + $response = $callback(...$parameters); + } else { + [$controller, $action] = $this->prepareHandler($dispatched->handler->callback); + $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); + $response = $controllerInstance->{$action}(...$parameters); + } + return $response; + } + + /** + * Handle the response when cannot found any routes. + */ + protected function handleNotFound(ServerRequestInterface $request): mixed + { + throw new NotFoundHttpException(); + } + + /** + * Handle the response when the routes found but doesn't match any available methods. + */ + protected function handleMethodNotAllowed(array $methods, ServerRequestInterface $request): mixed + { + throw new MethodNotAllowedHttpException('Allow: ' . implode(', ', $methods)); + } + + protected function prepareHandler(array|string $handler): array + { + if (is_string($handler)) { + if (str_contains($handler, '@')) { + return explode('@', $handler); + } + if (str_contains($handler, '::')) { + return explode('::', $handler); + } + return [$handler, '__invoke']; + } + if (is_array($handler) && isset($handler[0], $handler[1])) { + return $handler; + } + throw new RuntimeException('Handler not exist.'); + } + + /** + * Transfer the non-standard response content to a standard response object. + * + * @param null|array|Arrayable|Jsonable|ResponseInterface|string $response + */ + protected function transferToResponse($response, ServerRequestInterface $request): ResponsePlusInterface + { + if (is_string($response)) { + return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream($response)); + } + + if ($response instanceof ResponseInterface) { + return new ResponsePlusProxy($response); + } + + if (is_array($response) || $response instanceof Arrayable) { + return $this->response() + ->addHeader('content-type', 'application/json') + ->setBody(new SwooleStream(Json::encode($response))); + } + + if ($response instanceof Jsonable) { + return $this->response() + ->addHeader('content-type', 'application/json') + ->setBody(new SwooleStream((string) $response)); + } + + if ($this->response()->hasHeader('content-type')) { + return $this->response()->setBody(new SwooleStream((string) $response)); + } + + return $this->response()->addHeader('content-type', 'text/plain')->setBody(new SwooleStream((string) $response)); + } + + /** + * Get response instance from context. + */ + protected function response(): ResponsePlusInterface + { + return ResponseContext::get(); + } + + /** + * Parse the parameters of method definitions, and then bind the specified arguments or + * get the value from DI container, combine to an argument array that should be injected + * and return the array. + */ + protected function parseMethodParameters(string $controller, string $action, array $arguments): array + { + $definitions = $this->getMethodDefinitionCollector()->getParameters($controller, $action); + return $this->getInjections($definitions, "{$controller}::{$action}", $arguments); + } + + /** + * Parse the parameters of closure definitions, and then bind the specified arguments or + * get the value from DI container, combine to an argument array that should be injected + * and return the array. + */ + protected function parseClosureParameters(Closure $closure, array $arguments): array + { + if (! $this->container->has(ClosureDefinitionCollectorInterface::class)) { + return []; + } + $definitions = $this->getClosureDefinitionCollector()->getParameters($closure); + return $this->getInjections($definitions, 'Closure', $arguments); + } + + /** + * @param ReflectionType[] $definitions + */ + private function getInjections(array $definitions, string $callableName, array $arguments): array + { + $injections = []; + foreach ($definitions as $pos => $definition) { + $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? null; + if ($value === null) { + if ($definition->getMeta('defaultValueAvailable')) { + $injections[] = $definition->getMeta('defaultValue'); + } elseif ($this->container->has($definition->getName())) { + $injections[] = $this->container->get($definition->getName()); + } elseif ($definition->allowsNull()) { + $injections[] = null; + } else { + throw new InvalidArgumentException("Parameter '{$definition->getMeta('name')}' " + . "of {$callableName} should not be null"); + } + } else { + $injections[] = $this->getNormalizer()->denormalize($value, $definition->getName()); + } + } + return $injections; + } +} + +``` +::: + +`DispatcherFactory`类在实例化时,调用构造方法,进行初始化路由和调度器 +```php +public function __construct() +{ + // 根据注解初始化路由 + $this->initAnnotationRoute(AnnotationCollector::list()); + // 根据配置文件初始化路由 + $this->initConfigRoute(); +} + +public function getDispatcher(string $serverName): Dispatcher +{ + if (isset($this->dispatchers[$serverName])) { + return $this->dispatchers[$serverName]; + } + + $router = $this->getRouter($serverName); + return $this->dispatchers[$serverName] = new GroupCountBased($router->getData()); +} +``` +CoreMiddleware实例化完成。 + +```php + +public function initCoreMiddleware(string $serverName): void +{ + $this->serverName = $serverName; + // 这里保存了所有的路由信息 + $this->coreMiddleware = $this->createCoreMiddleware(); + // 获取配置文件 + $config = $this->container->get(ConfigInterface::class); // [!code focus:5] + // 设置配置的中间件数组 + $this->middlewares = $config->get('middlewares.' . $serverName, []); + // 设置异常处理器 + $this->exceptionHandlers = $config->get('exceptions.handler.' . $serverName, $this->getDefaultExceptionHandler()); + + $this->initOption(); +} +``` +### 设置异常处理器 +配置服务的异常处理程序,首先从配置文件`exceptions`中,获取配置的异常处理器,如不存在,则使用默认的异常处理器。 +```php +$this->exceptionHandlers = $config->get('exceptions.handler.' . $serverName, $this->getDefaultExceptionHandler()); + +protected function getDefaultExceptionHandler(): array +{ + return [ + HttpExceptionHandler::class, + ]; +} +``` + +### 初始化配置 +设置`Server`的`option`参数 +```php + +protected function initOption(): void +{ + // 获取服务器配置对象数组,类型为port[] + $ports = $this->container->get(ServerFactory::class)->getConfig()?->getServers(); + if (! $ports) { + return; + } + // 设置服务的option配置 + foreach ($ports as $port) { + if ($port->getName() === $this->serverName) { + $this->option = $port->getOptions(); + } + } + + $this->option ??= Option::make([]); + // 对中间件进行排序 + $this->option->setMustSortMiddlewaresByMiddlewares($this->middlewares); +} + +// 如果设置了顺序比如[foo::class => 1]或者继承了PriorityMiddleware中间件,都将启动中间件的排序操作 +public function setMustSortMiddlewaresByMiddlewares(array $middlewares): static +{ + foreach ($middlewares as $middleware) { + if (is_int($middleware) || $middleware instanceof PriorityMiddleware) { + return $this->setMustSortMiddlewares(true); + } + } + return $this; +} +``` +最后,初始化中间件完成,等待`http`请求。 \ No newline at end of file