├── .gitignore ├── .php_cs ├── LICENSE ├── README-CN.md ├── README.md ├── composer.json └── src ├── Application.php ├── Listener.php ├── Route.php ├── Server ├── Http.php ├── MainServer.php ├── MqttServer.php ├── Protocol │ ├── HTTP │ │ ├── SimpleResponse.php │ │ └── SimpleRoute.php │ └── MqttInterface.php └── WebSocket.php └── Singleton.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | app 3 | config 4 | .idea 5 | composer.lock 6 | skeleton 7 | /nbproject/private/ -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true) 13 | ->setRules([ 14 | '@PSR2' => true, 15 | '@Symfony' => true, 16 | '@DoctrineAnnotation' => true, 17 | '@PhpCsFixer' => true, 18 | 'header_comment' => [ 19 | 'commentType' => 'PHPDoc', 20 | 'header' => $header, 21 | 'separate' => 'none', 22 | 'location' => 'after_declare_strict', 23 | ], 24 | 'array_syntax' => [ 25 | 'syntax' => 'short' 26 | ], 27 | 'list_syntax' => [ 28 | 'syntax' => 'short' 29 | ], 30 | 'concat_space' => [ 31 | 'spacing' => 'one' 32 | ], 33 | 'blank_line_before_statement' => [ 34 | 'statements' => [ 35 | 'declare', 36 | ], 37 | ], 38 | 'general_phpdoc_annotation_remove' => [ 39 | 'annotations' => [ 40 | 'author' 41 | ], 42 | ], 43 | 'ordered_imports' => [ 44 | 'imports_order' => [ 45 | 'class', 'function', 'const', 46 | ], 47 | 'sort_algorithm' => 'alpha', 48 | ], 49 | 'single_line_comment_style' => [ 50 | 'comment_types' => [ 51 | ], 52 | ], 53 | 'yoda_style' => [ 54 | 'always_move_variable' => false, 55 | 'equal' => false, 56 | 'identical' => false, 57 | ], 58 | 'phpdoc_align' => [ 59 | 'align' => 'left', 60 | ], 61 | 'multiline_whitespace_before_semicolons' => [ 62 | 'strategy' => 'no_multi_line', 63 | ], 64 | 'constant_case' => [ 65 | 'case' => 'lower', 66 | ], 67 | 'class_attributes_separation' => true, 68 | 'combine_consecutive_unsets' => true, 69 | 'declare_strict_types' => true, 70 | 'linebreak_after_opening_tag' => true, 71 | 'lowercase_static_reference' => true, 72 | 'no_useless_else' => true, 73 | 'no_unused_imports' => true, 74 | 'not_operator_with_successor_space' => true, 75 | 'not_operator_with_space' => false, 76 | 'ordered_class_elements' => true, 77 | 'php_unit_strict' => false, 78 | 'phpdoc_separation' => false, 79 | 'single_quote' => true, 80 | 'standardize_not_equals' => true, 81 | 'multiline_comment_opening_closing' => true, 82 | ]) 83 | ->setFinder( 84 | PhpCsFixer\Finder::create() 85 | ->exclude('public') 86 | ->exclude('runtime') 87 | ->exclude('vendor') 88 | ->in(__DIR__) 89 | ) 90 | ->setUsingCache(false); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 中文 2 | 3 |

4 | 5 | Simps 6 | 7 |

