fix:更新已知bug,优化代码

This commit is contained in:
Ying
2022-11-28 19:11:12 +08:00
parent f6aee95cfc
commit 9445b206a2
1378 changed files with 53759 additions and 20789 deletions

View File

@@ -0,0 +1,4 @@
# These are supported funding model platforms
open_collective: walkor
patreon: walkor

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2009-2015 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/workerman/contributors)
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.

View File

@@ -0,0 +1,38 @@
GatewayWorker
=================
GatewayWorker基于[Workerman](https://github.com/walkor/Workerman)开发的一个项目框架用于快速开发长连接应用例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等。
GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接并转发客户端的数据给Worker进程处理Worker进程负责处理实际的业务逻辑并将结果推送给对应的客户端。Gateway服务和Worker服务可以分开部署在不同的服务器上实现分布式集群。
GatewayWorker提供非常方便的API可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器也可以定时推送数据。
快速开始
======
开发者可以从一个简单的demo开始(demo中包含了GatewayWorker内核以及start_gateway.php start_business.php等启动入口文件)<br>
[点击这里下载demo](http://www.workerman.net/download/GatewayWorker.zip)。<br>
demo说明见源码readme。
手册
=======
http://www.workerman.net/gatewaydoc/
安装内核
=======
只安装GatewayWorker内核文件不包含start_gateway.php start_businessworker.php等启动入口文件
```
composer require workerman/gateway-worker
```
使用GatewayWorker开发的项目
=======
## [tadpole](http://kedou.workerman.net/)
[Live demo](http://kedou.workerman.net/)
[Source code](https://github.com/walkor/workerman)
![workerman todpole](http://www.workerman.net/img/workerman-todpole.png)
## [chat room](http://chat.workerman.net/)
[Live demo](http://chat.workerman.net/)
[Source code](https://github.com/walkor/workerman-chat)
![workerman-chat](http://www.workerman.net/img/workerman-chat.png)

View File

@@ -0,0 +1,12 @@
{
"name" : "workerman/gateway-worker",
"keywords": ["distributed","communication"],
"homepage": "http://www.workerman.net",
"license" : "MIT",
"require": {
"workerman/workerman" : "^4.0.30"
},
"autoload": {
"psr-4": {"GatewayWorker\\": "./src"}
}
}

View File

@@ -0,0 +1,565 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace GatewayWorker;
use Workerman\Connection\TcpConnection;
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\AsyncTcpConnection;
use GatewayWorker\Protocols\GatewayProtocol;
use GatewayWorker\Lib\Context;
/**
*
* BusinessWorker 用于处理Gateway转发来的数据
*
* @author walkor<walkor@workerman.net>
*
*/
class BusinessWorker extends Worker
{
/**
* 保存与 gateway 的连接 connection 对象
*
* @var array
*/
public $gatewayConnections = array();
/**
* 注册中心地址
*
* @var string|array
*/
public $registerAddress = '127.0.0.1:1236';
/**
* 事件处理类,默认是 Event 类
*
* @var string
*/
public $eventHandler = 'Events';
/**
* 业务超时时间,可用来定位程序卡在哪里
*
* @var int
*/
public $processTimeout = 30;
/**
* 业务超时时间,可用来定位程序卡在哪里
*
* @var callable
*/
public $processTimeoutHandler = '\\Workerman\\Worker::log';
/**
* 秘钥
*
* @var string
*/
public $secretKey = '';
/**
* businessWorker进程将消息转发给gateway进程的发送缓冲区大小
*
* @var int
*/
public $sendToGatewayBufferSize = 10240000;
/**
* 保存用户设置的 worker 启动回调
*
* @var callback
*/
protected $_onWorkerStart = null;
/**
* 保存用户设置的 workerReload 回调
*
* @var callback
*/
protected $_onWorkerReload = null;
/**
* 保存用户设置的 workerStop 回调
*
* @var callback
*/
protected $_onWorkerStop= null;
/**
* 到注册中心的连接
*
* @var AsyncTcpConnection
*/
protected $_registerConnection = null;
/**
* 处于连接状态的 gateway 通讯地址
*
* @var array
*/
protected $_connectingGatewayAddresses = array();
/**
* 所有 geteway 内部通讯地址
*
* @var array
*/
protected $_gatewayAddresses = array();
/**
* 等待连接个 gateway 地址
*
* @var array
*/
protected $_waitingConnectGatewayAddresses = array();
/**
* Event::onConnect 回调
*
* @var callback
*/
protected $_eventOnConnect = null;
/**
* Event::onMessage 回调
*
* @var callback
*/
protected $_eventOnMessage = null;
/**
* Event::onClose 回调
*
* @var callback
*/
protected $_eventOnClose = null;
/**
* websocket回调
*
* @var null
*/
protected $_eventOnWebSocketConnect = null;
/**
* SESSION 版本缓存
*
* @var array
*/
protected $_sessionVersion = array();
/**
* 用于保持长连接的心跳时间间隔
*
* @var int
*/
const PERSISTENCE_CONNECTION_PING_INTERVAL = 25;
/**
* 构造函数
*
* @param string $socket_name
* @param array $context_option
*/
public function __construct($socket_name = '', $context_option = array())
{
parent::__construct($socket_name, $context_option);
$backrace = debug_backtrace();
$this->_autoloadRootPath = dirname($backrace[0]['file']);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->_onWorkerStart = $this->onWorkerStart;
$this->_onWorkerReload = $this->onWorkerReload;
$this->_onWorkerStop = $this->onWorkerStop;
$this->onWorkerStop = array($this, 'onWorkerStop');
$this->onWorkerStart = array($this, 'onWorkerStart');
$this->onWorkerReload = array($this, 'onWorkerReload');
parent::run();
}
/**
* 当进程启动时一些初始化工作
*
* @return void
*/
protected function onWorkerStart()
{
if (function_exists('opcache_reset')) {
opcache_reset();
}
if (!class_exists('\Protocols\GatewayProtocol')) {
class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol');
}
if (!is_array($this->registerAddress)) {
$this->registerAddress = array($this->registerAddress);
}
$this->connectToRegister();
\GatewayWorker\Lib\Gateway::setBusinessWorker($this);
\GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey;
if ($this->_onWorkerStart) {
call_user_func($this->_onWorkerStart, $this);
}
if (is_callable($this->eventHandler . '::onWorkerStart')) {
call_user_func($this->eventHandler . '::onWorkerStart', $this);
}
if (function_exists('pcntl_signal')) {
// 业务超时信号处理
pcntl_signal(SIGALRM, array($this, 'timeoutHandler'), false);
} else {
$this->processTimeout = 0;
}
// 设置回调
if (is_callable($this->eventHandler . '::onConnect')) {
$this->_eventOnConnect = $this->eventHandler . '::onConnect';
}
if (is_callable($this->eventHandler . '::onMessage')) {
$this->_eventOnMessage = $this->eventHandler . '::onMessage';
} else {
echo "Waring: {$this->eventHandler}::onMessage is not callable\n";
}
if (is_callable($this->eventHandler . '::onClose')) {
$this->_eventOnClose = $this->eventHandler . '::onClose';
}
if (is_callable($this->eventHandler . '::onWebSocketConnect')) {
$this->_eventOnWebSocketConnect = $this->eventHandler . '::onWebSocketConnect';
}
}
/**
* onWorkerReload 回调
*
* @param Worker $worker
*/
protected function onWorkerReload($worker)
{
// 防止进程立刻退出
$worker->reloadable = false;
// 延迟 0.05 秒退出,避免 BusinessWorker 瞬间全部退出导致没有可用的 BusinessWorker 进程
Timer::add(0.05, array('Workerman\Worker', 'stopAll'));
// 执行用户定义的 onWorkerReload 回调
if ($this->_onWorkerReload) {
call_user_func($this->_onWorkerReload, $this);
}
}
/**
* 当进程关闭时一些清理工作
*
* @return void
*/
protected function onWorkerStop()
{
if ($this->_onWorkerStop) {
call_user_func($this->_onWorkerStop, $this);
}
if (is_callable($this->eventHandler . '::onWorkerStop')) {
call_user_func($this->eventHandler . '::onWorkerStop', $this);
}
}
/**
* 连接服务注册中心
*
* @return void
*/
public function connectToRegister()
{
foreach ($this->registerAddress as $register_address) {
$register_connection = new AsyncTcpConnection("text://{$register_address}");
$secret_key = $this->secretKey;
$register_connection->onConnect = function () use ($register_connection, $secret_key, $register_address) {
$register_connection->send('{"event":"worker_connect","secret_key":"' . $secret_key . '"}');
// 如果Register服务器不在本地服务器则需要保持心跳
if (strpos($register_address, '127.0.0.1') !== 0) {
$register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) {
$register_connection->send('{"event":"ping"}');
});
}
};
$register_connection->onClose = function ($register_connection) {
if(!empty($register_connection->ping_timer)) {
Timer::del($register_connection->ping_timer);
}
$register_connection->reconnect(1);
};
$register_connection->onMessage = array($this, 'onRegisterConnectionMessage');
$register_connection->connect();
}
}
/**
* 当注册中心发来消息时
*
* @return void
*/
public function onRegisterConnectionMessage($register_connection, $data)
{
$data = json_decode($data, true);
if (!isset($data['event'])) {
echo "Received bad data from Register\n";
return;
}
$event = $data['event'];
switch ($event) {
case 'broadcast_addresses':
if (!is_array($data['addresses'])) {
echo "Received bad data from Register. Addresses empty\n";
return;
}
$addresses = $data['addresses'];
$this->_gatewayAddresses = array();
foreach ($addresses as $addr) {
$this->_gatewayAddresses[$addr] = $addr;
}
$this->checkGatewayConnections($addresses);
break;
default:
echo "Receive bad event:$event from Register.\n";
}
}
/**
* 当 gateway 转发来数据时
*
* @param TcpConnection $connection
* @param mixed $data
*/
public function onGatewayMessage($connection, $data)
{
$cmd = $data['cmd'];
if ($cmd === GatewayProtocol::CMD_PING) {
return;
}
// 上下文数据
Context::$client_ip = $data['client_ip'];
Context::$client_port = $data['client_port'];
Context::$local_ip = $data['local_ip'];
Context::$local_port = $data['local_port'];
Context::$connection_id = $data['connection_id'];
Context::$client_id = Context::addressToClientId($data['local_ip'], $data['local_port'],
$data['connection_id']);
// $_SERVER 变量
$_SERVER = array(
'REMOTE_ADDR' => long2ip($data['client_ip']),
'REMOTE_PORT' => $data['client_port'],
'GATEWAY_ADDR' => long2ip($data['local_ip']),
'GATEWAY_PORT' => $data['gateway_port'],
'GATEWAY_CLIENT_ID' => Context::$client_id,
);
// 检查session版本如果是过期的session数据则拉取最新的数据
if ($cmd !== GatewayProtocol::CMD_ON_CLOSE && isset($this->_sessionVersion[Context::$client_id]) && $this->_sessionVersion[Context::$client_id] !== crc32($data['ext_data'])) {
$_SESSION = Context::$old_session = \GatewayWorker\Lib\Gateway::getSession(Context::$client_id);
$this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']);
} else {
if (!isset($this->_sessionVersion[Context::$client_id])) {
$this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']);
}
// 尝试解析 session
if ($data['ext_data'] != '') {
Context::$old_session = $_SESSION = Context::sessionDecode($data['ext_data']);
} else {
Context::$old_session = $_SESSION = null;
}
}
if ($this->processTimeout) {
pcntl_alarm($this->processTimeout);
}
// 尝试执行 Event::onConnection、Event::onMessage、Event::onClose
switch ($cmd) {
case GatewayProtocol::CMD_ON_CONNECT:
if ($this->_eventOnConnect) {
call_user_func($this->_eventOnConnect, Context::$client_id);
}
break;
case GatewayProtocol::CMD_ON_MESSAGE:
if ($this->_eventOnMessage) {
call_user_func($this->_eventOnMessage, Context::$client_id, $data['body']);
}
break;
case GatewayProtocol::CMD_ON_CLOSE:
unset($this->_sessionVersion[Context::$client_id]);
if ($this->_eventOnClose) {
call_user_func($this->_eventOnClose, Context::$client_id);
}
break;
case GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT:
if ($this->_eventOnWebSocketConnect) {
call_user_func($this->_eventOnWebSocketConnect, Context::$client_id, $data['body']);
}
break;
}
if ($this->processTimeout) {
pcntl_alarm(0);
}
// session 必须是数组
if ($_SESSION !== null && !is_array($_SESSION)) {
throw new \Exception('$_SESSION must be an array. But $_SESSION=' . var_export($_SESSION, true) . ' is not array.');
}
// 判断 session 是否被更改
if ($_SESSION !== Context::$old_session && $cmd !== GatewayProtocol::CMD_ON_CLOSE) {
$session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : '';
\GatewayWorker\Lib\Gateway::setSocketSession(Context::$client_id, $session_str_now);
$this->_sessionVersion[Context::$client_id] = crc32($session_str_now);
}
Context::clear();
}
/**
* 当与 Gateway 的连接断开时触发
*
* @param TcpConnection $connection
* @return void
*/
public function onGatewayClose($connection)
{
$addr = $connection->remoteAddress;
unset($this->gatewayConnections[$addr], $this->_connectingGatewayAddresses[$addr]);
if (isset($this->_gatewayAddresses[$addr]) && !isset($this->_waitingConnectGatewayAddresses[$addr])) {
Timer::add(1, array($this, 'tryToConnectGateway'), array($addr), false);
$this->_waitingConnectGatewayAddresses[$addr] = $addr;
}
}
/**
* 尝试连接 Gateway 内部通讯地址
*
* @param string $addr
*/
public function tryToConnectGateway($addr)
{
if (!isset($this->gatewayConnections[$addr]) && !isset($this->_connectingGatewayAddresses[$addr]) && isset($this->_gatewayAddresses[$addr])) {
$gateway_connection = new AsyncTcpConnection("GatewayProtocol://$addr");
$gateway_connection->remoteAddress = $addr;
$gateway_connection->onConnect = array($this, 'onConnectGateway');
$gateway_connection->onMessage = array($this, 'onGatewayMessage');
$gateway_connection->onClose = array($this, 'onGatewayClose');
$gateway_connection->onError = array($this, 'onGatewayError');
$gateway_connection->maxSendBufferSize = $this->sendToGatewayBufferSize;
if (TcpConnection::$defaultMaxSendBufferSize == $gateway_connection->maxSendBufferSize) {
$gateway_connection->maxSendBufferSize = 50 * 1024 * 1024;
}
$gateway_data = GatewayProtocol::$empty;
$gateway_data['cmd'] = GatewayProtocol::CMD_WORKER_CONNECT;
$gateway_data['body'] = json_encode(array(
'worker_key' =>"{$this->name}:{$this->id}",
'secret_key' => $this->secretKey,
));
$gateway_connection->send($gateway_data);
$gateway_connection->connect();
$this->_connectingGatewayAddresses[$addr] = $addr;
}
unset($this->_waitingConnectGatewayAddresses[$addr]);
}
/**
* 检查 gateway 的通信端口是否都已经连
* 如果有未连接的端口,则尝试连接
*
* @param array $addresses_list
*/
public function checkGatewayConnections($addresses_list)
{
if (empty($addresses_list)) {
return;
}
foreach ($addresses_list as $addr) {
if (!isset($this->_waitingConnectGatewayAddresses[$addr])) {
$this->tryToConnectGateway($addr);
}
}
}
/**
* 当连接上 gateway 的通讯端口时触发
* 将连接 connection 对象保存起来
*
* @param TcpConnection $connection
* @return void
*/
public function onConnectGateway($connection)
{
$this->gatewayConnections[$connection->remoteAddress] = $connection;
unset($this->_connectingGatewayAddresses[$connection->remoteAddress], $this->_waitingConnectGatewayAddresses[$connection->remoteAddress]);
}
/**
* 当与 gateway 的连接出现错误时触发
*
* @param TcpConnection $connection
* @param int $error_no
* @param string $error_msg
*/
public function onGatewayError($connection, $error_no, $error_msg)
{
echo "GatewayConnection Error : $error_no ,$error_msg\n";
}
/**
* 获取所有 Gateway 内部通讯地址
*
* @return array
*/
public function getAllGatewayAddresses()
{
return $this->_gatewayAddresses;
}
/**
* 业务超时回调
*
* @param int $signal
* @throws \Exception
*/
public function timeoutHandler($signal)
{
switch ($signal) {
// 超时时钟
case SIGALRM:
// 超时异常
$e = new \Exception("process_timeout", 506);
$trace_str = $e->getTraceAsString();
// 去掉第一行timeoutHandler的调用栈
$trace_str = $e->getMessage() . ":\n" . substr($trace_str, strpos($trace_str, "\n") + 1) . "\n";
// 开发者没有设置超时处理函数,或者超时处理函数返回空则执行退出
if (!$this->processTimeoutHandler || !call_user_func($this->processTimeoutHandler, $trace_str, $e)) {
Worker::stopAll();
}
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace GatewayWorker\Lib;
use Exception;
/**
* 上下文 包含当前用户 uid 内部通信 local_ip local_port socket_id以及客户端 client_ip client_port
*/
class Context
{
/**
* 内部通讯 id
*
* @var string
*/
public static $local_ip;
/**
* 内部通讯端口
*
* @var int
*/
public static $local_port;
/**
* 客户端 ip
*
* @var string
*/
public static $client_ip;
/**
* 客户端端口
*
* @var int
*/
public static $client_port;
/**
* client_id
*
* @var string
*/
public static $client_id;
/**
* 连接 connection->id
*
* @var int
*/
public static $connection_id;
/**
* 旧的session
*
* @var string
*/
public static $old_session;
/**
* 编码 session
*
* @param mixed $session_data
* @return string
*/
public static function sessionEncode($session_data = '')
{
if ($session_data !== '') {
return serialize($session_data);
}
return '';
}
/**
* 解码 session
*
* @param string $session_buffer
* @return mixed
*/
public static function sessionDecode($session_buffer)
{
return unserialize($session_buffer);
}
/**
* 清除上下文
*
* @return void
*/
public static function clear()
{
self::$local_ip = self::$local_port = self::$client_ip = self::$client_port =
self::$client_id = self::$connection_id = self::$old_session = null;
}
/**
* 通讯地址到 client_id 的转换
*
* @param int $local_ip
* @param int $local_port
* @param int $connection_id
* @return string
*/
public static function addressToClientId($local_ip, $local_port, $connection_id)
{
return bin2hex(pack('NnN', $local_ip, $local_port, $connection_id));
}
/**
* client_id 到通讯地址的转换
*
* @param string $client_id
* @return array
* @throws Exception
*/
public static function clientIdToAddress($client_id)
{
if (strlen($client_id) !== 20) {
echo new Exception("client_id $client_id is invalid");
return false;
}
return unpack('Nlocal_ip/nlocal_port/Nconnection_id', pack('H*', $client_id));
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace GatewayWorker\Lib;
use Config\Db as DbConfig;
use Exception;
/**
* 数据库类
*/
class Db
{
/**
* 实例数组
*
* @var array
*/
protected static $instance = array();
/**
* 获取实例
*
* @param string $config_name
* @return DbConnection
* @throws Exception
*/
public static function instance($config_name)
{
if (!isset(DbConfig::$$config_name)) {
echo "\\Config\\Db::$config_name not set\n";
throw new Exception("\\Config\\Db::$config_name not set\n");
}
if (empty(self::$instance[$config_name])) {
$config = DbConfig::$$config_name;
self::$instance[$config_name] = new DbConnection($config['host'], $config['port'],
$config['user'], $config['password'], $config['dbname'],$config['charset']);
}
return self::$instance[$config_name];
}
/**
* 关闭数据库实例
*
* @param string $config_name
*/
public static function close($config_name)
{
if (isset(self::$instance[$config_name])) {
self::$instance[$config_name]->closeConnection();
self::$instance[$config_name] = null;
}
}
/**
* 关闭所有数据库实例
*/
public static function closeAll()
{
foreach (self::$instance as $connection) {
$connection->closeConnection();
}
self::$instance = array();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,216 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace GatewayWorker\Protocols;
/**
* Gateway 与 Worker 间通讯的二进制协议
*
* struct GatewayProtocol
* {
* unsigned int pack_len,
* unsigned char cmd,//命令字
* unsigned int local_ip,
* unsigned short local_port,
* unsigned int client_ip,
* unsigned short client_port,
* unsigned int connection_id,
* unsigned char flag,
* unsigned short gateway_port,
* unsigned int ext_len,
* char[ext_len] ext_data,
* char[pack_length-HEAD_LEN] body//包体
* }
* NCNnNnNCnN
*/
class GatewayProtocol
{
// 发给workergateway有一个新的连接
const CMD_ON_CONNECT = 1;
// 发给worker的客户端有消息
const CMD_ON_MESSAGE = 3;
// 发给worker上的关闭链接事件
const CMD_ON_CLOSE = 4;
// 发给gateway的向单个用户发送数据
const CMD_SEND_TO_ONE = 5;
// 发给gateway的向所有用户发送数据
const CMD_SEND_TO_ALL = 6;
// 发给gateway的踢出用户
// 1、如果有待发消息将在发送完后立即销毁用户连接
// 2、如果无待发消息将立即销毁用户连接
const CMD_KICK = 7;
// 发给gateway的立即销毁用户连接
const CMD_DESTROY = 8;
// 发给gateway通知用户session更新
const CMD_UPDATE_SESSION = 9;
// 获取在线状态
const CMD_GET_ALL_CLIENT_SESSIONS = 10;
// 判断是否在线
const CMD_IS_ONLINE = 11;
// client_id绑定到uid
const CMD_BIND_UID = 12;
// 解绑
const CMD_UNBIND_UID = 13;
// 向uid发送数据
const CMD_SEND_TO_UID = 14;
// 根据uid获取绑定的clientid
const CMD_GET_CLIENT_ID_BY_UID = 15;
// 加入组
const CMD_JOIN_GROUP = 20;
// 离开组
const CMD_LEAVE_GROUP = 21;
// 向组成员发消息
const CMD_SEND_TO_GROUP = 22;
// 获取组成员
const CMD_GET_CLIENT_SESSIONS_BY_GROUP = 23;
// 获取组在线连接数
const CMD_GET_CLIENT_COUNT_BY_GROUP = 24;
// 按照条件查找
const CMD_SELECT = 25;
// 获取在线的群组ID
const CMD_GET_GROUP_ID_LIST = 26;
// 取消分组
const CMD_UNGROUP = 27;
// worker连接gateway事件
const CMD_WORKER_CONNECT = 200;
// 心跳
const CMD_PING = 201;
// GatewayClient连接gateway事件
const CMD_GATEWAY_CLIENT_CONNECT = 202;
// 根据client_id获取session
const CMD_GET_SESSION_BY_CLIENT_ID = 203;
// 发给gateway覆盖session
const CMD_SET_SESSION = 204;
// 当websocket握手时触发只有websocket协议支持此命令字
const CMD_ON_WEBSOCKET_CONNECT = 205;
// 包体是标量
const FLAG_BODY_IS_SCALAR = 0x01;
// 通知gateway在send时不调用协议encode方法在广播组播时提升性能
const FLAG_NOT_CALL_ENCODE = 0x02;
/**
* 包头长度
*
* @var int
*/
const HEAD_LEN = 28;
public static $empty = array(
'cmd' => 0,
'local_ip' => 0,
'local_port' => 0,
'client_ip' => 0,
'client_port' => 0,
'connection_id' => 0,
'flag' => 0,
'gateway_port' => 0,
'ext_data' => '',
'body' => '',
);
/**
* 返回包长度
*
* @param string $buffer
* @return int return current package length
*/
public static function input($buffer)
{
if (strlen($buffer) < self::HEAD_LEN) {
return 0;
}
$data = unpack("Npack_len", $buffer);
return $data['pack_len'];
}
/**
* 获取整个包的 buffer
*
* @param mixed $data
* @return string
*/
public static function encode($data)
{
$flag = (int)is_scalar($data['body']);
if (!$flag) {
$data['body'] = serialize($data['body']);
}
$data['flag'] |= $flag;
$ext_len = strlen($data['ext_data']);
$package_len = self::HEAD_LEN + $ext_len + strlen($data['body']);
return pack("NCNnNnNCnN", $package_len,
$data['cmd'], $data['local_ip'],
$data['local_port'], $data['client_ip'],
$data['client_port'], $data['connection_id'],
$data['flag'], $data['gateway_port'],
$ext_len) . $data['ext_data'] . $data['body'];
}
/**
* 从二进制数据转换为数组
*
* @param string $buffer
* @return array
*/
public static function decode($buffer)
{
$data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nconnection_id/Cflag/ngateway_port/Next_len",
$buffer);
if ($data['ext_len'] > 0) {
$data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']);
if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) {
$data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']);
} else {
$data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len']));
}
} else {
$data['ext_data'] = '';
if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) {
$data['body'] = substr($buffer, self::HEAD_LEN);
} else {
$data['body'] = unserialize(substr($buffer, self::HEAD_LEN));
}
}
return $data;
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace GatewayWorker;
use Workerman\Worker;
use Workerman\Timer;
/**
*
* 注册中心,用于注册 Gateway 和 BusinessWorker
*
* @author walkor<walkor@workerman.net>
*
*/
class Register extends Worker
{
/**
* {@inheritdoc}
*/
public $name = 'Register';
/**
* {@inheritdoc}
*/
public $reloadable = false;
/**
* 秘钥
* @var string
*/
public $secretKey = '';
/**
* 所有 gateway 的连接
*
* @var array
*/
protected $_gatewayConnections = array();
/**
* 所有 worker 的连接
*
* @var array
*/
protected $_workerConnections = array();
/**
* 进程启动时间
*
* @var int
*/
protected $_startTime = 0;
/**
* {@inheritdoc}
*/
public function run()
{
// 设置 onMessage 连接回调
$this->onConnect = array($this, 'onConnect');
// 设置 onMessage 回调
$this->onMessage = array($this, 'onMessage');
// 设置 onClose 回调
$this->onClose = array($this, 'onClose');
// 记录进程启动的时间
$this->_startTime = time();
// 强制使用text协议
$this->protocol = '\Workerman\Protocols\Text';
// reusePort
$this->reusePort = false;
// 运行父方法
parent::run();
}
/**
* 设置个定时器,将未及时发送验证的连接关闭
*
* @param \Workerman\Connection\ConnectionInterface $connection
* @return void
*/
public function onConnect($connection)
{
$connection->timeout_timerid = Timer::add(10, function () use ($connection) {
Worker::log("Register auth timeout (".$connection->getRemoteIp()."). See http://doc2.workerman.net/register-auth-timeout.html");
$connection->close();
}, null, false);
}
/**
* 设置消息回调
*
* @param \Workerman\Connection\ConnectionInterface $connection
* @param string $buffer
* @return void
*/
public function onMessage($connection, $buffer)
{
// 删除定时器
Timer::del($connection->timeout_timerid);
$data = @json_decode($buffer, true);
if (empty($data['event'])) {
$error = "Bad request for Register service. Request info(IP:".$connection->getRemoteIp().", Request Buffer:$buffer). See http://doc2.workerman.net/register-auth-timeout.html";
Worker::log($error);
return $connection->close($error);
}
$event = $data['event'];
$secret_key = isset($data['secret_key']) ? $data['secret_key'] : '';
// 开始验证
switch ($event) {
// 是 gateway 连接
case 'gateway_connect':
if (empty($data['address'])) {
echo "address not found\n";
return $connection->close();
}
if ($secret_key !== $this->secretKey) {
Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true));
return $connection->close();
}
$this->_gatewayConnections[$connection->id] = $data['address'];
$this->broadcastAddresses();
break;
// 是 worker 连接
case 'worker_connect':
if ($secret_key !== $this->secretKey) {
Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true));
return $connection->close();
}
$this->_workerConnections[$connection->id] = $connection;
$this->broadcastAddresses($connection);
break;
case 'ping':
break;
default:
Worker::log("Register unknown event:$event IP: ".$connection->getRemoteIp()." Buffer:$buffer. See http://doc2.workerman.net/register-auth-timeout.html");
$connection->close();
}
}
/**
* 连接关闭时
*
* @param \Workerman\Connection\ConnectionInterface $connection
*/
public function onClose($connection)
{
if (isset($this->_gatewayConnections[$connection->id])) {
unset($this->_gatewayConnections[$connection->id]);
$this->broadcastAddresses();
}
if (isset($this->_workerConnections[$connection->id])) {
unset($this->_workerConnections[$connection->id]);
}
}
/**
* 向 BusinessWorker 广播 gateway 内部通讯地址
*
* @param \Workerman\Connection\ConnectionInterface $connection
*/
public function broadcastAddresses($connection = null)
{
$data = array(
'event' => 'broadcast_addresses',
'addresses' => array_unique(array_values($this->_gatewayConnections)),
);
$buffer = json_encode($data);
if ($connection) {
$connection->send($buffer);
return;
}
foreach ($this->_workerConnections as $con) {
$con->send($buffer);
}
}
}

View File

@@ -6,7 +6,8 @@
"description": "Message queue system written in PHP based on workerman and backed by Redis.",
"require": {
"php": ">=5.4",
"workerman/redis" : "^1.0"
"workerman/redis" : "^1.0",
"workerman/workerman" : "^4.0.20"
},
"autoload": {
"psr-4": {"Workerman\\RedisQueue\\": "./src"}

View File

@@ -5,22 +5,22 @@
"high performance",
"http service"
],
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"license": "MIT",
"description": "High performance HTTP Service Framework.",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"homepage": "https://www.workerman.net",
"role": "Developer"
}
],
"support": {
"email": "walkor@workerman.net",
"issues": "https://github.com/walkor/webman/issues",
"forum": "http://wenda.workerman.net/",
"wiki": "http://doc.workerman.net/",
"forum": "https://wenda.workerman.net/",
"wiki": "https://doc.workerman.net/",
"source": "https://github.com/walkor/webman-framework"
},
"require": {

View File

@@ -1,4 +1,5 @@
<?php
/**
* This file is part of webman.
*
@@ -18,6 +19,9 @@ use Closure;
use FastRoute\Dispatcher;
use Monolog\Logger;
use Psr\Container\ContainerInterface;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use Throwable;
use Webman\Exception\ExceptionHandler;
use Webman\Exception\ExceptionHandlerInterface;
@@ -118,7 +122,8 @@ class App
return static::send($connection, $callback($request), $request);
}
if (static::unsafeUri($connection, $path, $request) ||
if (
static::unsafeUri($connection, $path, $request) ||
static::findFile($connection, $path, $key, $request) ||
static::findRoute($connection, $path, $key, $request)
) {
@@ -126,13 +131,13 @@ class App
}
$controller_and_action = static::parseControllerAction($path);
if (!$controller_and_action || Route::hasDisableDefaultRoute()) {
$plugin = $controller_and_action['plugin'] ?? '';
if (!$controller_and_action || Route::hasDisableDefaultRoute($plugin)) {
$callback = static::getFallback();
$request->app = $request->controller = $request->action = '';
static::send($connection, $callback($request), $request);
return null;
}
$plugin = $controller_and_action['plugin'];
$app = $controller_and_action['app'];
$controller = $controller_and_action['controller'];
$action = $controller_and_action['action'];
@@ -177,12 +182,14 @@ class App
*/
protected static function unsafeUri(TcpConnection $connection, string $path, $request)
{
if (\strpos($path, '..') !== false ||
if (
!$path ||
\strpos($path, '..') !== false ||
\strpos($path, "\\") !== false ||
\strpos($path, "\0") !== false ||
\strpos($path, '//') !== false || !$path) {
\strpos($path, "\0") !== false
) {
$callback = static::getFallback();
$request->app = $request->controller = $request->action = '';
$request->plugin = $request->app = $request->controller = $request->action = '';
static::send($connection, $callback($request), $request);
return true;
}
@@ -196,7 +203,12 @@ class App
{
// when route, controller and action not found, try to use Route::fallback
return Route::getFallback() ?: function () {
return new Response(404, [], \file_get_contents(static::$_publicPath . '/404.html'));
try {
$notFoundContent = \file_get_contents(static::$_publicPath . '/404.html');
} catch (Throwable $e) {
$notFoundContent = '404 Not Found';
}
return new Response(404, [], $notFoundContent);
};
}
@@ -233,7 +245,7 @@ class App
/**
* @param $app
* @param $call
* @param null $args
* @param array|null $args
* @param bool $with_global_middleware
* @param RouteObject $route
* @return callable
@@ -253,18 +265,34 @@ class App
foreach ($middlewares as $key => $item) {
$middlewares[$key][0] = static::container($plugin)->get($item[0]);
}
$controller_reuse = static::config($plugin, 'app.controller_reuse', true);
$need_inject = static::isNeedInject($call, $args);
if (\is_array($call) && \is_string($call[0])) {
$controller_reuse = static::config($plugin, 'app.controller_reuse', true);
if (!$controller_reuse) {
$call = function ($request, ...$args) use ($call, $plugin) {
$call[0] = static::container($plugin)->make($call[0]);
return $call($request, ...$args);
};
if ($need_inject) {
$call = function ($request, ...$args) use ($call, $plugin) {
$call[0] = static::container($plugin)->make($call[0]);
$reflector = static::getReflector($call);
$args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
return $call(...$args);
};
$need_inject = false;
} else {
$call = function ($request, ...$args) use ($call, $plugin) {
$call[0] = static::container($plugin)->make($call[0]);
return $call($request, ...$args);
};
}
} else {
$call[0] = static::container($plugin)->get($call[0]);
}
}
if ($need_inject) {
$call = static::resolveInject($plugin, $call);
}
if ($middlewares) {
$callback = \array_reduce($middlewares, function ($carry, $pipe) {
return function ($request) use ($carry, $pipe) {
@@ -300,6 +328,129 @@ class App
return $callback;
}
/**
* @param string $plugin
* @param array|Closure $call
* @param null|array $args
* @return Closure
* @see Dependency injection through reflection information
*/
protected static function resolveInject(string $plugin, $call)
{
return function (Request $request, ...$args) use ($plugin, $call) {
$reflector = static::getReflector($call);
$args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
return $call(...$args);
};
}
/**
* Check whether inject is required
*
* @param $call
* @param $args
* @return bool
* @throws \ReflectionException
*/
protected static function isNeedInject($call, $args)
{
$args = $args ?: [];
$reflector = static::getReflector($call);
$reflection_parameters = $reflector->getParameters();
if (!$reflection_parameters) {
return false;
}
$first_parameter = \current($reflection_parameters);
unset($reflection_parameters[\key($reflection_parameters)]);
$adapters_list = ['int', 'string', 'bool', 'array', 'object', 'float', 'mixed', 'resource'];
foreach ($reflection_parameters as $parameter) {
if ($parameter->hasType() && !\in_array($parameter->getType()->getName(), $adapters_list)) {
return true;
}
}
if (!$first_parameter->hasType()) {
if (\count($args) <= count($reflection_parameters)) {
return false;
}
return true;
} elseif (!\is_a(static::$_request, $first_parameter->getType()->getName())) {
return true;
}
return false;
}
/**
* Get reflector.
*
* @param $call
* @return void
* @throws \ReflectionException
*/
protected static function getReflector($call)
{
if ($call instanceof Closure) {
return new ReflectionFunction($call);
}
return new ReflectionMethod($call[0], $call[1]);
}
/**
* Return dependent parameters
*
* @param string $plugin
* @param Request $request
* @param array $args
* @param ReflectionFunctionAbstract $reflector
* @return array
*/
protected static function resolveMethodDependencies(string $plugin, Request $request, array $args, ReflectionFunctionAbstract $reflector)
{
// Specification parameter information
$args = \array_values($args);
$parameters = [];
// An array of reflection classes for loop parameters, with each $parameter representing a reflection object of parameters
foreach ($reflector->getParameters() as $parameter) {
// Parameter quota consumption
if ($parameter->hasType()) {
$name = $parameter->getType()->getName();
switch ($name) {
case 'int':
case 'string':
case 'bool':
case 'array':
case 'object':
case 'float':
case 'mixed':
case 'resource':
goto _else;
default:
if (\is_a(static::$_request, $name)) {
//Inject Request
$parameters[] = $request;
} else {
$parameters[] = static::container($plugin)->make($name);
}
break;
}
} else {
_else:
// The variable parameter
if (null !== \key($args)) {
$parameters[] = \current($args);
} else {
// Indicates whether the current parameter has a default value. If yes, return true
$parameters[] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
}
// Quota of consumption variables
\next($args);
}
}
// Returns the result of parameters replacement
return $parameters;
}
/**
* @param string $plugin
* @return ContainerInterface
@@ -310,7 +461,7 @@ class App
}
/**
* @return Request
* @return Request|\support\Request
*/
public static function request()
{
@@ -454,6 +605,7 @@ class App
*/
protected static function parseControllerAction(string $path)
{
$path = \str_replace('-', '', $path);
$path_explode = \explode('/', trim($path, '/'));
$is_plugin = isset($path_explode[1]) && $path_explode[0] === 'app';
$config_prefix = $is_plugin ? "plugin.{$path_explode[1]}." : '';

View File

@@ -73,6 +73,14 @@ class Config
static::load($config_path, $exclude_file);
}
/**
* @return void
*/
public static function clear()
{
static::$_config = [];
}
/**
* @return void
*/

View File

@@ -16,6 +16,10 @@ class Container implements ContainerInterface
* @var array
*/
protected $_instances = [];
/**
* @var array
*/
protected $_definitions = [];
/**
* @param string $name
@@ -25,10 +29,14 @@ class Container implements ContainerInterface
public function get(string $name)
{
if (!isset($this->_instances[$name])) {
if (!\class_exists($name)) {
throw new NotFoundException("Class '$name' not found");
if (isset($this->_definitions[$name])) {
$this->_instances[$name] = call_user_func($this->_definitions[$name], $this);
} else {
if (!\class_exists($name)) {
throw new NotFoundException("Class '$name' not found");
}
$this->_instances[$name] = new $name();
}
$this->_instances[$name] = new $name();
}
return $this->_instances[$name];
}
@@ -39,7 +47,8 @@ class Container implements ContainerInterface
*/
public function has(string $name): bool
{
return \array_key_exists($name, $this->_instances);
return \array_key_exists($name, $this->_instances)
|| array_key_exists($name, $this->_definitions);
}
/**
@@ -56,4 +65,14 @@ class Container implements ContainerInterface
return new $name(... array_values($constructor));
}
/**
* @param array $definitions
* @return $this
*/
public function addDefinitions(array $definitions)
{
$this->_definitions = array_merge($this->_definitions, $definitions);
return $this;
}
}

View File

@@ -21,7 +21,7 @@ use Webman\Http\Response;
/**
* Class Handler
* @package Support\Exception
* @package support\exception
*/
class ExceptionHandler implements ExceptionHandlerInterface
{

View File

@@ -64,7 +64,7 @@ class UploadFile extends File
/**
* @return string
*/
public function getUploadMineType()
public function getUploadMimeType()
{
return $this->_uploadMimeType;
}
@@ -93,4 +93,12 @@ class UploadFile extends File
return $this->_uploadErrorCode === UPLOAD_ERR_OK;
}
}
/**
* @deprecated
* @return string
*/
public function getUploadMineType()
{
return $this->_uploadMimeType;
}
}

View File

@@ -47,8 +47,12 @@ class Install
mkdir($parent_dir, 0777, true);
}
}
copy_dir(__DIR__ . "/$source", base_path() . "/$dest", true);
$source_file = __DIR__ . "/$source";
copy_dir($source_file, base_path() . "/$dest", true);
echo "Create $dest\r\n";
if (is_file($source_file)) {
@unlink($source_file);
}
}
}

View File

@@ -358,7 +358,7 @@ class Route
require_once $route_config_file;
}
if (!is_dir($plugin_config_path = $config_path . '/plugin')) {
return;
continue;
}
$dir_iterator = new \RecursiveDirectoryIterator($plugin_config_path, \FilesystemIterator::FOLLOW_SYMLINKS);
$iterator = new \RecursiveIteratorIterator($dir_iterator);

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
Support\App::run();

View File

@@ -19,11 +19,11 @@ class App
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (class_exists(Dotenv::class) && file_exists(base_path() . '/.env')) {
if (class_exists(Dotenv::class) && file_exists(run_path('.env'))) {
if (method_exists(Dotenv::class, 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(base_path())->load();
Dotenv::createUnsafeImmutable(run_path())->load();
} else {
Dotenv::createMutable(base_path())->load();
Dotenv::createMutable(run_path())->load();
}
}
@@ -97,7 +97,7 @@ class App
require_once \base_path() . '/support/bootstrap.php';
$app = new \Webman\App(config('app.request_class', Request::class), Log::channel('default'), app_path(), public_path());
$worker->onMessage = [$app, 'onMessage'];
[$app, 'onWorkerStart']($worker);
\call_user_func([$app, 'onWorkerStart'], $worker);
};
}

View File

@@ -7,7 +7,7 @@ use Symfony\Component\Cache\Psr16Cache;
/**
* Class Cache
* @package Support\Bootstrap
* @package support\bootstrap
*
* Strings methods
* @method static mixed get($key, $default = null)

View File

@@ -19,7 +19,7 @@ use Webman\Config;
/**
* Class Container
* @package Support
* @package support
* @method static mixed get($name)
* @method static mixed make($name, array $parameters)
* @method static bool has($name)

View File

@@ -18,7 +18,7 @@ use Illuminate\Database\Capsule\Manager;
/**
* Class Db
* @package Support
* @package support
* @method static array select(string $query, $bindings = [], $useReadPdo = true)
* @method static int insert(string $query, $bindings = [])
* @method static int update(string $query, $bindings = [])

View File

@@ -21,7 +21,7 @@ use Monolog\Logger;
/**
* Class Log
* @package Support
* @package support
*
* @method static void log($level, $message, array $context = [])
* @method static void debug($message, array $context = [])

View File

@@ -23,7 +23,7 @@ use Workerman\Worker;
/**
* Class Redis
* @package Support
* @package support
*
* Strings methods
* @method static int append($key, $value)

View File

@@ -16,7 +16,7 @@ namespace support;
/**
* Class Request
* @package Support
* @package support
*/
class Request extends \Webman\Http\Request
{

View File

@@ -16,7 +16,7 @@ namespace support;
/**
* Class Response
* @package Support
* @package support
*/
class Response extends \Webman\Http\Response
{

View File

@@ -19,7 +19,7 @@ use Webman\Exception\NotFoundException;
/**
* Class Translation
* @package Support
* @package support
* @method static string trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
* @method static void setLocale(string $locale)
* @method static string getLocale()

View File

@@ -1,132 +0,0 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use Dotenv\Dotenv;
use support\Log;
use Webman\Bootstrap;
use Webman\Config;
use Webman\Route;
use Webman\Middleware;
use Webman\Util;
$worker = $worker ?? null;
if ($timezone = config('app.default_timezone')) {
date_default_timezone_set($timezone);
}
set_error_handler(function ($level, $message, $file = '', $line = 0) {
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
});
if ($worker) {
register_shutdown_function(function ($start_time) {
if (time() - $start_time <= 1) {
sleep(1);
}
}, time());
}
if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(base_path())->load();
} else {
Dotenv::createMutable(base_path())->load();
}
}
Support\App::loadAllConfig(['route']);
foreach (config('autoload.files', []) as $file) {
include_once $file;
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['autoload']['files'] ?? [] as $file) {
include_once $file;
}
}
foreach ($projects['autoload']['files'] ?? [] as $file) {
include_once $file;
}
}
Middleware::load(config('middleware', []), '');
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project) || $name === 'static') {
continue;
}
Middleware::load($project['middleware'] ?? [], '');
}
Middleware::load($projects['middleware'] ?? [], $firm);
if ($static_middlewares = config("plugin.$firm.static.middleware")) {
Middleware::load(['__static__' => $static_middlewares], $firm);
}
}
Middleware::load(['__static__' => config('static.middleware', [])], '');
foreach (config('bootstrap', []) as $class_name) {
if (!class_exists($class_name)) {
$log = "Warning: Class $class_name setting in config/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $class_name */
$class_name::start($worker);
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['bootstrap'] ?? [] as $class_name) {
if (!class_exists($class_name)) {
$log = "Warning: Class $class_name setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $class_name */
$class_name::start($worker);
}
}
foreach ($projects['bootstrap'] ?? [] as $class_name) {
if (!class_exists($class_name)) {
$log = "Warning: Class $class_name setting in plugin/$firm/config/bootstrap.php not found\r\n";
echo $log;
Log::error($log);
continue;
}
/** @var Bootstrap $class_name */
$class_name::start($worker);
}
}
$directory = base_path() . '/plugin';
$paths = [config_path()];
foreach (Util::scanDir($directory) as $path) {
if (is_dir($path = "$path/config")) {
$paths[] = $path;
}
}
Route::load($paths);

View File

@@ -21,7 +21,7 @@ use Workerman\Worker;
/**
* Class Session
* @package Support
* @package support
*/
class Session implements Bootstrap
{

View File

@@ -15,12 +15,23 @@
namespace support\exception;
use Exception;
use Webman\Http\Response;
use Webman\Http\Request;
/**
* Class BusinessException
* @package Support\Exception
* @package support\exception
*/
class BusinessException extends Exception
{
}
public function render(Request $request): ?Response
{
if ($request->expectsJson()) {
$code = $this->getCode();
$json = ['code' => $code ? $code : 500, 'msg' => $this->getMessage()];
return new Response(200, ['Content-Type' => 'application/json'],
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
return new Response(200, [], $this->getMessage());
}
}

View File

@@ -21,7 +21,7 @@ use Webman\Http\Response;
/**
* Class Handler
* @package Support\Exception
* @package support\exception
*/
class Handler extends ExceptionHandler
{
@@ -36,6 +36,11 @@ class Handler extends ExceptionHandler
public function render(Request $request, Throwable $exception): Response
{
if(($exception instanceof BusinessException) && ($response = $exception->render($request)))
{
return $response;
}
return parent::render($request, $exception);
}

View File

@@ -1,482 +0,0 @@
<?php
/**
* This file is part of webman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use support\Request;
use support\Response;
use support\Translation;
use support\Container;
use support\view\Raw;
use support\view\Blade;
use support\view\ThinkPHP;
use support\view\Twig;
use Workerman\Worker;
use Webman\App;
use Webman\Config;
use Webman\Route;
// Phar support.
if (\is_phar()) {
\define('BASE_PATH', dirname(__DIR__));
} else {
\define('BASE_PATH', realpath(__DIR__ . '/../'));
}
\define('WEBMAN_VERSION', '1.4');
/**
* @param $return_phar
* @return false|string
*/
function base_path(bool $return_phar = true)
{
static $real_path = '';
if (!$real_path) {
$real_path = \is_phar() ? \dirname(Phar::running(false)) : BASE_PATH;
}
return $return_phar ? BASE_PATH : $real_path;
}
/**
* @return string
*/
function app_path()
{
return BASE_PATH . DIRECTORY_SEPARATOR . 'app';
}
/**
* @return string
*/
function public_path()
{
static $path = '';
if (!$path) {
$path = \config('app.public_path', BASE_PATH . DIRECTORY_SEPARATOR . 'public');
}
return $path;
}
/**
* @return string
*/
function config_path()
{
return BASE_PATH . DIRECTORY_SEPARATOR . 'config';
}
/**
* Phar support.
* Compatible with the 'realpath' function in the phar file.
*
* @return string
*/
function runtime_path()
{
static $path = '';
if (!$path) {
$path = \config('app.runtime_path', BASE_PATH . DIRECTORY_SEPARATOR . 'runtime');
}
return $path;
}
/**
* @param int $status
* @param array $headers
* @param string $body
* @return Response
*/
function response($body = '', $status = 200, $headers = [])
{
return new Response($status, $headers, $body);
}
/**
* @param $data
* @param int $options
* @return Response
*/
function json($data, $options = JSON_UNESCAPED_UNICODE)
{
return new Response(200, ['Content-Type' => 'application/json'], \json_encode($data, $options));
}
/**
* @param $xml
* @return Response
*/
function xml($xml)
{
if ($xml instanceof SimpleXMLElement) {
$xml = $xml->asXML();
}
return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}
/**
* @param $data
* @param string $callback_name
* @return Response
*/
function jsonp($data, $callback_name = 'callback')
{
if (!\is_scalar($data) && null !== $data) {
$data = \json_encode($data);
}
return new Response(200, [], "$callback_name($data)");
}
/**
* @param string $location
* @param int $status
* @param array $headers
* @return Response
*/
function redirect(string $location, int $status = 302, array $headers = [])
{
$response = new Response($status, ['Location' => $location]);
if (!empty($headers)) {
$response->withHeaders($headers);
}
return $response;
}
/**
* @param $template
* @param array $vars
* @param null $app
* @return Response
*/
function view(string $template, array $vars = [], string $app = null)
{
$request = \request();
$plugin = $request->plugin ?? '';
$handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler');
return new Response(200, [], $handler::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
* @throws Throwable
*/
function raw_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], Raw::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function blade_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], Blade::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function think_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], ThinkPHP::render($template, $vars, $app));
}
/**
* @param string $template
* @param array $vars
* @param string|null $app
* @return Response
*/
function twig_view(string $template, array $vars = [], string $app = null)
{
return new Response(200, [], Twig::render($template, $vars, $app));
}
/**
* @return Request
*/
function request()
{
return App::request();
}
/**
* @param string|null $key
* @param $default
* @return array|mixed|null
*/
function config(string $key = null, $default = null)
{
return Config::get($key, $default);
}
/**
* @param string $name
* @param ...$parameters
* @return string
*/
function route(string $name, ...$parameters)
{
$route = Route::getByName($name);
if (!$route) {
return '';
}
if (!$parameters) {
return $route->url();
}
if (\is_array(\current($parameters))) {
$parameters = \current($parameters);
}
return $route->url($parameters);
}
/**
* @param mixed $key
* @param mixed $default
* @return mixed
*/
function session($key = null, $default = null)
{
$session = \request()->session();
if (null === $key) {
return $session;
}
if (\is_array($key)) {
$session->put($key);
return null;
}
if (\strpos($key, '.')) {
$key_array = \explode('.', $key);
$value = $session->all();
foreach ($key_array as $index) {
if (!isset($value[$index])) {
return $default;
}
$value = $value[$index];
}
return $value;
}
return $session->get($key, $default);
}
/**
* @param string $id
* @param array $parameters
* @param string|null $domain
* @param string|null $locale
* @return string
*/
function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
{
$res = Translation::trans($id, $parameters, $domain, $locale);
return $res === '' ? $id : $res;
}
/**
* @param null|string $locale
* @return string
*/
function locale(string $locale = null)
{
if (!$locale) {
return Translation::getLocale();
}
Translation::setLocale($locale);
}
/**
* 404 not found
*
* @return Response
*/
function not_found()
{
return new Response(404, [], \file_get_contents(public_path() . '/404.html'));
}
/**
* Copy dir.
*
* @param string $source
* @param string $dest
* @param bool $overwrite
* @return void
*/
function copy_dir(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 !== "..") {
\copy_dir("$source/$file", "$dest/$file");
}
}
} else if (\file_exists($source) && ($overwrite || !\file_exists($dest))) {
\copy($source, $dest);
}
}
/**
* Remove dir.
*
* @param string $dir
* @return bool
*/
function remove_dir(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)) ? \remove_dir("$dir/$file") : \unlink("$dir/$file");
}
return \rmdir($dir);
}
/**
* @param $worker
* @param $class
*/
function worker_bind($worker, $class)
{
$callback_map = [
'onConnect',
'onMessage',
'onClose',
'onError',
'onBufferFull',
'onBufferDrain',
'onWorkerStop',
'onWebSocketConnect'
];
foreach ($callback_map as $name) {
if (\method_exists($class, $name)) {
$worker->$name = [$class, $name];
}
}
if (\method_exists($class, 'onWorkerStart')) {
[$class, 'onWorkerStart']($worker);
}
}
/**
* @param $process_name
* @param $config
* @return void
*/
function worker_start($process_name, $config)
{
$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) {
require_once \base_path() . '/support/bootstrap.php';
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";
}
$instance = Container::make($server['handler'], $server['constructor'] ?? []);
\worker_bind($listen, $instance);
$listen->listen();
}
if (isset($config['handler'])) {
if (!\class_exists($config['handler'])) {
echo "process error: class {$config['handler']} not exists\r\n";
return;
}
$instance = Container::make($config['handler'], $config['constructor'] ?? []);
\worker_bind($worker, $instance);
}
};
}
/**
* Phar support.
* Compatible with the 'realpath' function in the phar file.
*
* @param string $file_path
* @return string
*/
function get_realpath(string $file_path): string
{
if (\strpos($file_path, 'phar://') === 0) {
return $file_path;
} else {
return \realpath($file_path);
}
}
/**
* @return bool
*/
function is_phar()
{
return \class_exists(\Phar::class, false) && Phar::running();
}
/**
* @return int
*/
function cpu_count()
{
// 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;
}

