├── README.md ├── composer.json └── src ├── Adapter ├── BlackfireOnMessageAdapter.php └── NewRelicOnMessageAdapter.php ├── OnMessage.php ├── OnMessageInterface.php ├── PsrRequestFactory.php ├── PsrRequestFactoryInterface.php ├── WorkermanResponseEmitter.php └── WorkermanResponseEmitterInterface.php /README.md: -------------------------------------------------------------------------------- 1 | # chubbyphp-workerman-request-handler 2 | 3 | [![CI](https://github.com/chubbyphp/chubbyphp-workerman-request-handler/actions/workflows/ci.yml/badge.svg)](https://github.com/chubbyphp/chubbyphp-workerman-request-handler/actions/workflows/ci.yml) 4 | [![Coverage Status](https://coveralls.io/repos/github/chubbyphp/chubbyphp-workerman-request-handler/badge.svg?branch=master)](https://coveralls.io/github/chubbyphp/chubbyphp-workerman-request-handler?branch=master) 5 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fchubbyphp%2Fchubbyphp-workerman-request-handler%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/chubbyphp/chubbyphp-workerman-request-handler/master) 6 | [![Latest Stable Version](https://poser.pugx.org/chubbyphp/chubbyphp-workerman-request-handler/v)](https://packagist.org/packages/chubbyphp/chubbyphp-workerman-request-handler) 7 | [![Total Downloads](https://poser.pugx.org/chubbyphp/chubbyphp-workerman-request-handler/downloads)](https://packagist.org/packages/chubbyphp/chubbyphp-workerman-request-handler) 8 | [![Monthly Downloads](https://poser.pugx.org/chubbyphp/chubbyphp-workerman-request-handler/d/monthly)](https://packagist.org/packages/chubbyphp/chubbyphp-workerman-request-handler) 9 | 10 | [![bugs](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=bugs)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 11 | [![code_smells](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=code_smells)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 12 | [![coverage](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=coverage)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 13 | [![duplicated_lines_density](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 14 | [![ncloc](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=ncloc)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 15 | [![sqale_rating](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 16 | [![alert_status](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=alert_status)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 17 | [![reliability_rating](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 18 | [![security_rating](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=security_rating)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 19 | [![sqale_index](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=sqale_index)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 20 | [![vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=chubbyphp_chubbyphp-workerman-request-handler&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=chubbyphp_chubbyphp-workerman-request-handler) 21 | 22 | ## Description 23 | 24 | A request handler adapter for workerman, using PSR-7, PSR-15 and PSR-17. 25 | 26 | ## Requirements 27 | 28 | * php: ^8.2 29 | * [psr/http-factory][2]: ^1.1 30 | * [psr/http-message][3]: ^1.1|^2.0 31 | * [psr/http-server-handler][4]: ^1.0.2 32 | * [psr/log][5]: ^2.0|^3.0.2 33 | * [workerman/workerman][6]: ^4.2.1 34 | 35 | ## Installation 36 | 37 | Through [Composer](http://getcomposer.org) as [chubbyphp/chubbyphp-workerman-request-handler][1]. 38 | 39 | ```sh 40 | composer require chubbyphp/chubbyphp-workerman-request-handler "^2.2" 41 | ``` 42 | 43 | ## Usage 44 | 45 | ```php 46 | count = 4; 69 | 70 | $http->onWorkerStart = function () { 71 | echo 'Workerman http server is started at http://0.0.0.0:8080'.PHP_EOL; 72 | }; 73 | 74 | $http->onMessage = new OnMessage( 75 | new PsrRequestFactory( 76 | new ServerRequestFactory(), 77 | new StreamFactory(), 78 | new UploadedFileFactory() 79 | ), 80 | new WorkermanResponseEmitter(), 81 | $app 82 | ); 83 | 84 | Worker::runAll(); 85 | ``` 86 | 87 | ### with blackfire 88 | 89 | ```php 90 | onMessage = $onMessage; 108 | ``` 109 | 110 | ### with newrelic 111 | 112 | ```php 113 | onMessage = $onMessage; 130 | ``` 131 | 132 | ## Copyright 133 | 134 | 2025 Dominik Zogg 135 | 136 | [1]: https://packagist.org/packages/chubbyphp/chubbyphp-workerman-request-handler 137 | [2]: https://packagist.org/packages/psr/http-factory 138 | [3]: https://packagist.org/packages/psr/http-message 139 | [4]: https://packagist.org/packages/psr/http-server-handler 140 | [5]: https://packagist.org/packages/psr/log 141 | [6]: https://packagist.org/packages/workerman/workerman 142 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chubbyphp/chubbyphp-workerman-request-handler", 3 | "description": "A request handler adapter for workerman, using PSR-7, PSR-15 and PSR-17.", 4 | "keywords": [ 5 | "chubbyphp", 6 | "requesthandler", 7 | "workerman", 8 | "psr-7", 9 | "psr-15", 10 | "psr-17" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Dominik Zogg", 16 | "email": "dominik.zogg@gmail.com" 17 | } 18 | ], 19 | "require": { 20 | "php": "^8.2", 21 | "psr/http-factory": "^1.1", 22 | "psr/http-message": "^1.1|^2.0", 23 | "psr/http-server-handler": "^1.0.2", 24 | "psr/log": "^2.0|^3.0.2", 25 | "workerman/workerman": "^4.2.1" 26 | }, 27 | "require-dev": { 28 | "blackfire/php-sdk": "^2.5.7", 29 | "chubbyphp/chubbyphp-dev-helper": "dev-master", 30 | "chubbyphp/chubbyphp-mock": "^2.0", 31 | "infection/infection": "^0.29.12", 32 | "php-coveralls/php-coveralls": "^2.7", 33 | "phpstan/extension-installer": "^1.4.3", 34 | "phpstan/phpstan": "^2.1.6", 35 | "phpunit/phpunit": "^11.5.9" 36 | }, 37 | "autoload": { 38 | "psr-4": { "Chubbyphp\\WorkermanRequestHandler\\": "src/" } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { "Chubbyphp\\Tests\\WorkermanRequestHandler\\": "tests/" } 42 | }, 43 | "config": { 44 | "sort-packages": true, 45 | "allow-plugins": { 46 | "infection/extension-installer": true, 47 | "phpstan/extension-installer": true 48 | } 49 | }, 50 | "extra": { 51 | "branch-alias": { 52 | "dev-master": "2.2-dev" 53 | } 54 | }, 55 | "scripts": { 56 | "fix:cs": "mkdir -p build && PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --cache-file=build/phpcs.cache", 57 | "test": [ 58 | "@test:lint", 59 | "@test:unit", 60 | "@test:integration", 61 | "@test:infection", 62 | "@test:static-analysis", 63 | "@test:cs" 64 | ], 65 | "test:cs": "mkdir -p build && PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --dry-run --stop-on-violation --cache-file=build/phpcs.cache", 66 | "test:infection": "vendor/bin/infection --threads=$(nproc) --min-msi=100 --verbose --coverage=build/phpunit", 67 | "test:integration": "vendor/bin/phpunit --testsuite=Integration --cache-directory=build/phpunit", 68 | "test:lint": "mkdir -p build && find src tests -name '*.php' -print0 | xargs -0 -n1 -P$(nproc) php -l | tee build/phplint.log", 69 | "test:static-analysis": "mkdir -p build && bash -c 'vendor/bin/phpstan analyse src --no-progress --level=7 --error-format=junit | tee build/phpstan.junit.xml; if [ ${PIPESTATUS[0]} -ne \"0\" ]; then exit 1; fi'", 70 | "test:unit": "vendor/bin/phpunit --testsuite=Unit --coverage-text --coverage-clover=build/phpunit/clover.xml --coverage-html=build/phpunit/coverage-html --coverage-xml=build/phpunit/coverage-xml --log-junit=build/phpunit/junit.xml --cache-directory=build/phpunit" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Adapter/BlackfireOnMessageAdapter.php: -------------------------------------------------------------------------------- 1 | config = $config ?? new Configuration(); 30 | $this->logger = $logger ?? new NullLogger(); 31 | } 32 | 33 | public function __invoke(WorkermanTcpConnection $workermanTcpConnection, WorkermanRequest $workermanRequest): void 34 | { 35 | if (null === $workermanRequest->header('x-blackfire-query')) { 36 | $this->onRequest->__invoke($workermanTcpConnection, $workermanRequest); 37 | 38 | return; 39 | } 40 | 41 | $probe = $this->startProbe(); 42 | 43 | $this->onRequest->__invoke($workermanTcpConnection, $workermanRequest); 44 | 45 | if (!$probe instanceof Probe) { 46 | return; 47 | } 48 | 49 | $this->endProbe($probe); 50 | } 51 | 52 | private function startProbe(): ?Probe 53 | { 54 | try { 55 | return $this->client->createProbe($this->config); 56 | } catch (ExceptionInterface $exception) { 57 | $this->logger->error(\sprintf('Blackfire exception: %s', $exception->getMessage())); 58 | } 59 | 60 | return null; 61 | } 62 | 63 | private function endProbe(Probe $probe): void 64 | { 65 | try { 66 | $this->client->endProbe($probe); 67 | } catch (ExceptionInterface $exception) { 68 | $this->logger->error(\sprintf('Blackfire exception: %s', $exception->getMessage())); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Adapter/NewRelicOnMessageAdapter.php: -------------------------------------------------------------------------------- 1 | appname); 18 | 19 | $this->onRequest->__invoke($workermanTcpConnection, $workermanRequest); 20 | 21 | newrelic_end_transaction(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/OnMessage.php: -------------------------------------------------------------------------------- 1 | workermanResponseEmitter->emit( 22 | $this->requestHander->handle($this->psrRequestFactory->create($workermanTcpConnection, $workermanRequest)), 23 | $workermanTcpConnection 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/OnMessageInterface.php: -------------------------------------------------------------------------------- 1 | serverRequestFactory->createServerRequest( 26 | $workermanRequest->method(), 27 | $workermanRequest->uri(), 28 | $this->createServerParams($workermanTcpConnection), 29 | ); 30 | 31 | /** @var array $headers */ 32 | $headers = $workermanRequest->header(); 33 | 34 | foreach ($headers as $name => $value) { 35 | $request = $request->withHeader($name, $value); 36 | } 37 | 38 | /** @var array $cookies */ 39 | $cookies = $workermanRequest->cookie(); 40 | 41 | $request = $request->withCookieParams($cookies); 42 | $request = $request->withQueryParams($workermanRequest->get()); 43 | $request = $request->withParsedBody($workermanRequest->post()); 44 | $request = $request->withUploadedFiles($this->uploadedFiles($workermanRequest->file())); 45 | 46 | $request->getBody()->write($workermanRequest->rawBody()); 47 | 48 | return $request; 49 | } 50 | 51 | /** 52 | * @return array 53 | */ 54 | private function createServerParams(WorkermanTcpConnection $workermanTcpConnection): array 55 | { 56 | return [ 57 | 'REMOTE_ADDR' => $workermanTcpConnection->getRemoteIp(), 58 | 'REMOTE_PORT' => (string) $workermanTcpConnection->getRemotePort(), 59 | ]; 60 | } 61 | 62 | /** 63 | * @param array> $files 64 | * 65 | * @return array 66 | */ 67 | private function uploadedFiles(array $files): array 68 | { 69 | $uploadedFiles = []; 70 | foreach ($files as $key => $file) { 71 | $uploadedFiles[$key] = isset($file['tmp_name']) ? $this->createUploadedFile($file) : $this->uploadedFiles($file); 72 | } 73 | 74 | return $uploadedFiles; 75 | } 76 | 77 | /** 78 | * @param array $file 79 | */ 80 | private function createUploadedFile(array $file): UploadedFileInterface 81 | { 82 | try { 83 | $stream = $this->streamFactory->createStreamFromFile($file['tmp_name']); 84 | } catch (\RuntimeException) { 85 | $stream = $this->streamFactory->createStream(); 86 | } 87 | 88 | return $this->uploadedFileFactory->createUploadedFile( 89 | $stream, 90 | $file['size'], 91 | $file['error'], 92 | $file['name'], 93 | $file['type'] 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/PsrRequestFactoryInterface.php: -------------------------------------------------------------------------------- 1 | send( 16 | (new WorkermanResponse()) 17 | ->withStatus($response->getStatusCode(), $response->getReasonPhrase()) 18 | ->withHeaders($response->getHeaders()) 19 | ->withBody((string) $response->getBody()) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/WorkermanResponseEmitterInterface.php: -------------------------------------------------------------------------------- 1 |