8 | 9 | # Simps 10 | 11 | [![Simps License](https://poser.pugx.org/simple-swoole/simps/license)](LICENSE) 12 | [![Latest Version](https://img.shields.io/packagist/v/simple-swoole/simps.svg)](https://packagist.org/packages/simple-swoole/simps) 13 | [![Contact Simps Team](https://img.shields.io/badge/contact-@Simps%20Team-blue.svg)](mailto:team@simps.io) 14 | [![PHP Version](https://img.shields.io/badge/php-%3E=7.1-brightgreen.svg)](https://www.php.net) 15 | [![Swoole Version](https://img.shields.io/badge/swoole-%3E=4.4.0-brightgreen.svg)](https://github.com/swoole/swoole-src) 16 | 17 | 🚀 Simps 是一个简单、轻量并且拥有超高性能的PHP协程框架。 18 | 19 | ## 性能测试 20 | 21 | 基准测试性能排名领先于Java、Go、Python等语言的Web框架。 22 | 23 | 具体可参考:[https://github.com/the-benchmarker/web-frameworks](https://github.com/the-benchmarker/web-frameworks/blob/8543e5dac6eb7f56308db278b4310beb80b0444d/README.md#results) 24 | 25 | ## 支持功能 26 | 27 | * [x] HTTP Server 28 | * [x] WebSocket Server 29 | * [x] TCP Server 30 | * [x] UDP Server 31 | * [x] MQTT Server/[Client](https://github.com/simps/mqtt) 32 | 33 | ## 文档 34 | 35 | [https://doc.simps.io](https://doc.simps.io) 36 | 37 | ## 如何贡献 38 | 39 | 非常欢迎您对 Simps 的开发作出贡献! 40 | 41 | 你可以选择以下方式向 Simps 贡献: 42 | 43 | * [发布issue进行问题反馈和建议](https://github.com/simple-swoole/simps/issues) 44 | * 通过Pull Request提交修复 45 | * 完善我们的文档和例子 46 | 47 | ## 贡献者 48 | 49 | 项目的发展离不开以下贡献者的努力! [[Contributors](https://github.com/simple-swoole/simps/graphs/contributors)]. 50 | 51 | 52 | 53 | ## 开源协议 54 | 55 | Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | English | [中文](./README-CN.md) 2 | 3 |

4 | 5 | Simps 6 | 7 |