View File

@@ -12,7 +12,7 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use Jenssegers\Blade\Blade as BladeView;
use Webman\View;
@@ -20,7 +20,7 @@ use Webman\View;
/**
* Class Blade
* composer require jenssegers/blade
* @package Support\View
* @package support\view
*/
class Blade implements View
{

View File

@@ -12,14 +12,14 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use Webman\View;
use Throwable;
/**
* Class Raw
* @package Support\View
* @package support\view
*/
class Raw implements View
{
@@ -51,14 +51,14 @@ class Raw implements View
$view_suffix = \config("{$config_prefix}view.options.view_suffix", 'html');
$app = $app === null ? $request->app : $app;
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
$view_path = $app === '' ? "$base_view_path/view/$template.$view_suffix" : "$base_view_path/$app/view/$template.$view_suffix";
$__template_path__ = $app === '' ? "$base_view_path/view/$template.$view_suffix" : "$base_view_path/$app/view/$template.$view_suffix";
\extract(static::$_vars, \EXTR_SKIP);
\extract($vars, \EXTR_SKIP);
\extract(static::$_vars);
\extract($vars);
\ob_start();
// Try to include php file.
try {
include $view_path;
include $__template_path__;
} catch (Throwable $e) {
static::$_vars = [];
\ob_end_clean();

View File

@@ -12,14 +12,14 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use think\Template;
use Webman\View;
/**
* Class Blade
* @package Support\View
* @package support\view
*/
class ThinkPHP implements View
{

View File

@@ -12,7 +12,7 @@
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace support\View;
namespace support\view;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
@@ -20,7 +20,7 @@ use Webman\View;
/**
* Class Blade
* @package Support\View
* @package support\view
*/
class Twig implements View
{

View File

@@ -1,116 +0,0 @@
<?php
/**
* Start file for windows
*/
require_once __DIR__ . '/vendor/autoload.php';
use process\Monitor;
use support\App;
use Dotenv\Dotenv;
use Workerman\Worker;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
Dotenv::createUnsafeImmutable(base_path())->load();
} else {
Dotenv::createMutable(base_path())->load();
}
}
App::loadAllConfig(['route']);
$error_reporting = config('app.error_reporting');
if (isset($error_reporting)) {
error_reporting($error_reporting);
}
$runtime_process_path = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
if (!is_dir($runtime_process_path)) {
mkdir($runtime_process_path);
}
$process_files = [
__DIR__ . DIRECTORY_SEPARATOR . 'start.php'
];
foreach (config('process', []) as $process_name => $config) {
$process_files[] = write_process_file($runtime_process_path, $process_name, '');
}
foreach (config('plugin', []) as $firm => $projects) {
foreach ($projects as $name => $project) {
if (!is_array($project)) {
continue;
}
foreach ($project['process'] ?? [] as $process_name => $config) {
$process_files[] = write_process_file($runtime_process_path, $process_name, "$firm.$name");
}
}
foreach ($projects['process'] ?? [] as $process_name => $config) {
$process_files[] = write_process_file($runtime_process_path, $process_name, $firm);
}
}
function write_process_file($runtime_process_path, $process_name, $firm)
{
$process_param = $firm ? "plugin.$firm.$process_name" : $process_name;
$config_param = $firm ? "config('plugin.$firm.process')['$process_name']" : "config('process')['$process_name']";
$file_content = <<<EOF
<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use Workerman\Worker;
use Webman\Config;
use support\App;
ini_set('display_errors', 'on');
error_reporting(E_ALL);
if (is_callable('opcache_reset')) {
opcache_reset();
}
App::loadAllConfig(['route']);
worker_start('$process_param', $config_param);
if (DIRECTORY_SEPARATOR != "/") {
Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
}
Worker::runAll();
EOF;
$process_file = $runtime_process_path . DIRECTORY_SEPARATOR . "start_$process_param.php";
file_put_contents($process_file, $file_content);
return $process_file;
}
if ($monitor_config = config('process.monitor.constructor')) {
$monitor = new Monitor(...array_values($monitor_config));
}
function popen_processes($process_files)
{
$cmd = "php " . implode(' ', $process_files);
$descriptorspec = [STDIN, STDOUT, STDOUT];
$resource = proc_open($cmd, $descriptorspec, $pipes);
if (!$resource) {
exit("Can not execute $cmd\r\n");
}
return $resource;
}
$resource = popen_processes($process_files);
echo "\r\n";
while (1) {
sleep(1);
if (!empty($monitor) && $monitor->checkAllFilesChange()) {
$status = proc_get_status($resource);
$pid = $status['pid'];
shell_exec("taskkill /F /T /PID $pid");
proc_close($resource);
$resource = popen_processes($process_files);
}
}

View File

@@ -16,6 +16,7 @@ namespace Workerman\Connection;
/**
* ConnectionInterface.
*/
#[\AllowDynamicProperties]
abstract class ConnectionInterface
{
/**

View File

@@ -13,6 +13,9 @@
*/
namespace Workerman\Events;
use Throwable;
use Workerman\Worker;
/**
* select eventloop
*/
@@ -211,6 +214,7 @@ class Select implements EventInterface
*/
protected function tick()
{
$tasks_to_insert = [];
while (!$this->_scheduler->isEmpty()) {
$scheduler_data = $this->_scheduler->top();
$timer_id = $scheduler_data['data'];
@@ -228,14 +232,28 @@ class Select implements EventInterface
$task_data = $this->_eventTimer[$timer_id];
if ($task_data[2] === self::EV_TIMER) {
$next_run_time = $time_now + $task_data[3];
$this->_scheduler->insert($timer_id, -$next_run_time);
$tasks_to_insert[] = [$timer_id, -$next_run_time];
}
try {
\call_user_func_array($task_data[0], $task_data[1]);
} catch (Throwable $e) {
Worker::stopAll(250, $e);
}
\call_user_func_array($task_data[0], $task_data[1]);
if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
$this->del($timer_id, self::EV_TIMER_ONCE);
}
continue;
} else {
break;
}
}
foreach ($tasks_to_insert as $item) {
$this->_scheduler->insert($item[0], $item[1]);
}
if (!$this->_scheduler->isEmpty()) {
$scheduler_data = $this->_scheduler->top();
$next_run_time = -$scheduler_data['priority'];
$time_now = \microtime(true);
$this->_selectTimeout = \max((int) (($next_run_time - $time_now) * 1000000), 0);
return;
}
$this->_selectTimeout = 100000000;
@@ -275,10 +293,8 @@ class Select implements EventInterface
} else {
$this->_selectTimeout >= 1 && usleep($this->_selectTimeout);
$ret = false;
}
if (!$this->_scheduler->isEmpty()) {
$this->tick();
}

View File

@@ -1,584 +0,0 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author 爬山虎<blogdaren@163.com>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols\FastCGI;
use Workerman\Worker;
use Workerman\Protocols\Fcgi;
class Request
{
/**
* allowed request methods
*
* @var array
*/
const ALLOWED_REQUEST_METHODS = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE'];
/**
* allowed FastCGI roles
*
* @var array
*/
const ALLOWED_ROLES = [
Fcgi::FCGI_RESPONDER,
Fcgi::FCGI_AUTHORIZER,
Fcgi::FCGI_FILTER,
];
/**
* allowed content type
*
* @var array
*/
const ALLOWED_CONTENT_TYPES = [
self::MIME_URL_ENCODED_FORM_DATA,
self::MIME_MULTI_PART_FORM_DATA,
self::MIME_JSON_DATA,
];
/**
* the MIME type of url encoded form data
*
* @var string
*/
const MIME_URL_ENCODED_FORM_DATA = 'application/x-www-form-urlencoded';
/**
* the MIME type of multi part form data
*
* @var string
*/
const MIME_MULTI_PART_FORM_DATA = 'multipart/form-data; boundary=__X_FASTCGI_CLIENT_BOUNDARY__';
/**
* the MIME type of json data
*
* @var string
*/
const MIME_JSON_DATA = 'application/json';
/**
* FastCGI script to be executed
*
* @var string
*/
public $script = '';
/**
* content MIME Type
*
* @var string
*/
public $contentType = self::MIME_URL_ENCODED_FORM_DATA;
/**
* content data
*
* @var string
*/
public $content = '';
/**
* content length
*
* @var int
*/
public $contentLength = 0;
/**
* request uri
*
* @var string
*/
public $requestUri = '';
/**
* request method
*
* @var string
*/
public $requestMethod = 'GET';
/**
* query string
*
* @var string
*/
public $queryString = '';
/**
* gateway inteface
*
* @var string
*/
public $gatewayInterface = 'FastCGI/1.0';
/**
* server software
*
* @var string
*/
public $serverSoftware = 'FastCGI-Client';
/**
* server name
*
* @var string
*/
public $serverName = 'localhost';
/**
* request id
*
* @var int
*/
public $requestId = 0;
/**
* proxy counter for request id
*
* @var int
*/
static protected $_idCounter = 1;
/**
* custom params
*
* @var array
*/
public $customParams = [];
/**
* indicates FastCGI server to keep connection alive or not after finishing one request
*
* @var boolean
*/
public $keepAlive = true;
/**
* indicates FastCGI server to play the specific role
*
* @var string
*/
public $role = Fcgi::FCGI_RESPONDER;
/**
* @brief __construct
*
* @param string $script
* @param string|array $content
*
* @return void
*/
public function __construct($script = '', $content = '')
{
$this->setScript($script);
$this->setContent($content);
(self::$_idCounter >= (1 << 16)) && self::$_idCounter = 0;
$this->requestId = self::$_idCounter++;
}
/**
* @brief get request id
*
* @return int
*/
public function getRequestId()
{
return $this->requestId;
}
/**
* @brief set the role
*
* @param int $role
*
* @return object
*/
public function setRole($role = Fcgi::FCGI_RESPONDER)
{
if(!is_int($role) || !in_array($role, static::ALLOWED_ROLES))
{
$role = Fcgi::FCGI_RESPONDER;
}
$this->role = $role;
return $this;
}
/**
* @brief get the role
*
* @return int
*/
public function getRole()
{
return $this->role;
}
/**
* @brief set connection alive status
*
* @param boolean $status
*
* @return object
*/
public function setKeepAlive($status = true)
{
$this->keepAlive = !is_bool($status) ? true : $status;
return $this;
}
/**
* @brief get connection alive status
*
* @return boolean
*/
public function getKeepAlive()
{
return $this->keepAlive;
}
/**
* @brief get server software
*
* @return string
*/
public function getServerSoftware()
{
return $this->serverSoftware;
}
/**
* @brief set server software
*
* @param string $software
*
* @return object
*/
public function setServerSoftware($software)
{
if(!empty($software) && \is_string($software))
{
$this->serverSoftware = $software;
}
return $this;
}
/**
* @brief get server name
*
* @return string
*/
public function getServerName()
{
return $this->serverName;
}
/**
* @brief set server name
*
* @param string $name
*
* @return object
*/
public function setServerName($name)
{
if(!empty($name) && \is_string($name))
{
$this->serverName = $name;
}
return $this;
}
/**
* @brief get content type
*
* @return string
*/
public function getContentType()
{
return $this->contentType;
}
/**
* @brief set content type
*
* @param string $type
*
* @return object
*/
public function setContentType($type)
{
if(!\is_string($type) || !in_array($type, static::ALLOWED_CONTENT_TYPES))
{
$type = static::MIME_URL_ENCODED_FORM_DATA;
}
$this->contentType = $type;
return $this;
}
/**
* @brief get content
*
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* @brief set content
*
* @param string|array $content
*
* @return object
*/
public function setContent($content)
{
if(\is_string($content) || \is_array($content))
{
$this->content = !\is_string($content) ? http_build_query($content) : $content;
$this->contentLength = \strlen($this->content);
}
return $this;
}
/**
* @brief get content length
*
* @return int
*/
public function getContentLength()
{
return $this->contentLength;
}
/**
* @brief get gateway interface
*
* @return string
*/
public function getGatewayInterface()
{
return $this->gatewayInterface;
}
/**
* @brief set FastCGI script
*
* @param string $filename
*
* @return object
*/
public function setScript($filename)
{
if(!empty($filename) && \is_string($filename))
{
$this->script = $filename;
}
return $this;
}
/**
* @brief get FastCGI script
*
* @return string
*/
public function getScript()
{
return $this->script;
}
/**
* @brief set custom params
*
* @param array $pair
*
* @return object
*/
public function setCustomParams($pair)
{
if(!\is_array($pair)) return $this;
foreach($pair as $k => $v)
{
if(!\is_string($v)) continue;
$this->customParams[$k] = $v;
}
return $this;
}
/**
* @brief append custom params
*
* @param array $pair
*
* @return object
*/
public function appendCustomParams($pair)
{
if(\is_array($pair))
{
$this->customParams = \array_merge($this->customParams, $pair);
}
return $this;
}
/**
* @brief reset custom params
*
* @return object
*/
public function resetCustomParams()
{
$this->customParams = [];
return $this;
}
/**
* @brief set query string
*
* @param string|array $string
*
* @return object
*/
public function setQueryString($data = '')
{
if(\is_string($data) || \is_array($data))
{
$this->queryString = !\is_string($data) ? http_build_query($data) : $data;
}
return $this;
}
/**
* @brief get query string
*
* @return string
*/
public function getQueryString()
{
return $this->queryString;
}
/**
* @brief get custom params
*
* @return array
*/
public function getCustomParams()
{
return $this->customParams;
}
/**
* @brief get all params
*
* @return array
*/
public function getParams()
{
return \array_merge($this->customParams, $this->getDefaultParams());
}
/**
* @brief get default params
*
* @return array
*/
public function getDefaultParams()
{
return [
'GATEWAY_INTERFACE' => $this->getGatewayInterface(),
'SCRIPT_FILENAME' => $this->getScript(),
'REQUEST_METHOD' => $this->getRequestMethod(),
'REQUEST_URI' => $this->getRequestUri(),
'QUERY_STRING' => $this->getQueryString(),
'CONTENT_TYPE' => $this->getContentType(),
'CONTENT_LENGTH' => $this->getContentLength(),
'SERVER_NAME' => $this->getServerName(),
'SERVER_SOFTWARE' => $this->getServerSoftware(),
];
}
/**
* @brief set request method
*
* @param string $method
*
* @return object
*/
public function setRequestMethod($method = 'GET')
{
if(!\is_string($method) || !in_array(strtoupper($method), static::ALLOWED_REQUEST_METHODS))
{
$method = 'GET';
}
$this->requestMethod = strtoupper($method);
return $this;
}
/**
* @brief get request method
*
* @return string
*/
public function getRequestMethod()
{
return $this->requestMethod;
}
/**
* @brief get request uri
*
* @return string
*/
public function getRequestUri()
{
return $this->requestUri;
}
/**
* @brief set request uri
*
* @param string $uri
*
* @return object
*/
public function setRequestUri($uri)
{
if(\is_string($uri))
{
$this->requestUri = $uri;
}
return $this;
}
}

View File

@@ -1,233 +0,0 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author 爬山虎<blogdaren@163.com>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols\FastCGI;
class Response
{
/**
* success status
*
* @var int
*/
const STATUS_OK = 200;
/**
* invalid status
*
* @var int
*/
const STATUS_INVALID = -1;
/**
* the request id from response
*
* @var int
*/
protected $_requestId;
/**
* the stdout from response
*
* @var string
*/
protected $_stdout = '';
/**
* the stderr from response
*
* @var string
*/
protected $_stderr = '';
/**
* the origin header from response
*
* @var string
*/
protected $_header = '';
/**
* the origin body from response
*
* @var string
*/
protected $_body = '';
/**
* @brief __construct
*
* @param int $request_id
*
* @return void
*/
public function __construct($request_id = 0)
{
$this->setRequestId($request_id);
}
/**
* @brief set request id
*
* @return int
*/
public function setRequestId($id = 0)
{
$this->_requestId = (\is_int($id) && $id > 0) ? $id : -1;
return $this;
}
/**
* @brief set stdout
*
* @param string $stdout
*
* @return object
*/
public function setStdout($stdout = '')
{
if(\is_string($stdout))
{
$this->_stdout = $stdout;
}
return $this;
}
/**
* @brief get stdout
*
* @return string
*/
public function getStdout()
{
return $this->_stdout;
}
/**
* @brief set stderr
*
* @param string $stderr
*
* @return object
*/
public function setStderr($stderr = '')
{
if(\is_string($stderr))
{
$this->_stderr = $stderr;
}
return $this;
}
/**
* @brief get stderr
*
* @return void
*/
public function getStderr()
{
return $this->_stderr;
}
/**
* @brief get header
*
* @return string
*/
public function getHeader()
{
return $this->_header;
}
/**
* @brief get body
*
* @return string
*/
public function getBody()
{
return $this->_body;
}
/**
* @brief get request id
*
* @return int
*/
public function getRequestId()
{
return $this->_requestId;
}
/**
* @brief format response output
*
* @return array
*/
public function formatOutput()
{
$status = static::STATUS_INVALID;
$header = [];
$body = '';
$crlf_pos = \strpos($this->getStdout(), "\r\n\r\n");
if(false !== $crlf_pos)
{
$status = static::STATUS_OK;
$head = \substr($this->getStdout(), 0, $crlf_pos);
$body = \substr($this->getStdout(), $crlf_pos + 4);
$this->_header = \substr($this->getStdout(), 0, $crlf_pos + 4);
$this->_body = $body;
$header_lines = \explode(PHP_EOL, $head);
foreach($header_lines as $line)
{
if(preg_match('/([\w-]+):\s*(.*)$/', $line, $matches))
{
$name = \trim($matches[1]);
$value = \trim($matches[2]);
if('status' === strtolower($name))
{
$pos = strpos($value, ' ') ;
$status = false !== $pos ? \substr($value, 0, $pos) : static::STATUS_OK;
continue;
}
if(!array_key_exists($name, $header))
{
$header[$name] = $value;
continue;
}
!\is_array($header[$name]) && $header[$name] = [$header[$name]];
$header[$name][] = $value;
}
}
}
$output = [
'requestId' => $this->getRequestId(),
'status' => $status,
'stderr' => $this->getStderr(),
'header' => $header,
'body' => $body,
];
return $output;
}
}

View File

@@ -1,541 +0,0 @@
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author 爬山虎<blogdaren@163.com>
* @protocol http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Workerman\Protocols;
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\FastCGI\Request;
use Workerman\Protocols\FastCGI\Response;
class Fcgi
{
/**
* the version of fcgi protocol
*
* @var int
*/
const FCGI_VERSION_1 = 1;
/**
* the fixed length of FCGI_Header: sizeof(FCGI_Header) === 8
*
* typedef struct {
* unsigned char version;
* unsigned char type;
* unsigned char requestIdB1;
* unsigned char requestIdB0;
* unsigned char contentLengthB1;
* unsigned char contentLengthB0;
* unsigned char paddingLength;
* unsigned char reserved;
* } FCGI_Header;
*
* @var int
*/
const FCGI_HEADER_LEN = 8;
/**
* the max length of payload
*
* @var int
*/
const FCGI_MAX_PAYLOAD_LEN = 65535;
/**
* the reserved bit FCGI_Header
*
* @var string
*/
const FCGI_RESERVED = '';
/**
* the padding bit FCGI_Header
*
* @var string
*/
const FCGI_PADDING = '';
/**
* the record type of FCGI_BEGIN_REQUEST
*
* @var int
*/
const FCGI_BEGIN_REQUEST = 1;
/**
* the record type of FCGI_ABORT_REQUEST
*
* @var int
*/
const FCGI_ABORT_REQUEST = 2;
/**
* the record type of FCGI_END_REQUEST
*
* @var int
*/
const FCGI_END_REQUEST = 3;
/**
* the record type of FCGI_PARAMS
*
* @var int
*/
const FCGI_PARAMS = 4;
/**
* -------------------------------------
* the pseudo record type of FCGI_PARAMS
* -------------------------------------
*
* @var int
*/
const FCGI_PARAMS_END = 4 << 3;
/**
* the record type of FCGI_STDIN
*
* @var int
*/
const FCGI_STDIN = 5;
/**
* -------------------------------------
* the pseudo record type of FCGI_STDIN
* -------------------------------------
*
* @var int
*/
const FCGI_STDIN_END = 5 << 4;
/**
* the record type of FCGI_STDOUT
*
* @var int
*/
const FCGI_STDOUT = 6;
/**
* the record type of FCGI_STDERR
*
* @var int
*/
const FCGI_STDERR = 7;
/**
* the record type of FCGI_DATA
*
* @var int
*/
const FCGI_DATA = 8;
/**
* the record type of FCGI_GET_VALUES
*
* @var int
*/
const FCGI_GET_VALUES = 9;
/**
* the record type of FCGI_GET_VALUES_RESULT
*
* @var int
*/
const FCGI_GET_VALUES_RESULT = 10;
/**
* the record type of FCGI_UNKNOWN_TYPE
*
* @var int
*/
const FCGI_UNKNOWN_TYPE = 11;
/**
* the role type of FCGI_RESPONDER
*
* @var int
*/
const FCGI_RESPONDER = 1;
/**
* the role type of FCGI_AUTHORIZER
*
* @var int
*/
const FCGI_AUTHORIZER = 2;
/**
* the role type of FCGI_FILTER
*
* @var int
*/
const FCGI_FILTER = 3;
/**
* the protocol status of FCGI_REQUEST_COMPLETE
*
* @var int
*/
const FCGI_REQUEST_COMPLETE = 0;
/**
* the protocol status of FCGI_CANT_MPX_CONN
*
* @var int
*/
const FCGI_CANT_MPX_CONN = 1;
/**
* the protocol status of FCGI_OVERLOADED
*
* @var int
*/
const FCGI_OVERLOADED = 2;
/**
* the protocol status of FCGI_UNKNOWN_ROLE
*
* @var int
*/
const FCGI_UNKNOWN_ROLE = 3;
/**
* the request object
*
* @var object
*/
static private $_request = NULL;
/**
* check the integrity of the package
*
* @param string $buffer
* @param TcpConnection $connection
*
* @return int
*/
public static function input($buffer, TcpConnection $connection)
{
$recv_len = \strlen($buffer);
if($recv_len < static::FCGI_HEADER_LEN) return 0;
if(!isset($connection->packetLength)) $connection->packetLength = 0;
$data = \unpack("Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/Creserved", $buffer);
if(false === $data) return 0;
$chunk_len = static::FCGI_HEADER_LEN + $data['contentLength'] + $data['paddingLength'];
if($recv_len < $chunk_len) return 0;
if(static::FCGI_END_REQUEST != $data['type'])
{
$connection->packetLength += $chunk_len;
$next_chunk_len = static::input(\substr($buffer, $chunk_len), $connection);
if(0 == $next_chunk_len)
{
//important!! don't forget to reset to zero byte!!
$connection->packetLength = 0;
return 0;
}
}
else
{
$connection->packetLength += $chunk_len;
}
//check package length exceeds the max package length or not
if($connection->packetLength > $connection->maxPackageSize)
{
$msg = "Exception: recv error package. package_length = {$connection->packetLength} ";
$msg .= "exceeds the limit {$connection->maxPackageSize}" . PHP_EOL;
Worker::safeEcho($msg);
$connection->close();
return 0;
}
return $connection->packetLength;
}
/**
* @brief decode package
*
* @param string $buffer
* @param TcpConnection $connection
*
* @return array
*/
public static function decode($buffer, TcpConnection $connection)
{
$offset = 0;
$stdout = $stderr = '';
do
{
$header_buffer = \substr($buffer, $offset, static::FCGI_HEADER_LEN);
$data = \unpack("Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/Creserved", $header_buffer);
//we are not going to throw new \Exception("Failed to unpack header data from the binary buffer.");
//but just break out of the loop to avoid bring much unnecessary TCP connections with TIME_WAIT status
if(false === $data)
{
$stderr = "Failed to unpack header data from the binary buffer";
Worker::safeEcho($stderr);
$connection->close();
break;
}
$chunk_len = static::FCGI_HEADER_LEN + $data['contentLength'] + $data['paddingLength'];
$body_buffer = \substr($buffer, $offset + static::FCGI_HEADER_LEN, $chunk_len - static::FCGI_HEADER_LEN);
switch($data['type'])
{
case static::FCGI_STDOUT:
$payload = \unpack("a{$data['contentLength']}contentData/a{$data['paddingLength']}paddingData", $body_buffer);
$stdout .= $payload['contentData'];
break;
case static::FCGI_STDERR:
$payload = \unpack("a{$data['contentLength']}contentData/a{$data['paddingLength']}paddingData", $body_buffer);
$stderr .= $payload['contentData'];
break;
case static::FCGI_END_REQUEST:
$payload = \unpack("NappStatus/CprotocolStatus/a3reserved", $body_buffer);
$result = static::checkProtocolStatus($payload['protocolStatus']);
if(0 <> $result['code'])
{
$stderr = $result['msg'];
Worker::safeEcho($stderr);
$connection->close();
}
break;
default:
//not support yet
$payload = '';
break;
}
$offset += $chunk_len;
}while($offset < $connection->packetLength);
//important!! don't forget to reset to zero byte!!
$connection->packetLength = 0;
//build response
$response = new Response();
$output = $response->setRequestId($data['requestId'] ?? -1)
->setStdout($stdout)
->setStderr($stderr)
->formatOutput();
//trigger user callback as onResponse
if(!empty($connection->onResponse) && is_callable($connection->onResponse))
{
try {
\call_user_func($connection->onResponse, $connection, $response);
} catch (\Exception $e) {
$msg = "Exception: onResponse: " . $e->getMessage();
Worker::safeEcho($msg);
$connection->close();
} catch (\Error $e) {
$msg = "Exception: onResponse: " . $e->getMessage();
Worker::safeEcho($msg);
$connection->close();
}
}
return $output;
}
/**
* @brief encode package
*
* @param Request $request
* @param TcpConnection $connection
*
* @return string
*/
public static function encode(Request $request, TcpConnection $connection)
{
if(!$request instanceof Request) return '';
static::$_request = $request;
$packet = '';
$packet .= static::createPacket(static::FCGI_BEGIN_REQUEST);
$packet .= static::createPacket(static::FCGI_PARAMS);
$packet .= static::createPacket(static::FCGI_PARAMS_END);
$packet .= static::createPacket(static::FCGI_STDIN);
$packet .= static::createPacket(static::FCGI_STDIN_END);
$connection->maxSendBufferSize = TcpConnection::$defaultMaxSendBufferSize * 10;
$packet_len = \strlen($packet);
if($packet_len > $connection->maxSendBufferSize)
{
$msg = "Exception: send error package. package_length = {$packet_len} ";
$msg .= "exceeds the limit {$connection->maxSendBufferSize}" . PHP_EOL;
Worker::safeEcho($msg);
$connection->close();
return '';
}
return $packet;
}
/**
* @brief pack payload
*
* @param string $type
*
* @return string
*/
static private function packPayload($type = '')
{
$payload = '';
switch($type)
{
case static::FCGI_BEGIN_REQUEST:
$payload = \pack(
"nCa5",
static::$_request->getRole(),
static::$_request->getKeepAlive(),
static::FCGI_RESERVED
);
break;
case static::FCGI_PARAMS:
case static::FCGI_PARAMS_END:
$payload = '';
$params = (static::FCGI_PARAMS == $type) ? static::$_request->getParams() : [];
foreach($params as $name => $value)
{
$name_len = \strlen($name);
$value_len = \strlen($value);
$format = [
$name_len > 127 ? 'N' : 'C',
$value_len > 127 ? 'N' : 'C',
"a{$name_len}",
"a{$value_len}",
];
$format = implode ('', $format);
$payload .= \pack(
$format,
$name_len > 127 ? ($name_len | 0x80000000) : $name_len,
$value_len > 127 ? ($value_len | 0x80000000) : $value_len,
$name,
$value
);
}
break;
case static::FCGI_STDIN:
case static::FCGI_ABORT_REQUEST:
case static::FCGI_DATA:
$payload = \pack("a" . static::$_request->getContentLength(), static::$_request->getContent());
break;
case static::FCGI_STDIN_END:
$payload = '';
break;
case static::FCGI_UNKNOWN_TYPE:
$payload = \pack("Ca7", static::FCGI_UNKNOWN_TYPE, static::FCGI_RESERVED);
break;
default:
$payload = '';
break;
}
return $payload;
}
/**
* @brief create request packet
*
* @param string $type
*
* @return string
*/
static public function createPacket($type = '')
{
$packet = '';
$offset = 0;
$payload = static::packPayload($type);
$total_len = \strlen($payload);
//don't forget to reset pseudo record type to normal
$type == static::FCGI_PARAMS_END && $type = static::FCGI_PARAMS;
$type == static::FCGI_STDIN_END && $type = static::FCGI_STDIN;
//maybe need to split payload into many chunks
do
{
$chunk = \substr($payload, $offset, static::FCGI_MAX_PAYLOAD_LEN);
$chunk_len = \strlen($chunk);
$remainder = \abs($chunk_len % 8);
$padding_len = $remainder > 0 ? 8 - $remainder : 0;
$header = \pack(
"CCnnCC",
static::FCGI_VERSION_1,
$type,
static::$_request->getRequestId(),
$chunk_len,
$padding_len,
static::FCGI_RESERVED
);
$padding = \pack("a{$padding_len}", static::FCGI_PADDING);
$packet .= $header . $chunk . $padding;
$offset += $chunk_len;
}while($offset < $total_len);
return $packet;
}
/**
* @brief check the protocol status from FCGI_END_REQUEST body
*
* @param int $status
*
* @return array
*/
static public function checkProtocolStatus($status = 0)
{
switch($status)
{
case static::FCGI_REQUEST_COMPLETE:
$msg = 'Accepted: request completed ok';
break;
case static::FCGI_CANT_MPX_CONN:
$msg = 'Rejected: FastCGI server does not support concurrent processing';
break;
case static::FCGI_OVERLOADED:
$msg = 'Rejected: FastCGI server run out of resources or reached the limit';
break;
case static::FCGI_UNKNOWN_ROLE:
$msg = 'Rejected: FastCGI server not support the specified role';
break;
default:
$msg = 'Rejected: FastCGI server does not know what happened';
break;
}
return [
'code' => $status,
'msg' => $msg,
];
}
}

View File

@@ -94,60 +94,58 @@ class Http
*/
public static function input($recv_buffer, TcpConnection $connection)
{
static $input = array();
static $input = [];
if (!isset($recv_buffer[512]) && isset($input[$recv_buffer])) {
return $input[$recv_buffer];
}
$crlf_pos = \strpos($recv_buffer, "\r\n\r\n");
if (false === $crlf_pos) {
// Judge whether the package length exceeds the limit.
if ($recv_len = \strlen($recv_buffer) >= 16384) {
if (\strlen($recv_buffer) >= 16384) {
$connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
return 0;
}
return 0;
}
$head_len = $crlf_pos + 4;
$length = $crlf_pos + 4;
$method = \strstr($recv_buffer, ' ', true);
if ($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD' || $method === 'DELETE') {
if (!isset($recv_buffer[512])) {
$input[$recv_buffer] = $head_len;
if (\count($input) > 512) {
unset($input[key($input)]);
}
}
return $head_len;
} else if ($method !== 'POST' && $method !== 'PUT' && $method !== 'PATCH') {
if (!\in_array($method, ['GET', 'POST', 'OPTIONS', 'HEAD', 'DELETE', 'PUT', 'PATCH'])) {
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
}
$header = \substr($recv_buffer, 0, $crlf_pos);
$length = false;
if ($pos = \strpos($header, "\r\nContent-Length: ")) {
$length = $head_len + (int)\substr($header, $pos + 18, 10);
$length = $length + (int)\substr($header, $pos + 18, 10);
$has_content_length = true;
} else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) {
$length = $head_len + $match[1];
$length = $length + $match[1];
$has_content_length = true;
} else {
$has_content_length = false;
if (false !== stripos($header, "\r\nTransfer-Encoding:")) {
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
}
}
if ($length !== false) {
if (!isset($recv_buffer[512])) {
$input[$recv_buffer] = $length;
if (\count($input) > 512) {
unset($input[key($input)]);
}
}
if ($has_content_length) {
if ($length > $connection->maxPackageSize) {
$connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
return 0;
}
return $length;
}
$connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
return 0;
if (!isset($recv_buffer[512])) {
$input[$recv_buffer] = $length;
if (\count($input) > 512) {
unset($input[key($input)]);
}
}
return $length;
}
/**
@@ -221,6 +219,7 @@ class Http
$file = $response->file['file'];
$offset = $response->file['offset'];
$length = $response->file['length'];
clearstatcache();
$file_size = (int)\filesize($file);
$body_len = $length > 0 ? $length : $file_size - $offset;
$response->withHeaders(array(

View File

@@ -520,6 +520,9 @@ class Request
{
$file = [];
$boundary = "\r\n$boundary";
if (\strlen($this->_buffer) < $section_start_offset) {
return 0;
}
$section_end_offset = \strpos($this->_buffer, $boundary, $section_start_offset);
if (!$section_end_offset) {
return 0;
@@ -596,7 +599,7 @@ class Request
*/
protected static function createSessionId()
{
return \bin2hex(\pack('d', \microtime(true)) . \pack('N', \mt_rand()));
return \bin2hex(\pack('d', \microtime(true)) . random_bytes(8));
}
/**

View File

@@ -410,7 +410,7 @@ class Session
public function __destruct()
{
$this->save();
if (\rand(1, static::$gcProbability[1]) <= static::$gcProbability[0]) {
if (\random_int(1, static::$gcProbability[1]) <= static::$gcProbability[0]) {
$this->gc();
}
}

View File

@@ -87,7 +87,7 @@ class FileSessionHandler implements SessionHandlerInterface
*/
public function write($session_id, $session_data)
{
$temp_file = static::$_sessionSavePath.uniqid(mt_rand(), true);
$temp_file = static::$_sessionSavePath . uniqid(bin2hex(random_bytes(8)), true);
if (!\file_put_contents($temp_file, $session_data)) {
return false;
}

View File

@@ -355,7 +355,7 @@ class Ws
$port = $connection->getRemotePort();
$host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port;
// Handshake header.
$connection->websocketSecKey = \base64_encode(\md5(\mt_rand(), true));
$connection->websocketSecKey = \base64_encode(random_bytes(16));
$user_header = isset($connection->headers) ? $connection->headers :
(isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null);
$user_header_str = '';

View File

@@ -26,6 +26,7 @@ use \Exception;
* Worker class
* A container for listening ports
*/
#[\AllowDynamicProperties]
class Worker
{
/**
@@ -33,7 +34,7 @@ class Worker
*
* @var string
*/
const VERSION = '4.0.42';
const VERSION = '4.1.4';
/**
* Status starting.
@@ -183,7 +184,7 @@ class Worker
public $onBufferDrain = null;
/**
* Emitted when worker processes stoped.
* Emitted when worker processes stopped.
*
* @var callable
*/
@@ -548,11 +549,13 @@ class Worker
{
static::checkSapiEnv();
static::init();
static::lock();
static::parseCommand();
static::daemonize();
static::initWorkers();
static::installSignal();
static::saveMasterPid();
static::lock(\LOCK_UN);
static::displayUI();
static::forkWorkers();
static::resetStd();
@@ -629,24 +632,25 @@ class Worker
*
* @return void
*/
protected static function lock()
protected static function lock($flag = \LOCK_EX)
{
$fd = \fopen(static::$_startFile, 'r');
if ($fd && !flock($fd, LOCK_EX)) {
static::log('Workerman['.static::$_startFile.'] already running.');
exit;
static $fd;
if (\DIRECTORY_SEPARATOR !== '/') {
return;
}
$lock_file = static::$pidFile . '.lock';
$fd = $fd ?: \fopen($lock_file, 'a+');
if ($fd) {
flock($fd, $flag);
if ($flag === \LOCK_UN) {
fclose($fd);
$fd = null;
clearstatcache();
if (\is_file($lock_file)) {
unlink($lock_file);
}
}
}
}
/**
* Unlock.
*
* @return void
*/
protected static function unlock()
{
$fd = \fopen(static::$_startFile, 'r');
$fd && flock($fd, \LOCK_UN);
}
/**
@@ -1693,11 +1697,9 @@ class Worker
// onWorkerExit
if ($worker->onWorkerExit) {
try {
call_user_func($worker->onWorkerExit, $worker, $status, $pid);
} catch (\Exception $e) {
static::log("worker[{$worker->name}] onWorkerExit $e");
} catch (\Error $e) {
static::log("worker[{$worker->name}] onWorkerExit $e");
($worker->onWorkerExit)($worker, $status, $pid);
} catch (\Throwable $exception) {
static::log("worker[{$worker->name}] onWorkerExit $exception");
}
}

View File

@@ -24,7 +24,7 @@
"source": "https://github.com/walkor/workerman"
},
"require": {
"php": ">=5.4"
"php": ">=7.0"
},
"suggest": {
"ext-event": "For better performance. "