From 5114010ca031a55ea5fc0c1609a80fa411a71a2c Mon Sep 17 00:00:00 2001 From: niqingyang Date: Fri, 16 Sep 2022 01:00:03 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E6=88=90=E4=B8=BB=E8=A6=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 11 + .gitignore | 3 +- LICENSE | 21 + README.md | 90 +++ composer.json | 25 +- composer.lock | 615 +++++++++++++++--- manifest.json | 9 + .../Compiler/ProcessPass.php | 40 ++ src/DependencyInjection/Configuration.php | 56 ++ .../WorkermanExtension.php | 32 + src/Process/Monitor.php | 198 ++++++ src/ProcessInterface.php | 28 + src/ProperHeaderCasingResponseFactory.php | 42 -- src/Resolver.php | 31 - src/Resources/config/packages/workerman.yaml | 32 + src/Runner.php | 227 ++++++- src/Runtime.php | 56 +- src/SymfonyRequestHandler.php | 35 - src/Utils.php | 198 ++++++ src/WorkerConfig.php | 238 +++++++ src/WorkermanBundle.php | 37 ++ 21 files changed, 1766 insertions(+), 258 deletions(-) create mode 100644 .editorconfig create mode 100644 LICENSE create mode 100644 README.md create mode 100644 manifest.json create mode 100644 src/DependencyInjection/Compiler/ProcessPass.php create mode 100644 src/DependencyInjection/Configuration.php create mode 100644 src/DependencyInjection/WorkermanExtension.php create mode 100644 src/Process/Monitor.php create mode 100644 src/ProcessInterface.php delete mode 100644 src/ProperHeaderCasingResponseFactory.php delete mode 100644 src/Resolver.php create mode 100644 src/Resources/config/packages/workerman.yaml delete mode 100644 src/SymfonyRequestHandler.php create mode 100644 src/Utils.php create mode 100644 src/WorkerConfig.php create mode 100644 src/WorkermanBundle.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3a07992 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# 🎨 editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore index 331c58f..4f38912 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea -vendor \ No newline at end of file +vendor +composer.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..92ec420 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 行风 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e39708d --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# workerman-bundle + +Symfony 的 workerman bundle,支持使用 `symfony runtime` 启动,并且支持通过 `service container` 自定义进程 + +## 安装 + +```bash +composer require wellkit/workerman-bundle +``` + +## 配置 + +1. 修改文件 `/config/bundles.php` + +```php +return [ + // ... + WellKit\WorkermanBundle\WorkermanBundle::class => ['all' => true], +]; +``` + +2. 增加配置文件 `/config/packages/workerman.yaml` + +```yaml +workerman: + # 服务配置 + server: + # 进程名称 + name: 'Symfony Workerman Server' + # 监听的协议 ip 及端口 (可选) + listen: http://0.0.0.0:8000 + # 进程数 (可选,生产环境下默认为 cpu核心数*2,其他环境下默认为1) + count: ~ + # 进程运行用户 (可选,默认当前用户) + user: '' + # 进程运行用户组 (可选,默认当前用户组) + group: '' + # 当前进程是否支持reload (可选,默认true) + reloadable: true + # 是否开启reusePort (可选,此选项需要php>=7.0,默认为true) + reusePort: true + # transport (可选,当需要开启ssl时设置为ssl,默认为tcp) + transport: tcp + # context (可选,当transport为是ssl时,需要传递证书路径) + context: [] + # After sending the stop command to the child process stopTimeout seconds, + # if the process is still living then forced to kill. + stopTimeout: 2 + # The file to store master process PID. + pidFile: '%kernel.project_dir%/var/workerman.pid' + # Log file. + logFile: '%kernel.project_dir%/var/log/workerman.log' + # The file used to store the master process status file. + statusFile: '%kernel.project_dir%/var/log/workerman.status' + # Stdout file. + stdoutFile: '%kernel.project_dir%/var/log/workerman.stdout.log' + # 自定义进程的 serviceId(功能与声明 `workerman.process` 标签相同) + processIds: + # - xxxxx +``` + +## 自定义进程 + +- 通过 `/config/services.yaml` 配置 + +```yaml +# 自定义进程的名称 +workerman.xxx: + # 自定义进程的类,必须声明 onWorkerStart 方法 + class: App\Process\XXX + # workerman-bundle 通过 `workerman.process` 标签来识别自定义进程 + tags: [ 'workerman.process' ] +``` + +## 启动 + +在项目根目录下执行 + +```bash +APP_RUNTIME=WellKit\\WorkermanBundle\\Runtime php ./public/index.php start +``` + +## 参考项目 + +- https://github.com/walkor/webman +- https://github.com/tourze/workerman-server-bundle + +## 许可证 + +MIT diff --git a/composer.json b/composer.json index 82e1398..399bc31 100644 --- a/composer.json +++ b/composer.json @@ -1,11 +1,12 @@ { - "name": "wellkit/workerman-runtime", - "type": "library", + "name": "wellkit/workerman-bundle", + "type": "symfony-bundle", "license": "MIT", - "description": "symfony workerman runtime", + "description": "symfony workerman bundle", "keywords": ["workerman", "symfony", "runtime", "php-runtime"], "homepage": "https://github.com/niqingyang/workerman-runtime", "minimum-stability": "stable", + "prefer-stable": true, "authors": [ { "name": "niqingyang", @@ -14,21 +15,31 @@ ], "require": { "symfony/runtime": "^6.0", - "symfony/http-kernel": "^6.0", + "symfony/http-kernel": "^6.1", "workerman/workerman": "^4.0", "php": ">=8.0.2", - "chubbyphp/chubbyphp-workerman-request-handler": "^2.0", "nyholm/psr7": "^1.5", - "symfony/psr-http-message-bridge": "^2.1" + "symfony/psr-http-message-bridge": "^2.1", + "league/mime-type-detection": "^1.11", + "workerman/psr7": "^1.4", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/yaml": "^6.1" }, "autoload": { "psr-4": { - "WellKit\\WorkermanRuntime\\": "src/" + "WellKit\\WorkermanBundle\\": "src/" } }, "config": { + "sort-packages": true, "allow-plugins": { "symfony/runtime": true } + }, + "extra": { + "branch-alias": { + "dev-master": "0.0.2-dev" + } } } diff --git a/composer.lock b/composer.lock index 487b91f..ecf9990 100644 --- a/composer.lock +++ b/composer.lock @@ -4,50 +4,35 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "af44e34a05756be8d37b090cb94d8164", + "content-hash": "b28a9daad6af00187fdb067d51abba20", "packages": [ { - "name": "chubbyphp/chubbyphp-workerman-request-handler", - "version": "2.0.0", + "name": "league/mime-type-detection", + "version": "1.11.0", "source": { "type": "git", - "url": "https://github.com/chubbyphp/chubbyphp-workerman-request-handler.git", - "reference": "1a19d31ef74079853b96886e715ad71d78eaf15f" + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chubbyphp/chubbyphp-workerman-request-handler/zipball/1a19d31ef74079853b96886e715ad71d78eaf15f", - "reference": "1a19d31ef74079853b96886e715ad71d78eaf15f", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", "shasum": "" }, "require": { - "php": "^8.0", - "psr/http-factory": "^1.0.1", - "psr/http-message": "^1.0.1", - "psr/http-server-handler": "^1.0.1", - "psr/log": "^1.1.4|^2.0|^3.0", - "workerman/workerman": "^4.0.30" + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" }, "require-dev": { - "blackfire/php-sdk": "^1.29", - "chubbyphp/chubbyphp-dev-helper": "dev-master", - "chubbyphp/chubbyphp-mock": "^1.6.1", - "infection/infection": "^0.26.5", - "php-coveralls/php-coveralls": "^2.5.2", - "phploc/phploc": "^7.0.2", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.4.8", - "phpunit/phpunit": "^9.5.17" + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, "autoload": { "psr-4": { - "Chubbyphp\\WorkermanRequestHandler\\": "src/" + "League\\MimeTypeDetection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -56,24 +41,26 @@ ], "authors": [ { - "name": "Dominik Zogg", - "email": "dominik.zogg@gmail.com" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "description": "A request handler adapter for workerman, using PSR-7, PSR-15 and PSR-17.", - "keywords": [ - "chubbyphp", - "psr-15", - "psr-17", - "psr-7", - "requesthandler", - "workerman" - ], + "description": "Mime-type detection for Flysystem", "support": { - "issues": "https://github.com/chubbyphp/chubbyphp-workerman-request-handler/issues", - "source": "https://github.com/chubbyphp/chubbyphp-workerman-request-handler/tree/2.0.0" + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" }, - "time": "2022-07-04T05:58:13+00:00" + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-04-17T13:12:02+00:00" }, { "name": "nyholm/psr7", @@ -206,6 +193,59 @@ }, "time": "2015-12-19T14:08:53+00:00" }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, { "name": "psr/event-dispatcher", "version": "1.0.0", @@ -365,32 +405,31 @@ "time": "2016-08-06T14:39:51+00:00" }, { - "name": "psr/http-server-handler", - "version": "1.0.1", + "name": "psr/log", + "version": "3.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=7.0", - "psr/http-message": "^1.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Server\\": "src/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -400,54 +439,149 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for HTTP server-side request handler", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "handler", - "http", - "http-interop", + "log", "psr", - "psr-15", - "psr-7", - "request", - "response", - "server" + "psr-3" ], "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" + "source": "https://github.com/php-fig/log/tree/3.0.0" }, - "time": "2018-10-30T16:46:14+00:00" + "time": "2021-07-14T16:46:02+00:00" }, { - "name": "psr/log", - "version": "3.0.0", + "name": "symfony/config", + "version": "v6.1.3", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "url": "https://github.com/symfony/config.git", + "reference": "a0645dc585d378b73c01115dd7ab9348f7d40c85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/symfony/config/zipball/a0645dc585d378b73c01115dd7ab9348f7d40c85", + "reference": "a0645dc585d378b73c01115dd7ab9348f7d40c85", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<5.4" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v6.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T15:00:40+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v6.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "079e336a1880f457b219aecc3d41bef2f1093b0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/079e336a1880f457b219aecc3d41bef2f1093b0b", + "reference": "079e336a1880f457b219aecc3d41bef2f1093b0b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/service-contracts": "^1.1.6|^2.0|^3.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<5.4", + "symfony/yaml": "<5.4" }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.1", + "symfony/expression-language": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "src" - } + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -455,21 +589,34 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/symfony/dependency-injection/tree/v6.1.3" }, - "time": "2021-07-14T16:46:02+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T13:46:29+00:00" }, { "name": "symfony/deprecation-contracts", @@ -771,6 +918,69 @@ ], "time": "2022-02-25T11:15:52+00:00" }, + { + "name": "symfony/filesystem", + "version": "v6.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "3f39c04d2630c34019907b02f85672dac99f8659" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3f39c04d2630c34019907b02f85672dac99f8659", + "reference": "3f39c04d2630c34019907b02f85672dac99f8659", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-02T16:17:38+00:00" + }, { "name": "symfony/http-foundation", "version": "v6.1.4", @@ -1285,6 +1495,91 @@ ], "time": "2022-06-27T17:24:16+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:18:58+00:00" + }, { "name": "symfony/var-dumper", "version": "v6.1.3", @@ -1373,6 +1668,148 @@ ], "time": "2022-07-20T13:46:29+00:00" }, + { + "name": "symfony/yaml", + "version": "v6.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/86ee4d8fa594ed45e40d86eedfda1bcb66c8d919", + "reference": "86ee4d8fa594ed45e40d86eedfda1bcb66c8d919", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.1.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-02T16:17:38+00:00" + }, + { + "name": "workerman/psr7", + "version": "v1.4.5", + "source": { + "type": "git", + "url": "https://github.com/walkor/psr7.git", + "reference": "c1a2ccbc2d8f30a64f2df65b22dc74f02bd39a8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/psr7/zipball/c1a2ccbc2d8f30a64f2df65b22dc74f02bd39a8c", + "reference": "c1a2ccbc2d8f30a64f2df65b22dc74f02bd39a8c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "Workerman\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "source": "https://github.com/walkor/psr7/tree/v1.4.5" + }, + "time": "2022-09-01T08:41:21+00:00" + }, { "name": "workerman/workerman", "version": "v4.1.0", @@ -1441,7 +1878,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": ">=8.0.2" diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..649dbb3 --- /dev/null +++ b/manifest.json @@ -0,0 +1,9 @@ +{ + "bundles": { + "WellKit\\WorkermanBundle\\WorkermanBundle": ["all"] + }, + "copy-from-recipe": { + "config/": "%CONFIG_DIR%/", + "src/": "%SRC_DIR%/" + } +} \ No newline at end of file diff --git a/src/DependencyInjection/Compiler/ProcessPass.php b/src/DependencyInjection/Compiler/ProcessPass.php new file mode 100644 index 0000000..b769caf --- /dev/null +++ b/src/DependencyInjection/Compiler/ProcessPass.php @@ -0,0 +1,40 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class ProcessPass implements CompilerPassInterface +{ + + public function process(ContainerBuilder $container) + { + // TODO: Implement process() method. + $serviceIds = $container->findTaggedServiceIds('workerman.process'); + + $serviceIds = array_keys($serviceIds); + + foreach ($serviceIds as $id) { + // 强制设置 public 为 true + $container->getDefinition($id)->setPublic(true); + } + + $processIds = $container->getParameter('workerman.processIds') ?? []; + $processIds = array_values(array_unique(array_merge($processIds, $serviceIds))); + + $container->setParameter('workerman.processIds', $processIds); + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..e0e6fab --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,56 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class Configuration implements ConfigurationInterface +{ + /** + * {@inheritdoc} + */ + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('workerman'); + + $treeBuilder + ->getRootNode() + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('server')->addDefaultsIfNotSet() + ->children() + ->scalarNode('name')->defaultNull()->end() + ->scalarNode('listen')->defaultNull()->end() + ->scalarNode('count')->defaultNull()->end() + ->scalarNode('user')->defaultNull()->end() + ->scalarNode('group')->defaultNull()->end() + ->booleanNode('reloadable')->defaultValue(true)->end() + ->booleanNode('reusePort')->defaultValue(true)->end() + ->enumNode('transport')->values(['tcp', 'ssl'])->defaultValue('tcp')->end() + ->arrayNode('context')->arrayPrototype()->end()->defaultValue([])->end() + ->scalarNode('stopTimeout')->defaultValue(2)->end() + ->scalarNode('pidFile')->defaultValue('')->end() + ->scalarNode('logFile')->defaultValue('')->end() + ->scalarNode('statusFile')->defaultValue('')->end() + ->scalarNode('stdoutFile')->defaultValue('')->end() + ->end() + ->end() + ->arrayNode('processIds')->scalarPrototype()->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/DependencyInjection/WorkermanExtension.php b/src/DependencyInjection/WorkermanExtension.php new file mode 100644 index 0000000..006d5b8 --- /dev/null +++ b/src/DependencyInjection/WorkermanExtension.php @@ -0,0 +1,32 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use WellKit\WorkermanBundle\DependencyInjection\Compiler\ProcessPass; +use WellKit\WorkermanBundle\Utils; + +class WorkermanExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $container->setParameter('workerman.server', $config['server']); + $container->setParameter('workerman.processIds', $config['processIds']); + } +} diff --git a/src/Process/Monitor.php b/src/Process/Monitor.php new file mode 100644 index 0000000..ba2ea6b --- /dev/null +++ b/src/Process/Monitor.php @@ -0,0 +1,198 @@ + + * @author niqingyang + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + + +namespace WellKit\WorkermanBundle\Process; + +use Workerman\Timer; +use Workerman\Worker; + +/** + * Class FileMonitor + * @package process + */ +class Monitor +{ + /** + * @var array + */ + protected array $_paths = []; + + /** + * @var array + */ + protected array $_extensions = []; + + /** + * FileMonitor constructor. + * @param $monitor_dir + * @param $monitor_extensions + * @param $memory_limit + */ + public function __construct($monitor_dir, $monitor_extensions, $memory_limit = null) + { + $this->_paths = (array)$monitor_dir; + $this->_extensions = $monitor_extensions; + if (!Worker::getAllWorkers()) { + return; + } + $disable_functions = explode(',', ini_get('disable_functions')); + if (in_array('exec', $disable_functions, true)) { + echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n"; + } else { + if (!Worker::$daemonize) { + Timer::add(1, function () { + $this->checkAllFilesChange(); + }); + } + } + + $memory_limit = $this->getMemoryLimit($memory_limit); + if ($memory_limit && DIRECTORY_SEPARATOR === '/') { + Timer::add(60, [$this, 'checkMemory'], [$memory_limit]); + } + } + + /** + * @param $monitor_dir + * @return bool|void + */ + public function checkFilesChange($monitor_dir) + { + static $last_mtime, $too_many_files_check; + if (!$last_mtime) { + $last_mtime = time(); + } + clearstatcache(); + if (!is_dir($monitor_dir)) { + if (!is_file($monitor_dir)) { + return; + } + $iterator = [new \SplFileInfo($monitor_dir)]; + } else { + // recursive traversal directory + $dir_iterator = new \RecursiveDirectoryIterator($monitor_dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new \RecursiveIteratorIterator($dir_iterator); + } + $count = 0; + foreach ($iterator as $file) { + $count++; + /** var SplFileInfo $file */ + if (is_dir($file)) { + continue; + } + // check mtime + if ($last_mtime < $file->getMTime() && in_array($file->getExtension(), $this->_extensions, true)) { + $var = 0; + exec('"' . PHP_BINARY . '" -l ' . $file, $out, $var); + if ($var) { + $last_mtime = $file->getMTime(); + continue; + } + $last_mtime = $file->getMTime(); + echo $file . " update and reload\n"; + // send SIGUSR1 signal to master process for reload + if (DIRECTORY_SEPARATOR === '/') { + posix_kill(posix_getppid(), SIGUSR1); + } else { + return true; + } + break; + } + } + if (!$too_many_files_check && $count > 1000) { + echo "Monitor: There are too many files ($count files) in $monitor_dir which makes file monitoring very slow\n"; + $too_many_files_check = 1; + } + } + + /** + * @return bool + */ + public function checkAllFilesChange(): bool + { + foreach ($this->_paths as $path) { + if ($this->checkFilesChange($path)) { + return true; + } + } + return false; + } + + /** + * @param $memory_limit + * @return void + */ + public function checkMemory($memory_limit): void + { + $ppid = posix_getppid(); + $children_file = "/proc/$ppid/task/$ppid/children"; + if (!is_file($children_file) || !($children = file_get_contents($children_file))) { + return; + } + foreach (explode(' ', $children) as $pid) { + $pid = (int)$pid; + $status_file = "/proc/$pid/status"; + if (!is_file($status_file) || !($status = file_get_contents($status_file))) { + continue; + } + $mem = 0; + if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) { + $mem = $match[1]; + } + $mem = (int)($mem / 1024); + if ($mem >= $memory_limit) { + posix_kill($pid, SIGINT); + } + } + } + + /** + * Get memory limit + * @return float|int + */ + protected function getMemoryLimit($memory_limit): float|int + { + if ($memory_limit === 0) { + return 0; + } + $use_php_ini = false; + if (!$memory_limit) { + $memory_limit = ini_get('memory_limit'); + $use_php_ini = true; + } + + if ($memory_limit == -1) { + return 0; + } + $unit = $memory_limit[strlen($memory_limit) - 1]; + if ($unit == 'G') { + $memory_limit = 1024 * (int)$memory_limit; + } else if ($unit == 'M') { + $memory_limit = (int)$memory_limit; + } else if ($unit == 'K') { + $memory_limit = (int)($memory_limit / 1024); + } else { + $memory_limit = (int)($memory_limit / (1024 * 1024)); + } + if ($memory_limit < 30) { + $memory_limit = 30; + } + if ($use_php_ini) { + $memory_limit = (int)(0.8 * $memory_limit); + } + return $memory_limit; + } +} diff --git a/src/ProcessInterface.php b/src/ProcessInterface.php new file mode 100644 index 0000000..ae4c17e --- /dev/null +++ b/src/ProcessInterface.php @@ -0,0 +1,28 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle; + +/** + * workerman 进程接口 + */ +interface ProcessInterface +{ + /** + * 获取 worker 配置 + * + * @return WorkerConfig|null + */ + public function getWorkerConfig(): ?WorkerConfig; +} diff --git a/src/ProperHeaderCasingResponseFactory.php b/src/ProperHeaderCasingResponseFactory.php deleted file mode 100644 index 5c7fba6..0000000 --- a/src/ProperHeaderCasingResponseFactory.php +++ /dev/null @@ -1,42 +0,0 @@ - 'Server', - 'connection' => 'Connection', - 'content-type' => 'Content-Type', - 'content-disposition' => 'Content-Disposition', - 'last-modified' => 'Last-Modified', - 'transfer-encoding' => 'Transfer-Encoding', - ]; - - public function createResponse(Response $symfonyResponse): ResponseInterface - { - $response = parent::createResponse($symfonyResponse); - - $headers = $response->getHeaders(); - - foreach ($headers as $key => $value) { - $response = $response->withHeader($this->convertHeaderName($key), $value); - } - - return $response; - } - - private function convertHeaderName(string $key): string - { - if (isset(self::$cache[$key])) { - return self::$cache[$key]; - } - - return $key; - } -} \ No newline at end of file diff --git a/src/Resolver.php b/src/Resolver.php deleted file mode 100644 index c944fda..0000000 --- a/src/Resolver.php +++ /dev/null @@ -1,31 +0,0 @@ -resolver->resolve(); - - return [ - static function () use ($app, $args): Closure { - // App instantiator as an app - return static function () use ($app, $args): HttpKernelInterface { - return $app(...$args); - }; - }, - [], - ]; - } -} \ No newline at end of file diff --git a/src/Resources/config/packages/workerman.yaml b/src/Resources/config/packages/workerman.yaml new file mode 100644 index 0000000..f313bc2 --- /dev/null +++ b/src/Resources/config/packages/workerman.yaml @@ -0,0 +1,32 @@ +workerman: + # 服务配置 + server: + # 进程名称 + name: 'Symfony Workerman Server' + # 监听的协议 ip 及端口 (可选) + listen: http://0.0.0.0:8000 + # 进程数 (可选,默认1) + count: ~ + # 进程运行用户 (可选,默认当前用户) + user: '' + # 进程运行用户组 (可选,默认当前用户组) + group: '' + # 当前进程是否支持reload (可选,默认true) + reloadable: true + # 是否开启reusePort (可选,此选项需要php>=7.0,默认为true) + reusePort: true + # transport (可选,当需要开启ssl时设置为ssl,默认为tcp) + transport: tcp + # context (可选,当transport为是ssl时,需要传递证书路径) + context: [] + # After sending the stop command to the child process stopTimeout seconds, + # if the process is still living then forced to kill. + stopTimeout: 2 + # The file to store master process PID. + pidFile: '%kernel.project_dir%/var/workerman.pid' + # Log file. + logFile: '%kernel.project_dir%/var/log/workerman.log' + # The file used to store the master process status file. + statusFile: '%kernel.project_dir%/var/log/workerman.status' + # Stdout file. + stdoutFile: '%kernel.project_dir%/var/log/workerman.stdout.log' diff --git a/src/Runner.php b/src/Runner.php index 34d11bb..84d8876 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -1,65 +1,226 @@ + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ -namespace WellKit\WorkermanRuntime; +namespace WellKit\WorkermanBundle; -use Chubbyphp\WorkermanRequestHandler\OnMessage; -use Chubbyphp\WorkermanRequestHandler\PsrRequestFactory; -use Chubbyphp\WorkermanRequestHandler\PsrRequestFactoryInterface; -use Chubbyphp\WorkermanRequestHandler\WorkermanResponseEmitter; -use Chubbyphp\WorkermanRequestHandler\WorkermanResponseEmitterInterface; -use Closure; +use League\MimeTypeDetection\FinfoMimeTypeDetector; use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Response; use Psr\Log\LoggerAwareTrait; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; -use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Runtime\RunnerInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Protocols\Http; +use Workerman\Psr7\ServerRequest; use Workerman\Worker; +use function Workerman\Psr7\response_to_string; class Runner implements RunnerInterface { use LoggerAwareTrait; + public int $maxRequest = 2000; + + private PsrHttpFactory $psrHttpFactory; private HttpFoundationFactoryInterface $httpFoundationFactory; - private HttpMessageFactoryInterface $httpMessageFactory; private Psr17Factory $psr17Factory; - private PsrRequestFactoryInterface $psrRequestFactory; - private WorkermanResponseEmitterInterface $workermanResponseEmitter; + private FinfoMimeTypeDetector $detector; + private ?string $environment = null; public function __construct( - private Closure $appFactory, - private string $socket, - private int $workers, - private string $pidFile, - private string $logFile, - ) { - $this->psr17Factory = new Psr17Factory(); - $this->httpFoundationFactory = new HttpFoundationFactory(); - $this->httpMessageFactory = new ProperHeaderCasingResponseFactory($this->psr17Factory, $this->psr17Factory, $this->psr17Factory, $this->psr17Factory); - $this->psrRequestFactory = new PsrRequestFactory($this->psr17Factory, $this->psr17Factory, $this->psr17Factory); - $this->workermanResponseEmitter = new WorkermanResponseEmitter(); + private HttpKernelInterface $kernel, + // the master config + private WorkerConfig $config, + private string $pidFile, + private string $logFile, + private string $statusFile, + private string $stdoutFile, + ) + { + $this->psr17Factory = new Psr17Factory(); + $this->httpFoundationFactory = new HttpFoundationFactory(); + $this->psrHttpFactory = new PsrHttpFactory($this->psr17Factory, $this->psr17Factory, $this->psr17Factory, $this->psr17Factory); + + $this->detector = new FinfoMimeTypeDetector(); + + Http::requestClass(ServerRequest::class); + + Worker::$onMasterReload = function () { + if (function_exists('opcache_get_status')) { + if ($status = \opcache_get_status()) { + if (isset($status['scripts']) && $scripts = $status['scripts']) { + foreach (array_keys($scripts) as $file) { + \opcache_invalidate($file, true); + } + } + } + } + }; } public function run(): int { - $worker = new Worker($this->socket); - $worker->count = $this->workers; - $worker::$pidFile = $this->pidFile; - $worker::$logFile = $this->logFile; + $this->kernel->boot(); - $worker->onWorkerStart = function (Worker $worker): void { - // Instantiate HttpKernel - $kernel = ($this->appFactory)(); + /** + * @var ContainerInterface $container + */ + $container = $this->kernel->getContainer(); + + $this->environment = $container->getParameter('kernel.environment'); + $config = $this->getMasterConfig($container->getParameter('workerman.server')); + + Worker::$pidFile = $config['pidFile']; + Worker::$logFile = $config['logFile']; + Worker::$stdoutFile = $config['stdoutFile']; + Worker::$eventLoopClass = $config['eventLoop']; + TcpConnection::$defaultMaxPackageSize = $config['maxPackageSize']; - $psr15Handler = new SymfonyRequestHandler($kernel, $this->httpFoundationFactory, $this->httpMessageFactory); + if (property_exists(Worker::class, 'statusFile')) { + Worker::$statusFile = $config['status_file'] ?? ''; + } + if (property_exists(Worker::class, 'stopTimeout')) { + Worker::$stopTimeout = $config['stop_timeout'] ?? 2; + } - $onMessage = new OnMessage($this->psrRequestFactory, $this->workermanResponseEmitter, $psr15Handler); + $worker = new Worker($config['listen'], $config['context']); - $worker->onMessage = $onMessage; + $property_map = [ + 'name', + 'count', + 'user', + 'group', + 'reusePort', + 'transport', + 'protocol' + ]; + + foreach ($property_map as $property) { + if (isset($config[$property])) { + $worker->$property = $config[$property]; + } + } + + $worker->onWorkerStart = function (Worker $worker): void { + $worker->onMessage = [$this, 'onMessage']; }; + // Windows does not support custom processes. + if (\DIRECTORY_SEPARATOR === '/') { + // 获取涉及 workerman 进程的 service id + $processIds = $container->getParameter('workerman.processIds'); + + foreach ($processIds ?? [] as $id) { + + $handler = $container->get($id); + + $config = []; + + if ($handler instanceof ProcessInterface) { + $workerConfig = $handler->getWorkerConfig(); + $config = $workerConfig ? $workerConfig->toArray() : []; + } + + $config['handler'] = $handler; + + Utils::workerStart($config['name'] ?: $id, $config); + } + } + Worker::runAll(); return 0; } -} \ No newline at end of file + + /** + * @throws \Exception + */ + public function onMessage(TcpConnection $connection, ServerRequest $psrRequest) + { + $checkFile = "{$this->kernel->getProjectDir()}/public{$psrRequest->getUri()->getPath()}"; + $checkFile = str_replace('..', '/', $checkFile); + + if (is_file($checkFile)) { + $code = file_get_contents($checkFile); + $psrResponse = new Response(200, [ + 'Content-Type' => $this->detector->detectMimeType($checkFile, $code), + 'Last-Modified' => gmdate('D, d M Y H:i:s', filemtime($checkFile)) . ' GMT', + ], $code); + $connection->send(response_to_string($psrResponse), true); + return; + } + + $this->kernel->boot(); + + // 将PSR规范的请求,转换为Symfony请求进行处理,最终再转换成PSR响应进行返回 + $symfonyRequest = $this->httpFoundationFactory->createRequest($psrRequest); + $symfonyResponse = $this->kernel->handle($symfonyRequest); + $psrResponse = $this->psrHttpFactory->createResponse($symfonyResponse); + + // 注意,下面的意思是直接格式化整个HTTP报文,做得很彻底喔 + $connection->send(response_to_string($psrResponse), true); + + // 这里做最终的环境变量收集和处理 + $this->kernel->terminate($symfonyRequest, $symfonyResponse); + + // 设置单进程请求量达到额定时重启,防止代码写得不好产生OOM + static $maxRequest; + if (++$maxRequest > $this->maxRequest) { + // $output->writeln("max request {$maxRequest} reached and reload"); + // send SIGUSR1 signal to master process for reload + posix_kill(posix_getppid(), SIGUSR1); + } + } + + /** + * the master server config + * + * @param array $config + * @return array + */ + public function getMasterConfig(array $config): array + { + $options = $this->config->toArray(); + + foreach ($config as $key => $value) { + if (!empty($options[$key])) { + $config[$key] = $options[$key]; + } + } + + $config['name'] = $config['name'] ?: 'Symfony Workerman Server'; + $config['listen'] = $config['listen'] ?: 'http://0.0.0.0:8000'; + + if ($this->environment === 'prod') { + $config['count'] = $config['count'] ?: Utils::cpuCount() * 2; + } else { + $config['count'] = 1; + } + + $config['stopTimeout'] = $config['stopTimeout'] ?: 2; + $config['context'] = $config['context'] ?: []; + $config['pidFile'] = $this->pidFile ?: $config['pidFile']; + $config['logFile'] = $this->logFile ?: $config['logFile']; + $config['statusFile'] = $this->logFile ?: $config['statusFile']; + $config['stdoutFile'] = $this->logFile ?: $config['stdoutFile']; + $config['eventLoop'] = $config['eventLoop'] ?? ''; + $config['maxPackageSize'] = $config['maxPackageSize'] ?? 10 * 1024 * 1024; + + return $config; + } +} diff --git a/src/Runtime.php b/src/Runtime.php index 1a979fb..25d49b8 100644 --- a/src/Runtime.php +++ b/src/Runtime.php @@ -1,42 +1,58 @@ + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle; use ReflectionFunction; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Runtime\ResolverInterface; use Symfony\Component\Runtime\RunnerInterface; use Symfony\Component\Runtime\SymfonyRuntime; -include '../vendor/autoload.php'; - class Runtime extends SymfonyRuntime { - private int $workers; - private string $socket; private string $pidFile; private string $logFile; + private string $statusFile; + private string $stdoutFile; + private WorkerConfig $config; public function __construct(array $options = []) { - $this->socket = $options['socket'] ?? 'http://0.0.0.0:' . ($_SERVER['PORT'] ?? $_ENV['PORT'] ?? 8000); - $this->workers = isset($options['workers']) ? (int)$options['workers'] : 2; + $this->config = new WorkerConfig(); + + if (isset($options['listen']) && $options['listen']) { + $this->config->setListen($options['listen']); + } + + if (isset($options['count']) && intval($options['count'])) { + $this->config->setCount(intval($options['count'])); + } - $hash = md5(__FILE__); - $this->pidFile = $options['pid_file'] ?? "/tmp/workerman-$hash.pid"; - $this->logFile = $options['log_file'] ?? "/tmp/workerman-$hash.log"; + $this->pidFile = $options['pidFile'] ?? ""; + $this->logFile = $options['logFile'] ?? ""; + $this->statusFile = $options['statusFile'] ?? ""; + $this->stdoutFile = $options['stdoutFile'] ?? ""; parent::__construct($options); } public function getRunner(?object $application): RunnerInterface { - return new Runner($application, $this->socket, $this->workers, $this->pidFile, $this->logFile); - } - - public function getResolver(callable $callable, ?ReflectionFunction $reflector = null): ResolverInterface - { - $resolver = parent::getResolver($callable, $reflector); - - return new Resolver($resolver); + if ($application instanceof HttpKernelInterface) { + return new Runner($application, $this->config, $this->pidFile, $this->logFile, $this->statusFile, $this->stdoutFile); + } + return parent::getRunner($application); } -} \ No newline at end of file +} diff --git a/src/SymfonyRequestHandler.php b/src/SymfonyRequestHandler.php deleted file mode 100644 index f6ee89f..0000000 --- a/src/SymfonyRequestHandler.php +++ /dev/null @@ -1,35 +0,0 @@ -httpFoundationFactory->createRequest($request); - $sfResponse = $this->kernel->handle($sfRequest); - $response = $this->httpMessageFactory->createResponse($sfResponse); - - if ($this->kernel instanceof TerminableInterface) { - $this->kernel->terminate($sfRequest, $sfResponse); - } - - return $response; - } -} \ No newline at end of file diff --git a/src/Utils.php b/src/Utils.php new file mode 100644 index 0000000..9917e2b --- /dev/null +++ b/src/Utils.php @@ -0,0 +1,198 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle; + +use Phar; +use Workerman\Worker; + +class Utils +{ + /** + * Copy dir. + * + * @param string $source + * @param string $dest + * @param bool $overwrite + * @return void + */ + public static function copyDir(string $source, string $dest, bool $overwrite = false) + { + if (\is_dir($source)) { + if (!is_dir($dest)) { + \mkdir($dest); + } + $files = \scandir($source); + foreach ($files as $file) { + if ($file !== "." && $file !== "..") { + static::copyDir("$source/$file", "$dest/$file"); + } + } + } else if (\file_exists($source) && ($overwrite || !\file_exists($dest))) { + \copy($source, $dest); + } + } + + /** + * Remove dir. + * + * @param string $dir + * @return bool + */ + public static function removeDir(string $dir) + { + if (\is_link($dir) || \is_file($dir)) { + return \unlink($dir); + } + $files = \array_diff(\scandir($dir), array('.', '..')); + foreach ($files as $file) { + (\is_dir("$dir/$file") && !\is_link($dir)) ? static::removeDir("$dir/$file") : \unlink("$dir/$file"); + } + return \rmdir($dir); + } + + /** + * @param Worker $worker + * @param object $handler + */ + public static function workerBind(Worker $worker, object|string $handler): void + { + $callback_map = [ + 'onConnect', + 'onMessage', + 'onClose', + 'onError', + 'onBufferFull', + 'onBufferDrain', + 'onWorkerStop', + 'onWebSocketConnect' + ]; + foreach ($callback_map as $name) { + if (\method_exists($handler, $name)) { + $worker->$name = [$handler, $name]; + } + } + if (\method_exists($handler, 'onWorkerStart')) { + \call_user_func([$handler, 'onWorkerStart'], $worker); + } + } + + /** + * @param string $process_name + * @param array $config + * @return void + */ + public static function workerStart(string $process_name, array $config = []): void + { + $worker = new Worker($config['listen'] ?? null, $config['context'] ?? []); + + $property_map = [ + 'count', + 'user', + 'group', + 'reloadable', + 'reusePort', + 'transport', + 'protocol', + ]; + + $worker->name = $process_name; + + foreach ($property_map as $property) { + if (isset($config[$property])) { + $worker->$property = $config[$property]; + } + } + + $worker->onWorkerStart = function ($worker) use ($config) { + + echo "other worker onWorkerStart run\n"; + + foreach ($config['services'] ?? [] as $server) { + if (!\class_exists($server['handler'])) { + echo "process error: class {$server['handler']} not exists\r\n"; + continue; + } + $listen = new Worker($server['listen'] ?? null, $server['context'] ?? []); + if (isset($server['listen'])) { + echo "listen: {$server['listen']}\n"; + } + static::workerBind($listen, $server['handler']); + $listen->listen(); + } + + if (isset($config['handler'])) { + static::workerBind($worker, $config['handler']); + } + }; + } + + /** + * Phar support. + * Compatible with the 'realpath' function in the phar file. + * + * @param string $file_path + * @return string + */ + public static function getRealpath(string $file_path): string + { + if (str_starts_with($file_path, 'phar://')) { + return $file_path; + } else { + return \realpath($file_path); + } + } + + /** + * @return bool + */ + public static function isPhar() + { + return \class_exists(\Phar::class, false) && Phar::running(); + } + + /** + * @return int + */ + public static function cpuCount() + { + // Windows does not support the number of processes setting. + if (\DIRECTORY_SEPARATOR === '\\') { + return 1; + } + $count = 4; + if (\is_callable('shell_exec')) { + if (\strtolower(PHP_OS) === 'darwin') { + $count = (int)\shell_exec('sysctl -n machdep.cpu.core_count'); + } else { + $count = (int)\shell_exec('nproc'); + } + } + return $count > 0 ? $count : 4; + } + + /** + * @param string $name + * @param array $constructor + * @return mixed + * @throws Exception + */ + public static function make(string $name, array $constructor = []) + { + if (!\class_exists($name)) { + throw new \Exception("Class '$name' not found"); + } + return new $name(... array_values($constructor)); + } +} diff --git a/src/WorkerConfig.php b/src/WorkerConfig.php new file mode 100644 index 0000000..152cf9e --- /dev/null +++ b/src/WorkerConfig.php @@ -0,0 +1,238 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle; + +/** + * worker 配置 + */ +class WorkerConfig +{ + /** + * 进程名称 + * @var string + */ + private string $name = ''; + + /** + * 监听的协议 ip 及端口 (可选) + * + * @var string + */ + private string $listen = ''; + + /** + * 进程数 (可选,默认1) + * + * @var int + */ + private ?int $count = null; + + /** + * 进程运行用户 (可选,默认当前用户) + * + * @var string + */ + private string $user = ''; + + /** + * 进程运行用户组 (可选,默认当前用户组) + * + * @var string + */ + private string $group = ''; + + /** + * 当前进程是否支持reload (可选,默认true) + * + * @var bool + */ + private bool $reloadable = true; + + /** + * 是否开启reusePort (可选,此选项需要php>=7.0,默认为true) + * + * @var bool + */ + private bool $reusePort = true; + + /** + * transport (可选,当需要开启ssl时设置为ssl,默认为tcp) + * + * @var string + */ + private string $transport = 'tcp'; + + /** + * context (可选,当transport为是ssl时,需要传递证书路径) + * + * @var array + */ + private array $context = []; + + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return string + */ + public function getListen(): string + { + return $this->listen; + } + + /** + * @param string $listen + */ + public function setListen(string $listen): void + { + $this->listen = $listen; + } + + /** + * @return int + */ + public function getCount(): ?int + { + return $this->count; + } + + /** + * @param int $count + */ + public function setCount(?int $count): void + { + $this->count = $count; + } + + /** + * @return string + */ + public function getUser(): string + { + return $this->user; + } + + /** + * @param string $user + */ + public function setUser(string $user): void + { + $this->user = $user; + } + + /** + * @return string + */ + public function getGroup(): string + { + return $this->group; + } + + /** + * @param string $group + */ + public function setGroup(string $group): void + { + $this->group = $group; + } + + /** + * @return bool + */ + public function isReloadable(): bool + { + return $this->reloadable; + } + + /** + * @param bool $reloadable + */ + public function setReloadable(bool $reloadable): void + { + $this->reloadable = $reloadable; + } + + /** + * @return bool + */ + public function isReusePort(): bool + { + return $this->reusePort; + } + + /** + * @param bool $reusePort + */ + public function setReusePort(bool $reusePort): void + { + $this->reusePort = $reusePort; + } + + /** + * @return string + */ + public function getTransport(): string + { + return $this->transport; + } + + /** + * @param string $transport + */ + public function setTransport(string $transport): void + { + $this->transport = $transport; + } + + /** + * @return array + */ + public function getContext(): array + { + return $this->context; + } + + /** + * @param array $context + */ + public function setContext(array $context): void + { + $this->context = $context; + } + + /** + * 转换为数组 + * + * @return array + */ + public function toArray(): array + { + return get_object_vars($this); + } +} diff --git a/src/WorkermanBundle.php b/src/WorkermanBundle.php new file mode 100644 index 0000000..b60415b --- /dev/null +++ b/src/WorkermanBundle.php @@ -0,0 +1,37 @@ + + * @copyright niqingyang + * @link https://github.com/niqingyang/workerman-bundle + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace WellKit\WorkermanBundle; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use WellKit\WorkermanBundle\DependencyInjection\Compiler\ProcessPass; +use WellKit\WorkermanBundle\DependencyInjection\WorkermanExtension; + + +class WorkermanBundle extends Bundle +{ + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new ProcessPass()); + } + + public function getContainerExtension(): ?ExtensionInterface + { + return new WorkermanExtension(); + } +}