8 | 9 | # Simps 10 | 11 | [![Simps License](https://poser.pugx.org/simple-swoole/simps/license)](LICENSE) 12 | [![Latest Version](https://img.shields.io/packagist/v/simple-swoole/simps.svg)](https://packagist.org/packages/simple-swoole/simps) 13 | [![Contact Simps Team](https://img.shields.io/badge/contact-@Simps%20Team-blue.svg)](mailto:team@simps.io) 14 | [![PHP Version](https://img.shields.io/badge/php-%3E=7.1-brightgreen.svg)](https://www.php.net) 15 | [![Swoole Version](https://img.shields.io/badge/swoole-%3E=4.4.0-brightgreen.svg)](https://github.com/swoole/swoole-src) 16 | [![Join Discord community and chat about Simps](https://img.shields.io/discord/740738911625674872.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2&cacheSeconds=60)](https://discord.gg/u4YAqeh) 17 | 18 | 🚀 A simple, lightweight and high-performance PHP coroutine framework. 19 | 20 | ## Benchmark 21 | 22 | Benchmark testing performance ranking ahead of web frameworks in Java, Go, Python and other languages. 23 | 24 | See [https://github.com/the-benchmarker/web-frameworks](https://github.com/the-benchmarker/web-frameworks/blob/8543e5dac6eb7f56308db278b4310beb80b0444d/README.md#results) 25 | 26 | ## Support Features 27 | 28 | * [x] HTTP Server 29 | * [x] WebSocket Server 30 | * [x] TCP Server 31 | * [x] UDP Server 32 | * [x] MQTT Server/[Client](https://github.com/simps/mqtt) 33 | 34 | ## Documentation 35 | 36 | [https://doc.simps.io](https://doc.simps.io) 37 | 38 | ## Contribution 39 | 40 | Your contribution to Simps development is very welcome! 41 | 42 | You may contribute in the following ways: 43 | 44 | * [Report issues and feedback](https://github.com/simple-swoole/simps/issues) 45 | * Submit fixes, features via Pull Request 46 | * Write/polish documentation 47 | 48 | ## Contributors 49 | 50 | This project exists thanks to all the people who contribute. [[Contributors](https://github.com/simple-swoole/simps/graphs/contributors)]. 51 | 52 | 53 | 54 | ## License 55 | 56 | Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html 57 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-swoole/simps", 3 | "description": "A simple, lightweight and high-performance PHP coroutine framework.", 4 | "type": "library", 5 | "license": "Apache-2.0", 6 | "keywords": [ 7 | "php", 8 | "swoole", 9 | "coroutine", 10 | "simple", 11 | "simps", 12 | "mqtt" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Lu Fei", 17 | "email": "lufei@simps.io" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=7.1", 22 | "ext-swoole": ">=4.4", 23 | "nikic/fast-route": "^1.3", 24 | "simple-swoole/utils": "^1.0", 25 | "simps/mqtt": "^1.1" 26 | }, 27 | "require-dev": { 28 | "friendsofphp/php-cs-fixer": "^2.9" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Simps\\": "src/" 33 | } 34 | }, 35 | "config": { 36 | "sort-packages": true, 37 | "optimize-autoloader": true 38 | }, 39 | "scripts": { 40 | "cs-fix": "php-cs-fixer fix $1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | get('listeners'); 30 | } 31 | return self::$instance; 32 | } 33 | 34 | public function listen($listener, ...$args) 35 | { 36 | $listeners = isset(self::$config[$listener]) ? self::$config[$listener] : []; 37 | while ($listeners) { 38 | [$class, $func] = array_shift($listeners); 39 | try { 40 | $class::getInstance()->{$func}(...$args); 41 | } catch (\Exception $e) { 42 | throw new \Exception($e->getMessage()); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Route.php: -------------------------------------------------------------------------------- 1 | get('routes', []); 36 | self::$dispatcher = simpleDispatcher( 37 | function (\FastRoute\RouteCollector $routerCollector) { 38 | foreach (self::$config as $routerDefine) { 39 | $routerCollector->addRoute($routerDefine[0], $routerDefine[1], $routerDefine[2]); 40 | } 41 | } 42 | ); 43 | } 44 | return self::$instance; 45 | } 46 | 47 | /** 48 | * @param $request 49 | * @param $response 50 | * @throws \Exception 51 | * @return mixed|void 52 | */ 53 | public function dispatch($request, $response) 54 | { 55 | $method = $request->server['request_method'] ?? 'GET'; 56 | $uri = $request->server['request_uri'] ?? '/'; 57 | $routeInfo = self::$dispatcher->dispatch($method, $uri); 58 | 59 | switch ($routeInfo[0]) { 60 | case Dispatcher::NOT_FOUND: 61 | return $this->defaultRouter($request, $response, $uri); 62 | case Dispatcher::METHOD_NOT_ALLOWED: 63 | $response->status(405); 64 | return $response->end(); 65 | case Dispatcher::FOUND: 66 | $handler = $routeInfo[1]; 67 | $vars = $routeInfo[2]; 68 | if (is_string($handler)) { 69 | $handler = explode('@', $handler); 70 | if (count($handler) != 2) { 71 | throw new RuntimeException("Route {$uri} config error, Only @ are supported"); 72 | } 73 | 74 | $className = $handler[0]; 75 | $func = $handler[1]; 76 | 77 | if (! class_exists($className)) { 78 | throw new RuntimeException("Route {$uri} defined '{$className}' Class Not Found"); 79 | } 80 | 81 | $controller = new $className(); 82 | 83 | if (! method_exists($controller, $func)) { 84 | throw new RuntimeException("Route {$uri} defined '{$func}' Method Not Found"); 85 | } 86 | 87 | $middlewareHandler = function ($request, $response, $vars) use ($controller, $func) { 88 | return $controller->{$func}($request, $response, $vars ?? null); 89 | }; 90 | $middleware = 'middleware'; 91 | if (property_exists($controller, $middleware)) { 92 | $classMiddlewares = $controller->{$middleware}['__construct'] ?? []; 93 | $methodMiddlewares = $controller->{$middleware}[$func] ?? []; 94 | $middlewares = array_merge($classMiddlewares, $methodMiddlewares); 95 | if ($middlewares) { 96 | $middlewareHandler = $this->packMiddleware($middlewareHandler, array_reverse($middlewares)); 97 | } 98 | } 99 | return $middlewareHandler($request, $response, $vars ?? null); 100 | } 101 | 102 | if (is_callable($handler)) { 103 | return call_user_func_array($handler, [$request, $response, $vars ?? null]); 104 | } 105 | 106 | throw new RuntimeException("Route {$uri} config error"); 107 | default: 108 | $response->status(400); 109 | return $response->end(); 110 | } 111 | } 112 | 113 | /** 114 | * @param $request 115 | * @param $response 116 | * @param $uri 117 | */ 118 | public function defaultRouter($request, $response, $uri) 119 | { 120 | $uri = trim($uri, '/'); 121 | $uri = explode('/', $uri); 122 | 123 | if ($uri[0] === '') { 124 | $className = '\\App\\Controller\\IndexController'; 125 | if (class_exists($className) && method_exists($className, 'index')) { 126 | return (new $className())->index($request, $response); 127 | } 128 | } 129 | $response->status(404); 130 | return $response->end(); 131 | } 132 | 133 | /** 134 | * @param $handler 135 | * @param array $middlewares 136 | * @return mixed 137 | */ 138 | public function packMiddleware($handler, $middlewares = []) 139 | { 140 | foreach ($middlewares as $middleware) { 141 | $handler = $middleware($handler); 142 | } 143 | return $handler; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Server/Http.php: -------------------------------------------------------------------------------- 1 | _config = $httpConfig; 35 | if (isset($httpConfig['settings']['only_simple_http'])) { 36 | $this->_server = new HttpServer($httpConfig['ip'], $httpConfig['port'], $config['mode']); 37 | $this->_server->on('workerStart', [$this, 'onSimpleWorkerStart']); 38 | $this->_server->on('receive', [$this, 'onReceive']); 39 | unset($httpConfig['settings']['only_simple_http']); 40 | } else { 41 | $this->_server = new Server( 42 | $httpConfig['ip'], 43 | $httpConfig['port'], 44 | $config['mode'], 45 | $httpConfig['sock_type'] 46 | ); 47 | $this->_server->on('workerStart', [$this, 'onWorkerStart']); 48 | $this->_server->on('request', [$this, 'onRequest']); 49 | } 50 | $this->_server->set($httpConfig['settings']); 51 | 52 | if ($config['mode'] == SWOOLE_BASE) { 53 | $this->_server->on('managerStart', [$this, 'onManagerStart']); 54 | } else { 55 | $this->_server->on('start', [$this, 'onStart']); 56 | } 57 | 58 | foreach ($httpConfig['callbacks'] as $eventKey => $callbackItem) { 59 | [$class, $func] = $callbackItem; 60 | $this->_server->on($eventKey, [$class, $func]); 61 | } 62 | 63 | if (isset($this->_config['process']) && ! empty($this->_config['process'])) { 64 | foreach ($this->_config['process'] as $processItem) { 65 | [$class, $func] = $processItem; 66 | $this->_server->addProcess($class::$func($this->_server)); 67 | } 68 | } 69 | 70 | $this->_server->start(); 71 | } 72 | 73 | public function onStart(HttpServer $server) 74 | { 75 | Application::echoSuccess("Swoole Http Server running:http://{$this->_config['ip']}:{$this->_config['port']}"); 76 | Listener::getInstance()->listen('start', $server); 77 | } 78 | 79 | public function onManagerStart(HttpServer $server) 80 | { 81 | Application::echoSuccess("Swoole Http Server running:http://{$this->_config['ip']}:{$this->_config['port']}"); 82 | Listener::getInstance()->listen('managerStart', $server); 83 | } 84 | 85 | public function onWorkerStart(HttpServer $server, int $workerId) 86 | { 87 | $this->_route = Route::getInstance(); 88 | Listener::getInstance()->listen('workerStart', $server, $workerId); 89 | } 90 | 91 | public function onSimpleWorkerStart(HttpServer $server, int $workerId) 92 | { 93 | $this->_route = SimpleRoute::getInstance(); 94 | Listener::getInstance()->listen('simpleWorkerStart', $server, $workerId); 95 | } 96 | 97 | public function onRequest(\Swoole\Http\Request $request, \Swoole\Http\Response $response) 98 | { 99 | Context::set('SwRequest', $request); 100 | Context::set('SwResponse', $response); 101 | $this->_route->dispatch($request, $response); 102 | } 103 | 104 | public function onReceive($server, $fd, $from_id, $data) 105 | { 106 | $this->_route->dispatch($server, $fd, $data); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Server/MainServer.php: -------------------------------------------------------------------------------- 1 | _config = $config['main']; 23 | $this->_server = new $this->_config['class_name']( 24 | $this->_config['ip'], 25 | $this->_config['port'], 26 | $this->_config['mode'] ?? SWOOLE_PROCESS, 27 | $this->_config['sock_type'] ?? SWOOLE_SOCK_TCP 28 | ); 29 | $this->_server->set($this->_config['settings']); 30 | 31 | foreach ($this->_config['callbacks'] as $eventKey => $callbackItem) { 32 | [$class, $func] = $callbackItem; 33 | $this->_server->on($eventKey, [$class, $func]); 34 | } 35 | 36 | if (isset($this->_config['process']) && ! empty($this->_config['process'])) { 37 | foreach ($this->_config['process'] as $processItem) { 38 | [$class, $func] = $processItem; 39 | $this->_server->addProcess($class::$func($this->_server)); 40 | } 41 | } 42 | 43 | if (isset($this->_config['sub']) && ! empty($this->_config['sub'])) { 44 | foreach ($this->_config['sub'] as $item) { 45 | $sub_server = $this->_server->addListener($item['ip'], $item['port'], $item['sock_type'] ?? SWOOLE_SOCK_TCP); 46 | if (isset($item['settings'])) { 47 | $sub_server->set($item['settings']); 48 | } 49 | foreach ($item['callbacks'] as $eventKey => $callbackItem) { 50 | [$class, $func] = $callbackItem; 51 | $sub_server->on($eventKey, [$class, $func]); 52 | } 53 | } 54 | } 55 | 56 | $this->_server->start(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Server/MqttServer.php: -------------------------------------------------------------------------------- 1 | _config = $mqttConfig; 33 | $this->_server = new Server($mqttConfig['ip'], $mqttConfig['port'], $config['mode'], $mqttConfig['sock_type'] ?? SWOOLE_SOCK_TCP); 34 | $this->_server->set($mqttConfig['settings']); 35 | 36 | $this->_server->on('Start', [$this, 'onStart']); 37 | $this->_server->on('workerStart', [$this, 'onWorkerStart']); 38 | $this->_server->on('Receive', [$this, 'onReceive']); 39 | foreach ($mqttConfig['callbacks'] as $eventKey => $callbackItem) { 40 | [$class, $func] = $callbackItem; 41 | $this->_server->on($eventKey, [$class, $func]); 42 | } 43 | $this->_server->start(); 44 | } 45 | 46 | public function onStart($server) 47 | { 48 | Application::echoSuccess("Swoole MQTT Server running:mqtt://{$this->_config['ip']}:{$this->_config['port']}"); 49 | Listener::getInstance()->listen('start', $server); 50 | } 51 | 52 | public function onWorkerStart(Server $server, int $workerId) 53 | { 54 | Listener::getInstance()->listen('workerStart', $server, $workerId); 55 | } 56 | 57 | public function onReceive($server, $fd, $fromId, $data) 58 | { 59 | try { 60 | $data = Protocol::unpack($data); 61 | if (is_array($data) && isset($data['type'])) { 62 | switch ($data['type']) { 63 | case Types::PINGREQ: // 心跳请求 64 | [$class, $func] = $this->_config['receiveCallbacks'][Types::PINGREQ]; 65 | $obj = new $class(); 66 | if ($obj->{$func}($server, $fd, $fromId, $data)) { 67 | // 返回心跳响应 68 | $server->send($fd, Protocol::pack(['type' => Types::PINGRESP])); 69 | } 70 | break; 71 | case Types::DISCONNECT: // 客户端断开连接 72 | [$class, $func] = $this->_config['receiveCallbacks'][Types::DISCONNECT]; 73 | $obj = new $class(); 74 | if ($obj->{$func}($server, $fd, $fromId, $data)) { 75 | if ($server->exist($fd)) { 76 | $server->close($fd); 77 | } 78 | } 79 | break; 80 | case Types::CONNECT: // 连接 81 | case Types::PUBLISH: // 发布消息 82 | case Types::SUBSCRIBE: // 订阅 83 | case Types::UNSUBSCRIBE: // 取消订阅 84 | [$class, $func] = $this->_config['receiveCallbacks'][$data['type']]; 85 | $obj = new $class(); 86 | $obj->{$func}($server, $fd, $fromId, $data); 87 | break; 88 | } 89 | } else { 90 | $server->close($fd); 91 | } 92 | } catch (\Throwable $e) { 93 | $server->close($fd); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Server/Protocol/HTTP/SimpleResponse.php: -------------------------------------------------------------------------------- 1 | 'Continue', 25 | 101 => 'Switching Protocols', 26 | 102 => 'Processing', 27 | 200 => 'OK', 28 | 201 => 'Created', 29 | 202 => 'Accepted', 30 | 203 => 'Non-Authoritative Information', 31 | 204 => 'No Content', 32 | 205 => 'Reset Content', 33 | 206 => 'Partial Content', 34 | 207 => 'Multi-status', 35 | 208 => 'Already Reported', 36 | 300 => 'Multiple Choices', 37 | 301 => 'Moved Permanently', 38 | 302 => 'Found', 39 | 303 => 'See Other', 40 | 304 => 'Not Modified', 41 | 305 => 'Use Proxy', 42 | 306 => 'Switch Proxy', 43 | 307 => 'Temporary Redirect', 44 | 400 => 'Bad Request', 45 | 401 => 'Unauthorized', 46 | 402 => 'Payment Required', 47 | 403 => 'Forbidden', 48 | 404 => 'Not Found', 49 | 405 => 'Method Not Allowed', 50 | 406 => 'Not Acceptable', 51 | 407 => 'Proxy Authentication Required', 52 | 408 => 'Request Time-out', 53 | 409 => 'Conflict', 54 | 410 => 'Gone', 55 | 411 => 'Length Required', 56 | 412 => 'Precondition Failed', 57 | 413 => 'Request Entity Too Large', 58 | 414 => 'Request-URI Too Large', 59 | 415 => 'Unsupported Media Type', 60 | 416 => 'Requested range not satisfiable', 61 | 417 => 'Expectation Failed', 62 | 418 => 'I\'m a teapot', 63 | 422 => 'Unprocessable Entity', 64 | 423 => 'Locked', 65 | 424 => 'Failed Dependency', 66 | 425 => 'Unordered Collection', 67 | 426 => 'Upgrade Required', 68 | 428 => 'Precondition Required', 69 | 429 => 'Too Many Requests', 70 | 431 => 'Request Header Fields Too Large', 71 | 451 => 'Unavailable For Legal Reasons', 72 | 500 => 'Internal Server Error', 73 | 501 => 'Not Implemented', 74 | 502 => 'Bad Gateway', 75 | 503 => 'Service Unavailable', 76 | 504 => 'Gateway Time-out', 77 | 505 => 'HTTP Version not supported', 78 | 506 => 'Variant Also Negotiates', 79 | 507 => 'Insufficient Storage', 80 | 508 => 'Loop Detected', 81 | 511 => 'Network Authentication Required', 82 | ]; 83 | 84 | /** 85 | * @param string $body 86 | * @param int $status 87 | * @return string 88 | */ 89 | public static function build($body = '', $status = 200, array $headers = []) 90 | { 91 | $reason = static::$_phrases[$status]; 92 | $body_len = \strlen($body); 93 | $version = static::$_version; 94 | if (empty($headers)) { 95 | return "HTTP/{$version} {$status} {$reason}\r\nServer: simps-http-server\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: {$body_len}\r\nConnection: keep-alive\r\n\r\n{$body}"; 96 | } 97 | 98 | $head = "HTTP/{$version} {$status} {$reason}\r\n"; 99 | $headers = $headers; 100 | if (! isset($headers['Server'])) { 101 | $head .= "Server: simps-http-server\r\n"; 102 | } 103 | foreach ($headers as $name => $value) { 104 | if (\is_array($value)) { 105 | foreach ($value as $item) { 106 | $head .= "{$name}: {$item}\r\n"; 107 | } 108 | continue; 109 | } 110 | $head .= "{$name}: {$value}\r\n"; 111 | } 112 | 113 | if (! isset($headers['Connection'])) { 114 | $head .= "Connection: keep-alive\r\n"; 115 | } 116 | 117 | if (! isset($headers['Content-Type'])) { 118 | $head .= "Content-Type: text/html;charset=utf-8\r\n"; 119 | } else { 120 | if ($headers['Content-Type'] === 'text/event-stream') { 121 | return $head . $body; 122 | } 123 | } 124 | 125 | if (! isset($headers['Transfer-Encoding'])) { 126 | $head .= "Content-Length: {$body_len}\r\n\r\n"; 127 | } else { 128 | return "{$head}\r\n" . dechex($body_len) . "\r\n{$body}\r\n"; 129 | } 130 | 131 | return $head . $body; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Server/Protocol/HTTP/SimpleRoute.php: -------------------------------------------------------------------------------- 1 | get('routes', []); 38 | self::$dispatcher = simpleDispatcher( 39 | function (\FastRoute\RouteCollector $routerCollector) { 40 | foreach (self::$config as $routerDefine) { 41 | $routerCollector->addRoute($routerDefine[0], $routerDefine[1], $routerDefine[2]); 42 | } 43 | } 44 | ); 45 | } 46 | return self::$instance; 47 | } 48 | 49 | /** 50 | * @param $server 51 | * @param $fd 52 | * @param $data 53 | * @throws \Exception 54 | * @return mixed 55 | */ 56 | public function dispatch($server, $fd, $data) 57 | { 58 | $first_line = \strstr($data, "\r\n", true); 59 | $tmp = \explode(' ', $first_line, 3); 60 | $method = $tmp[0] ?? 'GET'; 61 | $uri = $tmp[1] ?? '/'; 62 | $routeInfo = self::$dispatcher->dispatch($method, $uri); 63 | 64 | switch ($routeInfo[0]) { 65 | // Dispatcher::FOUND 66 | case 1: 67 | $handler = $routeInfo[1]; 68 | $vars = $routeInfo[2]; 69 | 70 | if (isset(self::$cache[$handler])) { 71 | $cache_entity = self::$cache[$handler]; 72 | return $cache_entity[0]->{$cache_entity[1]}($server, $fd, $vars ?? null); 73 | } 74 | 75 | if (is_string($handler)) { 76 | $handlerArr = explode('@', $handler); 77 | if (count($handlerArr) != 2) { 78 | throw new RuntimeException("Route {$uri} config error, Only @ are supported"); 79 | } 80 | 81 | $className = $handlerArr[0]; 82 | $func = $handlerArr[1]; 83 | 84 | if (! class_exists($className)) { 85 | throw new RuntimeException("Route {$uri} defined '{$className}' Class Not Found"); 86 | } 87 | 88 | $controller = new $className(); 89 | 90 | if (! method_exists($controller, $func)) { 91 | throw new RuntimeException("Route {$uri} defined '{$func}' Method Not Found"); 92 | } 93 | 94 | self::$cache[$handler] = [$controller, $func]; 95 | return $controller->{$func}($server, $fd, $vars ?? null); 96 | } 97 | if (is_callable($handler)) { 98 | return call_user_func_array($handler, [$server, $fd, $vars ?? null]); 99 | } 100 | 101 | throw new RuntimeException("Route {$uri} config error"); 102 | break; 103 | case Dispatcher::NOT_FOUND: 104 | return $this->defaultRouter($server, $fd, $uri); 105 | break; 106 | case Dispatcher::METHOD_NOT_ALLOWED: 107 | return $server->send($fd, SimpleResponse::build('', 405)); 108 | // throw new RuntimeException('Request Method Not Allowed', 405); 109 | break; 110 | default: 111 | return $server->send($fd, SimpleResponse::build('', 400)); 112 | } 113 | throw new RuntimeException("Undefined Route {$uri}"); 114 | } 115 | 116 | /** 117 | * @param $server 118 | * @param $fd 119 | * @param $uri 120 | * @throws \Exception 121 | * @return mixed 122 | */ 123 | public function defaultRouter($server, $fd, $uri) 124 | { 125 | $uri = trim($uri, '/'); 126 | $uri = explode('/', $uri); 127 | 128 | if ($uri[0] === '') { 129 | $className = '\\App\\Controller\\IndexController'; 130 | if (class_exists($className) && method_exists($className, 'index')) { 131 | return (new $className())->index($server, $fd); 132 | } 133 | // throw new RuntimeException('The default route index/index class does not exist', 404); 134 | } 135 | return $server->send($fd, SimpleResponse::build('', 404)); 136 | // throw new RuntimeException('Route Not Found', 404); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Server/Protocol/MqttInterface.php: -------------------------------------------------------------------------------- 1 | _config = $wsConfig; 32 | $this->_server = new Server($wsConfig['ip'], $wsConfig['port'], $config['mode'], $wsConfig['sock_type'] ?? SWOOLE_SOCK_TCP); 33 | $this->_server->set($wsConfig['settings']); 34 | 35 | if ($config['mode'] == SWOOLE_BASE) { 36 | $this->_server->on('managerStart', [$this, 'onManagerStart']); 37 | } else { 38 | $this->_server->on('start', [$this, 'onStart']); 39 | } 40 | 41 | $this->_server->on('workerStart', [$this, 'onWorkerStart']); 42 | 43 | foreach ($wsConfig['callbacks'] as $eventKey => $callbackItem) { 44 | [$class, $func] = $callbackItem; 45 | $this->_server->on($eventKey, [$class, $func]); 46 | } 47 | 48 | if (isset($this->_config['process']) && ! empty($this->_config['process'])) { 49 | foreach ($this->_config['process'] as $processItem) { 50 | [$class, $func] = $processItem; 51 | $this->_server->addProcess($class::$func($this->_server)); 52 | } 53 | } 54 | 55 | $this->_server->start(); 56 | } 57 | 58 | public function onStart(\Swoole\Server $server) 59 | { 60 | Application::echoSuccess("Swoole WebSocket Server running:ws://{$this->_config['ip']}:{$this->_config['port']}"); 61 | Listener::getInstance()->listen('start', $server); 62 | } 63 | 64 | public function onManagerStart(\Swoole\Server $server) 65 | { 66 | Application::echoSuccess("Swoole WebSocket Server running:ws://{$this->_config['ip']}:{$this->_config['port']}"); 67 | Listener::getInstance()->listen('managerStart', $server); 68 | } 69 | 70 | public function onWorkerStart(\Swoole\Server $server, int $workerId) 71 | { 72 | $this->_route = Route::getInstance(); 73 | Listener::getInstance()->listen('workerStart', $server, $workerId); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Singleton.php: -------------------------------------------------------------------------------- 1 |