├── .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 |
6 |
7 |
8 |
9 | # Simps
10 |
11 | [](LICENSE)
12 | [](https://packagist.org/packages/simple-swoole/simps)
13 | [](mailto:team@simps.io)
14 | [](https://www.php.net)
15 | [](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 |
6 |
7 |
8 |
9 | # Simps
10 |
11 | [](LICENSE)
12 | [](https://packagist.org/packages/simple-swoole/simps)
13 | [](mailto:team@simps.io)
14 | [](https://www.php.net)
15 | [](https://github.com/swoole/swoole-src)
16 | [](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 |