init
- 框架初始化 - 安装插件 - 修复PHP8.4报错
This commit is contained in:
101
vendor/workerman/channel/README.md
vendored
Normal file
101
vendor/workerman/channel/README.md
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# Channel
|
||||
基于订阅的多进程通讯组件,用于workerman进程间通讯或者服务器集群通讯,类似redis订阅发布机制。基于workerman开发。
|
||||
|
||||
Channel 提供两种通讯形式,分别是发布订阅的事件机制和消息队列机制。
|
||||
|
||||
它们的主要区别是:
|
||||
- 事件机制是消息发出后,所有订阅该事件的客户端都能收到消息。
|
||||
- 消息队列机制是消息发出后,所有订阅该消息的客户端只有一个会收到消息,如果客户端忙消息会进行排队直到有客户端闲置后重新取到消息。
|
||||
- 需要注意的是 Channel 只是提供一种通讯方式,本身并不提供消息确认、重试、延迟、持久化等功能,请根据实际情况合理使用。
|
||||
|
||||
# 手册地址
|
||||
[Channel手册](http://doc.workerman.net/components/channel.html)
|
||||
|
||||
# 服务端
|
||||
```php
|
||||
use Workerman\Worker;
|
||||
|
||||
//Tcp 通讯方式
|
||||
$channel_server = new Channel\Server('0.0.0.0', 2206);
|
||||
|
||||
//Unix Domain Socket 通讯方式
|
||||
//$channel_server = new Channel\Server('unix:///tmp/workerman-channel.sock');
|
||||
|
||||
if(!defined('GLOBAL_START'))
|
||||
{
|
||||
Worker::runAll();
|
||||
}
|
||||
```
|
||||
|
||||
# 客户端
|
||||
```php
|
||||
use Workerman\Worker;
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->onWorkerStart = function()
|
||||
{
|
||||
// Channel客户端连接到Channel服务端
|
||||
Channel\Client::connect('<Channel服务端ip>', 2206);
|
||||
|
||||
// 使用 Unix Domain Socket 通讯
|
||||
//Channel\Client::connect('unix:///tmp/workerman-channel.sock');
|
||||
|
||||
// 要订阅的事件名称(名称可以为任意的数字和字符串组合)
|
||||
$event_name = 'event_xxxx';
|
||||
// 订阅某个自定义事件并注册回调,收到事件后会自动触发此回调
|
||||
Channel\Client::on($event_name, function($event_data){
|
||||
var_dump($event_data);
|
||||
});
|
||||
};
|
||||
$worker->onMessage = function($connection, $data)
|
||||
{
|
||||
// 要发布的事件名称
|
||||
$event_name = 'event_xxxx';
|
||||
// 事件数据(数据格式可以为数字、字符串、数组),会传递给客户端回调函数作为参数
|
||||
$event_data = array('some data.', 'some data..');
|
||||
// 发布某个自定义事件,订阅这个事件的客户端会收到事件数据,并触发客户端对应的事件回调
|
||||
Channel\Client::publish($event_name, $event_data);
|
||||
};
|
||||
|
||||
if(!defined('GLOBAL_START'))
|
||||
{
|
||||
Worker::runAll();
|
||||
}
|
||||
````
|
||||
|
||||
## 消息队列示例
|
||||
```php
|
||||
use Workerman\Worker;
|
||||
use Workerman\Timer;
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->name = 'Producer';
|
||||
$worker->onWorkerStart = function()
|
||||
{
|
||||
Client::connect();
|
||||
|
||||
$count = 0;
|
||||
Timer::add(1, function() {
|
||||
Client::enqueue('queue', 'Hello World '.time());
|
||||
});
|
||||
};
|
||||
|
||||
$mq = new Worker();
|
||||
$mq->name = 'Consumer';
|
||||
$mq->count = 4;
|
||||
$mq->onWorkerStart = function($worker) {
|
||||
Client::connect();
|
||||
|
||||
//订阅消息 queue
|
||||
Client::watch('queue', function($data) use ($worker) {
|
||||
echo "Worker {$worker->id} get queue: $data\n";
|
||||
});
|
||||
|
||||
//10 秒后取消订阅该消息
|
||||
Timer::add(10, function() {
|
||||
Client::unwatch('queue');
|
||||
}, [], false);
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
12
vendor/workerman/channel/composer.json
vendored
Normal file
12
vendor/workerman/channel/composer.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name" : "workerman/channel",
|
||||
"type" : "library",
|
||||
"homepage": "http://www.workerman.net",
|
||||
"license" : "MIT",
|
||||
"require": {
|
||||
"workerman/workerman" : ">=4.0.12"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {"Channel\\": "./src"}
|
||||
}
|
||||
}
|
||||
394
vendor/workerman/channel/src/Client.php
vendored
Normal file
394
vendor/workerman/channel/src/Client.php
vendored
Normal file
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
namespace Channel;
|
||||
|
||||
use Workerman\Connection\AsyncTcpConnection;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Protocols\Frame;
|
||||
|
||||
/**
|
||||
* Channel/Client
|
||||
* @version 1.0.7
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/**
|
||||
* onMessage.
|
||||
* @var callback
|
||||
*/
|
||||
public static $onMessage = null;
|
||||
|
||||
/**
|
||||
* onConnect
|
||||
* @var callback
|
||||
*/
|
||||
public static $onConnect = null;
|
||||
|
||||
/**
|
||||
* onClose
|
||||
* @var callback
|
||||
*/
|
||||
public static $onClose = null;
|
||||
|
||||
/**
|
||||
* Connction to channel server.
|
||||
* @var \Workerman\Connection\TcpConnection
|
||||
*/
|
||||
protected static $_remoteConnection = null;
|
||||
|
||||
/**
|
||||
* Channel server ip.
|
||||
* @var string
|
||||
*/
|
||||
protected static $_remoteIp = null;
|
||||
|
||||
/**
|
||||
* Channel server port.
|
||||
* @var int
|
||||
*/
|
||||
protected static $_remotePort = null;
|
||||
|
||||
/**
|
||||
* Reconnect timer.
|
||||
* @var Timer
|
||||
*/
|
||||
protected static $_reconnectTimer = null;
|
||||
|
||||
/**
|
||||
* Ping timer.
|
||||
* @var Timer
|
||||
*/
|
||||
protected static $_pingTimer = null;
|
||||
|
||||
/**
|
||||
* All event callback.
|
||||
* @var array
|
||||
*/
|
||||
protected static $_events = array();
|
||||
|
||||
/**
|
||||
* All queue callback.
|
||||
* @var callable
|
||||
*/
|
||||
protected static $_queues = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $_isWorkermanEnv = true;
|
||||
|
||||
/**
|
||||
* Ping interval.
|
||||
* @var int
|
||||
*/
|
||||
public static $pingInterval = 55;
|
||||
|
||||
/**
|
||||
* Connect to channel server
|
||||
* @param string $ip Channel server ip address or unix domain socket address
|
||||
* Ip like (TCP): 192.168.1.100
|
||||
* Unix domain socket like: unix:///tmp/workerman-channel.sock
|
||||
* @param int $port Port to connect when use tcp
|
||||
*/
|
||||
public static function connect($ip = '127.0.0.1', $port = 2206)
|
||||
{
|
||||
if (self::$_remoteConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$_remoteIp = $ip;
|
||||
self::$_remotePort = $port;
|
||||
|
||||
if (PHP_SAPI !== 'cli' || !class_exists('Workerman\Worker', false)) {
|
||||
self::$_isWorkermanEnv = false;
|
||||
}
|
||||
|
||||
// For workerman environment.
|
||||
if (self::$_isWorkermanEnv) {
|
||||
if (strpos($ip, 'unix://') === false) {
|
||||
$conn = new AsyncTcpConnection('frame://' . self::$_remoteIp . ':' . self::$_remotePort);
|
||||
} else {
|
||||
$conn = new AsyncTcpConnection($ip);
|
||||
$conn->protocol = Frame::class;
|
||||
}
|
||||
|
||||
$conn->onClose = [self::class, 'onRemoteClose'];
|
||||
$conn->onConnect = [self::class, 'onRemoteConnect'];
|
||||
$conn->onMessage = [self::class , 'onRemoteMessage'];
|
||||
$conn->connect();
|
||||
|
||||
if (empty(self::$_pingTimer)) {
|
||||
self::$_pingTimer = Timer::add(self::$pingInterval, 'Channel\Client::ping');
|
||||
}
|
||||
// Not workerman environment.
|
||||
} else {
|
||||
$remote = strpos($ip, 'unix://') === false ? 'tcp://'.self::$_remoteIp.':'.self::$_remotePort : $ip;
|
||||
$conn = stream_socket_client($remote, $code, $message, 5);
|
||||
if (!$conn) {
|
||||
throw new \Exception($message);
|
||||
}
|
||||
}
|
||||
|
||||
self::$_remoteConnection = $conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* onRemoteMessage.
|
||||
* @param \Workerman\Connection\TcpConnection $connection
|
||||
* @param string $data
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function onRemoteMessage($connection, $data)
|
||||
{
|
||||
$data = unserialize($data);
|
||||
$type = $data['type'];
|
||||
$event = $data['channel'];
|
||||
$event_data = $data['data'];
|
||||
|
||||
$callback = null;
|
||||
|
||||
if ($type == 'event') {
|
||||
if (!empty(self::$_events[$event])) {
|
||||
call_user_func(self::$_events[$event], $event_data);
|
||||
} elseif (!empty(Client::$onMessage)) {
|
||||
call_user_func(Client::$onMessage, $event, $event_data);
|
||||
} else {
|
||||
throw new \Exception("event:$event have not callback");
|
||||
}
|
||||
} else {
|
||||
if (isset(self::$_queues[$event])) {
|
||||
call_user_func(self::$_queues[$event], $event_data);
|
||||
} else {
|
||||
throw new \Exception("queue:$event have not callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping.
|
||||
* @return void
|
||||
*/
|
||||
public static function ping()
|
||||
{
|
||||
if(self::$_remoteConnection)
|
||||
{
|
||||
self::$_remoteConnection->send('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onRemoteClose.
|
||||
* @return void
|
||||
*/
|
||||
public static function onRemoteClose()
|
||||
{
|
||||
Timer::add(0.5, function() {
|
||||
echo "Waring channel connection closed and try to reconnect\n";
|
||||
}, array(), false);
|
||||
self::$_remoteConnection = null;
|
||||
self::clearTimer();
|
||||
self::$_reconnectTimer = Timer::add(1, 'Channel\Client::connect', array(self::$_remoteIp, self::$_remotePort));
|
||||
if (self::$onClose) {
|
||||
call_user_func(Client::$onClose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onRemoteConnect.
|
||||
* @return void
|
||||
*/
|
||||
public static function onRemoteConnect()
|
||||
{
|
||||
$all_event_names = array_keys(self::$_events);
|
||||
if($all_event_names)
|
||||
{
|
||||
self::subscribe($all_event_names);
|
||||
}
|
||||
self::clearTimer();
|
||||
|
||||
if (self::$onConnect) {
|
||||
call_user_func(Client::$onConnect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clearTimer.
|
||||
* @return void
|
||||
*/
|
||||
public static function clearTimer()
|
||||
{
|
||||
if (!self::$_isWorkermanEnv) {
|
||||
throw new \Exception('Channel\\Client not support clearTimer method when it is not in the workerman environment.');
|
||||
}
|
||||
if(self::$_reconnectTimer)
|
||||
{
|
||||
Timer::del(self::$_reconnectTimer);
|
||||
self::$_reconnectTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On.
|
||||
* @param string $event
|
||||
* @param callback $callback
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function on($event, $callback)
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
throw new \Exception('callback is not callable for event.');
|
||||
}
|
||||
self::$_events[$event] = $callback;
|
||||
self::subscribe($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe.
|
||||
* @param string $events
|
||||
* @return void
|
||||
*/
|
||||
public static function subscribe($events)
|
||||
{
|
||||
$events = (array)$events;
|
||||
self::send(array('type' => 'subscribe', 'channels'=>$events));
|
||||
foreach ($events as $event) {
|
||||
if(!isset(self::$_events[$event])) {
|
||||
self::$_events[$event] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe.
|
||||
* @param string $events
|
||||
* @return void
|
||||
*/
|
||||
public static function unsubscribe($events)
|
||||
{
|
||||
$events = (array)$events;
|
||||
self::send(array('type' => 'unsubscribe', 'channels'=>$events));
|
||||
foreach($events as $event) {
|
||||
unset(self::$_events[$event]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish.
|
||||
* @param string $events
|
||||
* @param mixed $data
|
||||
*/
|
||||
public static function publish($events, $data , $is_loop = false)
|
||||
{
|
||||
$type = $is_loop == true ? 'publishLoop' : 'publish';
|
||||
self::sendAnyway(array('type' => $type, 'channels' => (array)$events, 'data' => $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch a channel of queue
|
||||
* @param string|array $channels
|
||||
* @param callable $callback
|
||||
* @param boolean $autoReserve Auto reserve after callback finished.
|
||||
* But sometime you may don't want reserve immediately, or in some asynchronous job,
|
||||
* you want reserve in finished callback, so you should set $autoReserve to false
|
||||
* and call Client::reserve() after watch() and in finish callback manually.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function watch($channels, $callback, $autoReserve=true)
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
throw new \Exception('callback is not callable for watch.');
|
||||
}
|
||||
|
||||
if ($autoReserve) {
|
||||
$callback = static function($data) use ($callback) {
|
||||
try {
|
||||
call_user_func($callback, $data);
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
} catch (\Error $e) {
|
||||
throw $e;
|
||||
} finally {
|
||||
self::reserve();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$channels = (array)$channels;
|
||||
self::send(array('type' => 'watch', 'channels'=>$channels));
|
||||
|
||||
foreach ($channels as $channel) {
|
||||
self::$_queues[$channel] = $callback;
|
||||
}
|
||||
|
||||
if ($autoReserve) {
|
||||
self::reserve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwatch a channel of queue
|
||||
* @param string $channel
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function unwatch($channels)
|
||||
{
|
||||
$channels = (array)$channels;
|
||||
self::send(array('type' => 'unwatch', 'channels'=>$channels));
|
||||
foreach ($channels as $channel) {
|
||||
if (isset(self::$_queues[$channel])) {
|
||||
unset(self::$_queues[$channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put data to queue
|
||||
* @param string|array $channels
|
||||
* @param mixed $data
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function enqueue($channels, $data)
|
||||
{
|
||||
self::sendAnyway(array('type' => 'enqueue', 'channels' => (array)$channels, 'data' => $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start reserve queue manual
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function reserve()
|
||||
{
|
||||
self::send(array('type' => 'reserve'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send through workerman environment
|
||||
* @param $data
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function send($data)
|
||||
{
|
||||
if (!self::$_isWorkermanEnv) {
|
||||
throw new \Exception("Channel\\Client not support {$data['type']} method when it is not in the workerman environment.");
|
||||
}
|
||||
self::connect(self::$_remoteIp, self::$_remotePort);
|
||||
self::$_remoteConnection->send(serialize($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send from any environment
|
||||
* @param $data
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function sendAnyway($data)
|
||||
{
|
||||
self::connect(self::$_remoteIp, self::$_remotePort);
|
||||
$body = serialize($data);
|
||||
if (self::$_isWorkermanEnv) {
|
||||
self::$_remoteConnection->send($body);
|
||||
} else {
|
||||
$buffer = pack('N', 4+strlen($body)) . $body;
|
||||
fwrite(self::$_remoteConnection, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
89
vendor/workerman/channel/src/Queue.php
vendored
Normal file
89
vendor/workerman/channel/src/Queue.php
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Channel;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
class Queue
|
||||
{
|
||||
|
||||
public $name = 'default';
|
||||
public $watcher = array();
|
||||
public $consumer = array();
|
||||
protected $queue = null;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->queue = new \SplQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
*/
|
||||
public function addWatch($connection)
|
||||
{
|
||||
if (!isset($this->watcher[$connection->id])) {
|
||||
$this->watcher[$connection->id] = $connection;
|
||||
$connection->watchs[] = $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
*/
|
||||
public function removeWatch($connection)
|
||||
{
|
||||
if (isset($connection->watchs) && in_array($this->name, $connection->watchs)) {
|
||||
$idx = array_search($this->name, $connection->watchs);
|
||||
unset($connection->watchs[$idx]);
|
||||
}
|
||||
if (isset($this->watcher[$connection->id])) {
|
||||
unset($this->watcher[$connection->id]);
|
||||
}
|
||||
if (isset($this->consumer[$connection->id])) {
|
||||
unset($this->consumer[$connection->id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
*/
|
||||
public function addConsumer($connection)
|
||||
{
|
||||
if (isset($this->watcher[$connection->id]) && !isset($this->consumer[$connection->id])) {
|
||||
$this->consumer[$connection->id] = $connection;
|
||||
}
|
||||
$this->dispatch();
|
||||
}
|
||||
|
||||
public function enqueue($data)
|
||||
{
|
||||
$this->queue->enqueue($data);
|
||||
$this->dispatch();
|
||||
}
|
||||
|
||||
private function dispatch()
|
||||
{
|
||||
if ($this->queue->isEmpty() || count($this->consumer) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!$this->queue->isEmpty()) {
|
||||
$data = $this->queue->dequeue();
|
||||
$idx = key($this->consumer);
|
||||
$connection = $this->consumer[$idx];
|
||||
unset($this->consumer[$idx]);
|
||||
$connection->send(serialize(array('type'=>'queue', 'channel'=>$this->name, 'data' => $data)));
|
||||
if (count($this->consumer) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->watcher) && $this->queue->isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
179
vendor/workerman/channel/src/Server.php
vendored
Normal file
179
vendor/workerman/channel/src/Server.php
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace Channel;
|
||||
|
||||
use Workerman\Protocols\Frame;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Channel server.
|
||||
*/
|
||||
class Server
|
||||
{
|
||||
/**
|
||||
* Worker instance.
|
||||
* @var Worker
|
||||
*/
|
||||
protected $_worker = null;
|
||||
|
||||
/**
|
||||
* Queues
|
||||
* @var Queue[]
|
||||
*/
|
||||
protected $_queues = array();
|
||||
|
||||
private $ip;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
* @param string $ip Bind ip address or unix domain socket.
|
||||
* Bind unix domain socket use 'unix:///tmp/channel.sock'
|
||||
* @param int $port Tcp port to bind, only used when listen on tcp.
|
||||
*/
|
||||
public function __construct($ip = '0.0.0.0', $port = 2206)
|
||||
{
|
||||
if (strpos($ip, 'unix:') === false) {
|
||||
$worker = new Worker("frame://$ip:$port");
|
||||
} else {
|
||||
$worker = new Worker($ip);
|
||||
$worker->protocol = Frame::class;
|
||||
}
|
||||
$this->ip = $ip;
|
||||
$worker->count = 1;
|
||||
$worker->name = 'ChannelServer';
|
||||
$worker->channels = array();
|
||||
$worker->onMessage = array($this, 'onMessage') ;
|
||||
$worker->onClose = array($this, 'onClose');
|
||||
$this->_worker = $worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* onClose
|
||||
* @return void
|
||||
*/
|
||||
public function onClose($connection)
|
||||
{
|
||||
if (!empty($connection->channels)) {
|
||||
foreach ($connection->channels as $channel) {
|
||||
unset($this->_worker->channels[$channel][$connection->id]);
|
||||
if (empty($this->_worker->channels[$channel])) {
|
||||
unset($this->_worker->channels[$channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($connection->watchs)) {
|
||||
foreach ($connection->watchs as $channel) {
|
||||
if (isset($this->_queues[$channel])) {
|
||||
$this->_queues[$channel]->removeWatch($connection);
|
||||
if ($this->_queues[$channel]->isEmpty()) {
|
||||
unset($this->_queues[$channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onMessage.
|
||||
* @param \Workerman\Connection\TcpConnection $connection
|
||||
* @param string $data
|
||||
*/
|
||||
public function onMessage($connection, $data)
|
||||
{
|
||||
if(!$data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$worker = $this->_worker;
|
||||
$data = unserialize($data);
|
||||
$type = $data['type'];
|
||||
switch($type)
|
||||
{
|
||||
case 'subscribe':
|
||||
foreach($data['channels'] as $channel)
|
||||
{
|
||||
$connection->channels[$channel] = $channel;
|
||||
$worker->channels[$channel][$connection->id] = $connection;
|
||||
}
|
||||
break;
|
||||
case 'unsubscribe':
|
||||
foreach($data['channels'] as $channel) {
|
||||
if (isset($connection->channels[$channel])) {
|
||||
unset($connection->channels[$channel]);
|
||||
}
|
||||
if (isset($worker->channels[$channel][$connection->id])) {
|
||||
unset($worker->channels[$channel][$connection->id]);
|
||||
if (empty($worker->channels[$channel])) {
|
||||
unset($worker->channels[$channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'publish':
|
||||
foreach ($data['channels'] as $channel) {
|
||||
if (empty($worker->channels[$channel])) {
|
||||
continue;
|
||||
}
|
||||
$buffer = serialize(array('type' => 'event', 'channel' => $channel, 'data' => $data['data']));
|
||||
foreach ($worker->channels[$channel] as $connection) {
|
||||
$connection->send($buffer);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'publishLoop':
|
||||
//choose one subscriber from the list
|
||||
foreach ($data['channels'] as $channel) {
|
||||
if (empty($worker->channels[$channel])) {
|
||||
continue;
|
||||
}
|
||||
$buffer = serialize(array('type' => 'event', 'channel' => $channel, 'data' => $data['data']));
|
||||
|
||||
//这是要点,每次取出一个元素,如果取不到,说明已经到最后,重置到第一个
|
||||
$connection = next($worker->channels[$channel]);
|
||||
if( $connection == false ){
|
||||
$connection = reset($worker->channels[$channel]);
|
||||
}
|
||||
$connection->send($buffer);
|
||||
}
|
||||
break;
|
||||
case 'watch':
|
||||
foreach ($data['channels'] as $channel) {
|
||||
$this->getQueue($channel)->addWatch($connection);
|
||||
}
|
||||
break;
|
||||
case 'unwatch':
|
||||
foreach ($data['channels'] as $channel) {
|
||||
if (isset($this->_queues[$channel])) {
|
||||
$this->_queues[$channel]->removeWatch($connection);
|
||||
if ($this->_queues[$channel]->isEmpty()) {
|
||||
unset($this->_queues[$channel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'enqueue':
|
||||
foreach ($data['channels'] as $channel) {
|
||||
$this->getQueue($channel)->enqueue($data['data']);
|
||||
}
|
||||
break;
|
||||
case 'reserve':
|
||||
if (isset($connection->watchs)) {
|
||||
foreach ($connection->watchs as $channel) {
|
||||
if (isset($this->_queues[$channel])) {
|
||||
$this->_queues[$channel]->addConsumer($connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function getQueue($channel)
|
||||
{
|
||||
if (isset($this->_queues[$channel])) {
|
||||
return $this->_queues[$channel];
|
||||
}
|
||||
return ($this->_queues[$channel] = new Queue($channel));
|
||||
}
|
||||
|
||||
}
|
||||
53
vendor/workerman/channel/test/queue.php
vendored
Normal file
53
vendor/workerman/channel/test/queue.php
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Channel\Client;
|
||||
use Channel\Server;
|
||||
use Workerman\Worker;
|
||||
use Workerman\Timer;
|
||||
|
||||
// composer autoload
|
||||
include __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$channel_server = new Server();
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->name = 'Event';
|
||||
$worker->onWorkerStart = function()
|
||||
{
|
||||
Client::connect();
|
||||
|
||||
$count = 0;
|
||||
$timerId = Timer::add(0.01, function() use (&$timerId, &$count) {
|
||||
Client::publish('test event', 'some data');
|
||||
$count++;
|
||||
Client::enqueue('task-queue', time());
|
||||
if ($count == 1000) {
|
||||
Timer::del($timerId);
|
||||
}
|
||||
});
|
||||
|
||||
Timer::add(10, function() {
|
||||
Client::enqueue('task-queue', 'hello every 10 seconds');
|
||||
});
|
||||
};
|
||||
|
||||
$mq = new Worker();
|
||||
$mq->name = 'Queue';
|
||||
$mq->count = 4;
|
||||
$mq->onWorkerStart = function($worker) {
|
||||
Client::connect();
|
||||
$countDown = 20;
|
||||
$id = 1;
|
||||
Client::watch('task-queue', function($data) use ($worker, &$countDown, &$id) {
|
||||
echo "[$id] Worker {$worker->id} get queue: $data\n";
|
||||
sleep(0.2);
|
||||
$countDown--;
|
||||
$id++;
|
||||
if ($worker->id > 1 && $countDown == 0) {
|
||||
Client::unwatch('task-queue');
|
||||
}
|
||||
Timer::add(1, [Client::class, 'reserve'], [], false);
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
28
vendor/workerman/channel/test/server.php
vendored
Normal file
28
vendor/workerman/channel/test/server.php
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Channel\Client;
|
||||
use Channel\Server;
|
||||
use Workerman\Worker;
|
||||
use Workerman\Timer;
|
||||
|
||||
// composer autoload
|
||||
include __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$channel_server = new Server();
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->onWorkerStart = function()
|
||||
{
|
||||
Client::connect();
|
||||
|
||||
Client::on('test event', function($event_data){
|
||||
echo 'test event triggered event_data :';
|
||||
var_dump($event_data);
|
||||
});
|
||||
|
||||
Timer::add(2, function(){
|
||||
Client::publish('test event', 'some data');
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
23
vendor/workerman/channel/test/start_channel.php
vendored
Normal file
23
vendor/workerman/channel/test/start_channel.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Administrator
|
||||
* Date: 2022/2/20
|
||||
* Time: 12:00
|
||||
*/
|
||||
|
||||
include_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
$processName = "ChannelServerTest";
|
||||
Worker::$pidFile = "var/{$processName}.pid";
|
||||
Worker::$logFile = "var/{$processName}_logFile.log";
|
||||
Worker::$stdoutFile = "var/{$processName}_stdout.log";
|
||||
|
||||
$channel_server = new Channel\Server('0.0.0.0', 2206);
|
||||
|
||||
if(!defined('GLOBAL_START'))
|
||||
{
|
||||
Worker::runAll();
|
||||
}
|
||||
34
vendor/workerman/channel/test/start_client.php
vendored
Normal file
34
vendor/workerman/channel/test/start_client.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
include_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Lib\Timer;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Connection\AsyncUdpConnection;
|
||||
use Workerman\Connection\AsyncTcpConnection;
|
||||
|
||||
//监听端口
|
||||
$worker = new Worker("");
|
||||
|
||||
//开启进程数量
|
||||
$worker->count = 8;
|
||||
$processName = "client";
|
||||
$worker->name = $processName;
|
||||
$worker->reusePort = true; //开启均衡负载模式
|
||||
|
||||
Worker::$pidFile = "var/{$processName}.pid";
|
||||
Worker::$logFile = "var/{$processName}_logFile.log";
|
||||
Worker::$stdoutFile = "var/{$processName}_stdout.log";
|
||||
|
||||
$worker->onWorkerStart = function() use($worker){
|
||||
usleep(10);
|
||||
Channel\Client::connect('127.0.0.1' , 2206);
|
||||
$event_name = "test_channel";
|
||||
Channel\Client::on($event_name, function($event_data)use($worker ,$event_name ){
|
||||
$log_str = "{$worker->id} on {$event_name}:".json_encode($event_data,320)."\n";
|
||||
echo $log_str;
|
||||
});
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
35
vendor/workerman/channel/test/start_send.php
vendored
Normal file
35
vendor/workerman/channel/test/start_send.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
include_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Lib\Timer;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Connection\AsyncUdpConnection;
|
||||
use Workerman\Connection\AsyncTcpConnection;
|
||||
|
||||
//监听端口
|
||||
$worker = new Worker("");
|
||||
|
||||
//开启进程数量
|
||||
$worker->count = 1;
|
||||
$processName = "send";
|
||||
$worker->name = $processName;
|
||||
$worker->reusePort = true; //开启均衡负载模式
|
||||
|
||||
Worker::$pidFile = "var/{$processName}.pid";
|
||||
Worker::$logFile = "var/{$processName}_logFile.log";
|
||||
Worker::$stdoutFile = "var/{$processName}_stdout.log";
|
||||
|
||||
$worker->onWorkerStart = function() use($worker){
|
||||
Channel\Client::connect('127.0.0.1' , 2206);
|
||||
Timer::add( 1 , function ()use($worker){
|
||||
$data_arr = [
|
||||
'time' => microtime(true),
|
||||
'date' => date("Y-m-d H:i:s"),
|
||||
];
|
||||
$event_name = "test_channel";
|
||||
Channel\Client::publish($event_name, $data_arr , true);
|
||||
});
|
||||
};
|
||||
Worker::runAll();
|
||||
184
vendor/workerman/phpsocket.io/README.md
vendored
Normal file
184
vendor/workerman/phpsocket.io/README.md
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
# phpsocket.io
|
||||
A server side alternative implementation of [socket.io](https://github.com/socketio/socket.io) in PHP based on [Workerman](https://github.com/walkor/Workerman).<br>
|
||||
|
||||
# Notice
|
||||
Only support socket.io >= v1.3.0 and <= v2.x <br>
|
||||
This project is just translate socket.io by [workerman](https://github.com/walkor/Workerman).<br>
|
||||
More api just see [https://socket.io/docs/v2/server-api/](https://socket.io/docs/v2/server-api/)
|
||||
|
||||
# Install
|
||||
composer require workerman/phpsocket.io
|
||||
|
||||
# Examples
|
||||
## Simple chat
|
||||
start.php
|
||||
```php
|
||||
|
||||
use Workerman\Worker;
|
||||
use PHPSocketIO\SocketIO;
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Listen port 2021 for socket.io client
|
||||
$io = new SocketIO(2021);
|
||||
$io->on('connection', function ($socket) use ($io) {
|
||||
$socket->on('chat message', function ($msg) use ($io) {
|
||||
$io->emit('chat message', $msg);
|
||||
});
|
||||
});
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
## Another chat demo
|
||||
|
||||
https://github.com/walkor/phpsocket.io/blob/master/examples/chat/start_io.php
|
||||
```php
|
||||
|
||||
use Workerman\Worker;
|
||||
use PHPSocketIO\SocketIO;
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Listen port 2020 for socket.io client
|
||||
$io = new SocketIO(2020);
|
||||
$io->on('connection', function ($socket) {
|
||||
$socket->addedUser = false;
|
||||
|
||||
// When the client emits 'new message', this listens and executes
|
||||
$socket->on('new message', function ($data) use ($socket) {
|
||||
// We tell the client to execute 'new message'
|
||||
$socket->broadcast->emit('new message', array(
|
||||
'username' => $socket->username,
|
||||
'message' => $data
|
||||
));
|
||||
});
|
||||
|
||||
// When the client emits 'add user', this listens and executes
|
||||
$socket->on('add user', function ($username) use ($socket) {
|
||||
global $usernames, $numUsers;
|
||||
|
||||
// We store the username in the socket session for this client
|
||||
$socket->username = $username;
|
||||
// Add the client's username to the global list
|
||||
$usernames[$username] = $username;
|
||||
++$numUsers;
|
||||
|
||||
$socket->addedUser = true;
|
||||
$socket->emit('login', array(
|
||||
'numUsers' => $numUsers
|
||||
));
|
||||
|
||||
// echo globally (all clients) that a person has connected
|
||||
$socket->broadcast->emit('user joined', array(
|
||||
'username' => $socket->username,
|
||||
'numUsers' => $numUsers
|
||||
));
|
||||
});
|
||||
|
||||
// When the client emits 'typing', we broadcast it to others
|
||||
$socket->on('typing', function () use ($socket) {
|
||||
$socket->broadcast->emit('typing', array(
|
||||
'username' => $socket->username
|
||||
));
|
||||
});
|
||||
|
||||
// When the client emits 'stop typing', we broadcast it to others
|
||||
$socket->on('stop typing', function () use ($socket) {
|
||||
$socket->broadcast->emit('stop typing', array(
|
||||
'username' => $socket->username
|
||||
));
|
||||
});
|
||||
|
||||
// When the user disconnects, perform this
|
||||
$socket->on('disconnect', function () use ($socket) {
|
||||
global $usernames, $numUsers;
|
||||
|
||||
// Remove the username from global usernames list
|
||||
if ($socket->addedUser) {
|
||||
unset($usernames[$socket->username]);
|
||||
--$numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
$socket->broadcast->emit('user left', array(
|
||||
'username' => $socket->username,
|
||||
'numUsers' => $numUsers
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
## Enable SSL for https
|
||||
**```(phpsocket.io>=1.1.1 && workerman>=3.3.7 required)```**
|
||||
|
||||
start.php
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
use PHPSocketIO\SocketIO;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// SSL context
|
||||
$context = array(
|
||||
'ssl' => array(
|
||||
'local_cert' => '/your/path/of/server.pem',
|
||||
'local_pk' => '/your/path/of/server.key',
|
||||
'verify_peer' => false
|
||||
)
|
||||
);
|
||||
$io = new SocketIO(2021, $context);
|
||||
|
||||
$io->on('connection', function ($connection) use ($io) {
|
||||
echo "New connection coming\n";
|
||||
});
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
## Acknowledgement callback
|
||||
```php
|
||||
|
||||
use Workerman\Worker;
|
||||
use PHPSocketIO\SocketIO;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$io = new SocketIO(2021);
|
||||
|
||||
$io->on('connection', function ($connection) use ($io) {
|
||||
$socket->on('message with ack', function ($data, $callback) use ($socket, $io) {
|
||||
// acknowledgement callback
|
||||
if ($callback && is_callable($callback)) {
|
||||
$callback(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
# 手册
|
||||
[中文手册](https://github.com/walkor/phpsocket.io/tree/master/docs/zh)
|
||||
|
||||
# Livedemo
|
||||
[chat demo](http://demos.workerman.net/phpsocketio-chat/)
|
||||
|
||||
# Run chat example
|
||||
cd examples/chat
|
||||
|
||||
## Start
|
||||
```php start.php start``` for debug mode
|
||||
|
||||
```php start.php start -d ``` for daemon mode
|
||||
|
||||
## Stop
|
||||
```php start.php stop```
|
||||
|
||||
## Status
|
||||
```php start.php status```
|
||||
|
||||
# License
|
||||
MIT
|
||||
30
vendor/workerman/phpsocket.io/composer.json
vendored
Normal file
30
vendor/workerman/phpsocket.io/composer.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "workerman/phpsocket.io",
|
||||
"description": "A server side alternative implementation of socket.io in PHP based on Workerman",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"socket.io",
|
||||
"phpsocket.io",
|
||||
"workerman",
|
||||
"sockets",
|
||||
"async",
|
||||
"stream",
|
||||
"server",
|
||||
"non-blocking"
|
||||
],
|
||||
"homepage": "https://www.workerman.net",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"workerman/workerman": "^4.0.0",
|
||||
"workerman/channel": ">=1.0.0",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPSocketIO\\": "./src"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
}
|
||||
}
|
||||
116
vendor/workerman/phpsocket.io/src/ChannelAdapter.php
vendored
Normal file
116
vendor/workerman/phpsocket.io/src/ChannelAdapter.php
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ChannelAdapter extends DefaultAdapter
|
||||
{
|
||||
protected $_channelId = null;
|
||||
|
||||
public static $ip = '127.0.0.1';
|
||||
|
||||
public static $port = 2206;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($nsp)
|
||||
{
|
||||
parent::__construct($nsp);
|
||||
$this->_channelId = (function_exists('random_int') ? random_int(1, 10000000) : rand(1, 10000000)) . "-" . (function_exists('posix_getpid') ? posix_getpid() : 1);
|
||||
\Channel\Client::connect(self::$ip, self::$port);
|
||||
\Channel\Client::$onMessage = [$this, 'onChannelMessage'];
|
||||
\Channel\Client::subscribe("socket.io#/#");
|
||||
Debug::debug('ChannelAdapter __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('ChannelAdapter __destruct');
|
||||
}
|
||||
|
||||
public function add($id, $room)
|
||||
{
|
||||
$this->sids[$id][$room] = true;
|
||||
$this->rooms[$room][$id] = true;
|
||||
$channel = "socket.io#/#$room#";
|
||||
\Channel\Client::subscribe($channel);
|
||||
}
|
||||
|
||||
public function del($id, $room)
|
||||
{
|
||||
unset($this->sids[$id][$room]);
|
||||
unset($this->rooms[$room][$id]);
|
||||
if (empty($this->rooms[$room])) {
|
||||
unset($this->rooms[$room]);
|
||||
$channel = "socket.io#/#$room#";
|
||||
\Channel\Client::unsubscribe($channel);
|
||||
}
|
||||
}
|
||||
|
||||
public function delAll($id)
|
||||
{
|
||||
$rooms = isset($this->sids[$id]) ? array_keys($this->sids[$id]) : [];
|
||||
if ($rooms) {
|
||||
foreach ($rooms as $room) {
|
||||
if (isset($this->rooms[$room][$id])) {
|
||||
unset($this->rooms[$room][$id]);
|
||||
$channel = "socket.io#/#$room#";
|
||||
\Channel\Client::unsubscribe($channel);
|
||||
}
|
||||
if (isset($this->rooms[$room]) && empty($this->rooms[$room])) {
|
||||
unset($this->rooms[$room]);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($this->sids[$id]);
|
||||
}
|
||||
|
||||
public function onChannelMessage($channel, $msg)
|
||||
{
|
||||
if ($this->_channelId === array_shift($msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$packet = $msg[0];
|
||||
|
||||
$opts = $msg[1];
|
||||
|
||||
if (! $packet) {
|
||||
echo "invalid channel:$channel packet \n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($packet['nsp'])) {
|
||||
$packet['nsp'] = '/';
|
||||
}
|
||||
|
||||
if ($packet['nsp'] != $this->nsp->name) {
|
||||
echo "ignore different namespace {$packet['nsp']} != {$this->nsp->name}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$this->broadcast($packet, $opts, true);
|
||||
}
|
||||
|
||||
public function broadcast($packet, $opts, $remote = false)
|
||||
{
|
||||
parent::broadcast($packet, $opts);
|
||||
if (! $remote) {
|
||||
$packet['nsp'] = '/';
|
||||
|
||||
if (! empty($opts['rooms'])) {
|
||||
foreach ($opts['rooms'] as $room) {
|
||||
$chn = "socket.io#/#$room#";
|
||||
$msg = [$this->_channelId, $packet, $opts];
|
||||
\Channel\Client::publish($chn, $msg);
|
||||
}
|
||||
} else {
|
||||
$chn = "socket.io#/#";
|
||||
$msg = [$this->_channelId, $packet, $opts];
|
||||
\Channel\Client::publish($chn, $msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
250
vendor/workerman/phpsocket.io/src/Client.php
vendored
Normal file
250
vendor/workerman/phpsocket.io/src/Client.php
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Parser\Decoder;
|
||||
use PHPSocketIO\Parser\Encoder;
|
||||
use PHPSocketIO\Parser\Parser;
|
||||
|
||||
class Client
|
||||
{
|
||||
public $server = null;
|
||||
public $conn = null;
|
||||
public $encoder = null;
|
||||
public $decoder = null;
|
||||
public $id = null;
|
||||
public $request = null;
|
||||
public $nsps = [];
|
||||
public $connectBuffer = [];
|
||||
/**
|
||||
* @var array|mixed|null
|
||||
*/
|
||||
public $sockets;
|
||||
|
||||
public function __construct($server, $conn)
|
||||
{
|
||||
$this->server = $server;
|
||||
$this->conn = $conn;
|
||||
$this->encoder = new Encoder();
|
||||
$this->decoder = new Decoder();
|
||||
$this->id = $conn->id;
|
||||
$this->request = $conn->request;
|
||||
$this->setup();
|
||||
Debug::debug('Client __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Client __destruct');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up event listeners.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->decoder->on('decoded', [$this, 'ondecoded']);
|
||||
$this->conn->on('data', [$this, 'ondata']);
|
||||
$this->conn->on('error', [$this, 'onerror']);
|
||||
$this->conn->on('close', [$this, 'onclose']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a client to a namespace.
|
||||
*
|
||||
* @param {String} namespace name
|
||||
* @api private
|
||||
*/
|
||||
|
||||
public function connect($name)
|
||||
{
|
||||
if (! isset($this->server->nsps[$name])) {
|
||||
$this->packet(['type' => Parser::ERROR, 'nsp' => $name, 'data' => 'Invalid namespace']);
|
||||
return;
|
||||
}
|
||||
$nsp = $this->server->of($name);
|
||||
if ('/' !== $name && ! isset($this->nsps['/'])) {
|
||||
$this->connectBuffer[$name] = $name;
|
||||
return;
|
||||
}
|
||||
$nsp->add($this, $nsp, [$this, 'nspAdd']);
|
||||
}
|
||||
|
||||
public function nspAdd($socket, $nsp)
|
||||
{
|
||||
$this->sockets[$socket->id] = $socket;
|
||||
$this->nsps[$nsp->name] = $socket;
|
||||
if ('/' === $nsp->name && $this->connectBuffer) {
|
||||
foreach ($this->connectBuffer as $name) {
|
||||
$this->connect($name);
|
||||
}
|
||||
$this->connectBuffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects from all namespaces and closes transport.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
foreach ($this->sockets as $socket) {
|
||||
$socket->disconnect();
|
||||
}
|
||||
$this->sockets = [];
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a socket. Called by each `Socket`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function remove($socket)
|
||||
{
|
||||
if (isset($this->sockets[$socket->id])) {
|
||||
$nsp = $this->sockets[$socket->id]->nsp->name;
|
||||
unset($this->sockets[$socket->id]);
|
||||
unset($this->nsps[$nsp]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying connection.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if (empty($this->conn)) {
|
||||
return;
|
||||
}
|
||||
if ('open' === $this->conn->readyState) {
|
||||
$this->conn->close();
|
||||
$this->onclose('forced server close');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet to the transport.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
public function packet($packet, $preEncoded = false, $volatile = false)
|
||||
{
|
||||
if (! empty($this->conn) && 'open' === $this->conn->readyState) {
|
||||
if (! $preEncoded) {
|
||||
// not broadcasting, need to encode
|
||||
$encodedPackets = $this->encoder->encode($packet);
|
||||
$this->writeToEngine($encodedPackets, $volatile);
|
||||
} else { // a broadcast pre-encodes a packet
|
||||
$this->writeToEngine($packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function writeToEngine($encodedPackets, $volatile = false)
|
||||
{
|
||||
if ($volatile) {
|
||||
echo new Exception('volatile');
|
||||
}
|
||||
if ($volatile && ! $this->conn->transport->writable) {
|
||||
return;
|
||||
}
|
||||
if (isset($encodedPackets['nsp'])) {
|
||||
unset($encodedPackets['nsp']);
|
||||
}
|
||||
foreach ($encodedPackets as $packet) {
|
||||
$this->conn->write($packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with incoming transport data.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function ondata($data)
|
||||
{
|
||||
try {
|
||||
// todo chek '2["chat message","2"]' . "\0" . ''
|
||||
$this->decoder->add(trim($data));
|
||||
} catch (Exception $e) {
|
||||
$this->onerror($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when parser fully decodes a packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function ondecoded($packet)
|
||||
{
|
||||
if (Parser::CONNECT == $packet['type']) {
|
||||
$this->connect($packet['nsp']);
|
||||
} else {
|
||||
if (isset($this->nsps[$packet['nsp']])) {
|
||||
$this->nsps[$packet['nsp']]->onpacket($packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
*
|
||||
* @param {Objcet} error object
|
||||
* @api private
|
||||
*/
|
||||
public function onerror($err)
|
||||
{
|
||||
foreach ($this->sockets as $socket) {
|
||||
$socket->onerror($err);
|
||||
}
|
||||
$this->onclose('client error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @api private
|
||||
*/
|
||||
public function onclose($reason)
|
||||
{
|
||||
if (empty($this->conn)) {
|
||||
return;
|
||||
}
|
||||
// ignore a potential subsequent `close` event
|
||||
$this->destroy();
|
||||
|
||||
// `nsps` and `sockets` are cleaned up seamlessly
|
||||
foreach ($this->sockets as $socket) {
|
||||
$socket->onclose($reason);
|
||||
}
|
||||
$this->sockets = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up event listeners.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
if (! $this->conn) {
|
||||
return;
|
||||
}
|
||||
$this->conn->removeAllListeners();
|
||||
$this->decoder->removeAllListeners();
|
||||
$this->encoder->removeAllListeners();
|
||||
$this->server = $this->conn = $this->encoder = $this->decoder = $this->request = $this->nsps = null;
|
||||
}
|
||||
}
|
||||
14
vendor/workerman/phpsocket.io/src/Debug.php
vendored
Normal file
14
vendor/workerman/phpsocket.io/src/Debug.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
class Debug
|
||||
{
|
||||
public static function debug($var)
|
||||
{
|
||||
global $debug;
|
||||
if ($debug) {
|
||||
echo var_export($var, true) . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
100
vendor/workerman/phpsocket.io/src/DefaultAdapter.php
vendored
Normal file
100
vendor/workerman/phpsocket.io/src/DefaultAdapter.php
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
class DefaultAdapter
|
||||
{
|
||||
public $nsp = null;
|
||||
public $rooms = [];
|
||||
public $sids = [];
|
||||
public $encoder = null;
|
||||
|
||||
public function __construct($nsp)
|
||||
{
|
||||
$this->nsp = $nsp;
|
||||
$this->encoder = new Parser\Encoder();
|
||||
Debug::debug('DefaultAdapter __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('DefaultAdapter __destruct');
|
||||
}
|
||||
|
||||
public function add($id, $room)
|
||||
{
|
||||
$this->sids[$id][$room] = true;
|
||||
$this->rooms[$room][$id] = true;
|
||||
}
|
||||
|
||||
public function del($id, $room)
|
||||
{
|
||||
unset($this->sids[$id][$room]);
|
||||
unset($this->rooms[$room][$id]);
|
||||
if (empty($this->rooms[$room])) {
|
||||
unset($this->rooms[$room]);
|
||||
}
|
||||
}
|
||||
|
||||
public function delAll($id)
|
||||
{
|
||||
$rooms = array_keys($this->sids[$id] ?? []);
|
||||
foreach ($rooms as $room) {
|
||||
$this->del($id, $room);
|
||||
}
|
||||
unset($this->sids[$id]);
|
||||
}
|
||||
|
||||
public function broadcast($packet, $opts, $remote = false)
|
||||
{
|
||||
$rooms = $opts['rooms'] ?? [];
|
||||
$except = $opts['except'] ?? [];
|
||||
$flags = $opts['flags'] ?? [];
|
||||
$packetOpts = [
|
||||
'preEncoded' => true,
|
||||
'volatile' => $flags['volatile'] ?? null,
|
||||
'compress' => $flags['compress'] ?? null
|
||||
];
|
||||
$packet['nsp'] = $this->nsp->name;
|
||||
$encodedPackets = $this->encoder->encode($packet);
|
||||
if ($rooms) {
|
||||
$ids = [];
|
||||
foreach ($rooms as $i => $room) {
|
||||
if (! isset($this->rooms[$room])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$room = $this->rooms[$room];
|
||||
foreach ($room as $id => $item) {
|
||||
if (isset($ids[$id]) || isset($except[$id])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($this->nsp->connected[$id])) {
|
||||
$ids[$id] = true;
|
||||
$this->nsp->connected[$id]->packet($encodedPackets, $packetOpts);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->sids as $id => $sid) {
|
||||
if (isset($except[$id])) {
|
||||
continue;
|
||||
}
|
||||
if (isset($this->nsp->connected[$id])) {
|
||||
$socket = $this->nsp->connected[$id];
|
||||
$volatile = $flags['volatile'] ?? null;
|
||||
$socket->packet($encodedPackets, true, $volatile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function clients($rooms, $fn)
|
||||
{
|
||||
$sids = [];
|
||||
foreach ($rooms as $room) {
|
||||
$sids = array_merge($sids, $this->rooms[$room]);
|
||||
}
|
||||
$fn();
|
||||
}
|
||||
}
|
||||
276
vendor/workerman/phpsocket.io/src/Engine/Engine.php
vendored
Normal file
276
vendor/workerman/phpsocket.io/src/Engine/Engine.php
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Engine\Transports\WebSocket;
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Engine extends Emitter
|
||||
{
|
||||
public $server;
|
||||
public $pingTimeout = 60;
|
||||
public $pingInterval = 25;
|
||||
public $upgradeTimeout = 5;
|
||||
public $transports = [];
|
||||
public $allowUpgrades = [];
|
||||
public $allowRequest = [];
|
||||
public $clients = [];
|
||||
public $origins = '*:*';
|
||||
public static $allowTransports = [
|
||||
'polling' => 'polling',
|
||||
'websocket' => 'websocket'
|
||||
];
|
||||
|
||||
public static $errorMessages = [
|
||||
'Transport unknown',
|
||||
'Session ID unknown',
|
||||
'Bad handshake method',
|
||||
'Bad request'
|
||||
];
|
||||
|
||||
private const ERROR_UNKNOWN_TRANSPORT = 0;
|
||||
|
||||
private const ERROR_UNKNOWN_SID = 1;
|
||||
|
||||
private const ERROR_BAD_HANDSHAKE_METHOD = 2;
|
||||
|
||||
private const ERROR_BAD_REQUEST = 3;
|
||||
|
||||
public function __construct($opts = [])
|
||||
{
|
||||
$ops_map = [
|
||||
'pingTimeout',
|
||||
'pingInterval',
|
||||
'upgradeTimeout',
|
||||
'transports',
|
||||
'allowUpgrades',
|
||||
'allowRequest'
|
||||
];
|
||||
|
||||
foreach ($ops_map as $key) {
|
||||
if (isset($opts[$key])) {
|
||||
$this->$key = $opts[$key];
|
||||
}
|
||||
}
|
||||
Debug::debug('Engine __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Engine __destruct');
|
||||
}
|
||||
|
||||
public function handleRequest(object $req, object $res)
|
||||
{
|
||||
$this->prepare($req);
|
||||
$req->res = $res;
|
||||
$this->verify($req, $res, false, [$this, 'dealRequest']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dealRequest($err, bool $success, object $req)
|
||||
{
|
||||
if (! $success) {
|
||||
self::sendErrorMessage($req, $req->res, $err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($req->_query['sid'])) {
|
||||
$this->clients[$req->_query['sid']]->transport->onRequest($req);
|
||||
} else {
|
||||
$this->handshake($req->_query['transport'], $req);
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendErrorMessage(object $req, object $res, ?string $code): void
|
||||
{
|
||||
$headers = ['Content-Type' => 'application/json'];
|
||||
if (isset($req->headers['origin'])) {
|
||||
$headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
$headers['Access-Control-Allow-Origin'] = $req->headers['origin'];
|
||||
} else {
|
||||
$headers['Access-Control-Allow-Origin'] = '*';
|
||||
}
|
||||
|
||||
$res->writeHead(403, '', $headers);
|
||||
$res->end(
|
||||
json_encode(
|
||||
[
|
||||
'code' => $code,
|
||||
'message' => self::$errorMessages[$code] ?? $code
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function verify(object $req, object $res, bool $upgrade, callable $fn)
|
||||
{
|
||||
if (! isset($req->_query['transport']) || ! isset(self::$allowTransports[$req->_query['transport']])) {
|
||||
return call_user_func($fn, self::ERROR_UNKNOWN_TRANSPORT, false, $req, $res);
|
||||
}
|
||||
$transport = $req->_query['transport'];
|
||||
$sid = $req->_query['sid'] ?? '';
|
||||
if ($sid) {
|
||||
if (! isset($this->clients[$sid])) {
|
||||
return call_user_func($fn, self::ERROR_UNKNOWN_SID, false, $req, $res);
|
||||
}
|
||||
if (! $upgrade && $this->clients[$sid]->transport->name !== $transport) {
|
||||
return call_user_func($fn, self::ERROR_BAD_REQUEST, false, $req, $res);
|
||||
}
|
||||
} else {
|
||||
if ('GET' !== $req->method) {
|
||||
return call_user_func($fn, self::ERROR_BAD_HANDSHAKE_METHOD, false, $req, $res);
|
||||
}
|
||||
return $this->checkRequest($req, $res, $fn);
|
||||
}
|
||||
call_user_func($fn, null, true, $req, $res);
|
||||
}
|
||||
|
||||
public function checkRequest(object $req, object $res, callable $fn)
|
||||
{
|
||||
if ($this->origins === "*:*" || empty($this->origins)) {
|
||||
return call_user_func($fn, null, true, $req, $res);
|
||||
}
|
||||
$origin = null;
|
||||
if (isset($req->headers['origin'])) {
|
||||
$origin = $req->headers['origin'];
|
||||
} elseif (isset($req->headers['referer'])) {
|
||||
$origin = $req->headers['referer'];
|
||||
}
|
||||
|
||||
// file:// URLs produce a null Origin which can't be authorized via echo-back
|
||||
if ('null' === $origin || null === $origin) {
|
||||
return call_user_func($fn, null, true, $req, $res);
|
||||
}
|
||||
|
||||
if ($origin) {
|
||||
$parts = parse_url($origin);
|
||||
$defaultPort = 'https:' === $parts['scheme'] ? 443 : 80;
|
||||
$parts['port'] = $parts['port'] ?? $defaultPort;
|
||||
$allowed_origins = explode(' ', $this->origins);
|
||||
foreach ($allowed_origins as $allow_origin) {
|
||||
$ok =
|
||||
$allow_origin === $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'] ||
|
||||
$allow_origin === $parts['scheme'] . '://' . $parts['host'] ||
|
||||
$allow_origin === $parts['scheme'] . '://' . $parts['host'] . ':*' ||
|
||||
$allow_origin === '*:' . $parts['port'];
|
||||
if ($ok) {
|
||||
return call_user_func($fn, null, true, $req, $res);
|
||||
}
|
||||
}
|
||||
}
|
||||
call_user_func($fn, null, false, $req, $res);
|
||||
}
|
||||
|
||||
protected function prepare(object $req)
|
||||
{
|
||||
if (! isset($req->_query)) {
|
||||
$info = parse_url($req->url);
|
||||
if (isset($info['query'])) {
|
||||
parse_str($info['query'], $req->_query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handshake(string $transport, object $req)
|
||||
{
|
||||
$id = bin2hex(pack('d', microtime(true)) . pack('N', function_exists('random_int') ? random_int(1, 100000000) : rand(1, 100000000)));
|
||||
if ($transport == 'websocket') {
|
||||
$transport = '\\PHPSocketIO\\Engine\\Transports\\WebSocket';
|
||||
} elseif (isset($req->_query['j'])) {
|
||||
$transport = '\\PHPSocketIO\\Engine\\Transports\\PollingJsonp';
|
||||
} else {
|
||||
$transport = '\\PHPSocketIO\\Engine\\Transports\\PollingXHR';
|
||||
}
|
||||
|
||||
$transport = new $transport($req);
|
||||
|
||||
$transport->supportsBinary = ! isset($req->_query['b64']);
|
||||
|
||||
$socket = new Socket($id, $this, $transport, $req);
|
||||
|
||||
$transport->onRequest($req);
|
||||
|
||||
$this->clients[$id] = $socket;
|
||||
$socket->once('close', [$this, 'onSocketClose']);
|
||||
$this->emit('connection', $socket);
|
||||
}
|
||||
|
||||
public function onSocketClose($id): void
|
||||
{
|
||||
unset($this->clients[$id]);
|
||||
}
|
||||
|
||||
public function attach($worker): void
|
||||
{
|
||||
$this->server = $worker;
|
||||
$worker->onConnect = [$this, 'onConnect'];
|
||||
}
|
||||
|
||||
public function onConnect(object $connection): void
|
||||
{
|
||||
$connection->onRequest = [$this, 'handleRequest'];
|
||||
$connection->onWebSocketConnect = [$this, 'onWebSocketConnect'];
|
||||
// clean
|
||||
$connection->onClose = function ($connection) {
|
||||
if (! empty($connection->httpRequest)) {
|
||||
$connection->httpRequest->destroy();
|
||||
$connection->httpRequest = null;
|
||||
}
|
||||
if (! empty($connection->httpResponse)) {
|
||||
$connection->httpResponse->destroy();
|
||||
$connection->httpResponse = null;
|
||||
}
|
||||
if (! empty($connection->onRequest)) {
|
||||
$connection->onRequest = null;
|
||||
}
|
||||
if (! empty($connection->onWebSocketConnect)) {
|
||||
$connection->onWebSocketConnect = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function onWebSocketConnect($connection, object $req, object $res): void
|
||||
{
|
||||
$this->prepare($req);
|
||||
$this->verify($req, $res, true, [$this, 'dealWebSocketConnect']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dealWebSocketConnect($err, bool $success, object $req, object $res): void
|
||||
{
|
||||
if (! $success) {
|
||||
self::sendErrorMessage($req, $res, $err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($req->_query['sid'])) {
|
||||
if (! isset($this->clients[$req->_query['sid']])) {
|
||||
self::sendErrorMessage($req, $res, 'upgrade attempt for closed client');
|
||||
return;
|
||||
}
|
||||
$client = $this->clients[$req->_query['sid']];
|
||||
if ($client->upgrading) {
|
||||
self::sendErrorMessage($req, $res, 'transport has already been trying to upgrade');
|
||||
return;
|
||||
}
|
||||
if ($client->upgraded) {
|
||||
self::sendErrorMessage($req, $res, 'transport had already been upgraded');
|
||||
return;
|
||||
}
|
||||
$transport = new WebSocket($req);
|
||||
$client->maybeUpgrade($transport);
|
||||
} else {
|
||||
$this->handshake($req->_query['transport'], $req);
|
||||
}
|
||||
}
|
||||
}
|
||||
253
vendor/workerman/phpsocket.io/src/Engine/Parser.php
vendored
Normal file
253
vendor/workerman/phpsocket.io/src/Engine/Parser.php
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Parser
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
Debug::debug('Engine/Parser __construct');
|
||||
}
|
||||
|
||||
public static $packets = [
|
||||
'open' => 0, // non-ws
|
||||
'close' => 1, // non-ws
|
||||
'ping' => 2,
|
||||
'pong' => 3,
|
||||
'message' => 4,
|
||||
'upgrade' => 5,
|
||||
'noop' => 6,
|
||||
];
|
||||
|
||||
public static $packetsList = [
|
||||
'open',
|
||||
'close',
|
||||
'ping',
|
||||
'pong',
|
||||
'message',
|
||||
'upgrade',
|
||||
'noop'
|
||||
];
|
||||
|
||||
public static $err = [
|
||||
'type' => 'error',
|
||||
'data' => 'parser error'
|
||||
];
|
||||
|
||||
public static function encodePacket($packet): string
|
||||
{
|
||||
$data = ! isset($packet['data']) ? '' : $packet['data'];
|
||||
return self::$packets[$packet['type']] . $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a packet. Data also available as an ArrayBuffer if requested.
|
||||
*
|
||||
* @return array|string[] {Object} with `type` and `data` (if any)
|
||||
*/
|
||||
public static function decodePacket(string $data): array
|
||||
{
|
||||
if ($data[0] === 'b') {
|
||||
return self::decodeBase64Packet(substr($data, 1));
|
||||
}
|
||||
|
||||
$type = $data[0];
|
||||
if (! isset(self::$packetsList[$type])) {
|
||||
return self::$err;
|
||||
}
|
||||
|
||||
if (isset($data[1])) {
|
||||
return ['type' => self::$packetsList[$type], 'data' => substr($data, 1)];
|
||||
} else {
|
||||
return ['type' => self::$packetsList[$type]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a packet encoded in a base64 string.
|
||||
*
|
||||
* @param $msg
|
||||
* @return array {Object} with `type` and `data` (if any)
|
||||
*/
|
||||
public static function decodeBase64Packet($msg): array
|
||||
{
|
||||
$type = self::$packetsList[$msg[0]];
|
||||
$data = base64_decode(substr($msg, 1));
|
||||
return ['type' => $type, 'data' => $data];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload).
|
||||
*
|
||||
* <length>:data
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* 11:hello world2:hi
|
||||
*
|
||||
* If any contents are binary, they will be encoded as base64 strings. Base64
|
||||
* encoded strings are marked with a b before the length specifier
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api private
|
||||
*/
|
||||
public static function encodePayload($packets, $supportsBinary = null): string
|
||||
{
|
||||
if ($supportsBinary) {
|
||||
return self::encodePayloadAsBinary($packets);
|
||||
}
|
||||
|
||||
if (! $packets) {
|
||||
return '0:';
|
||||
}
|
||||
|
||||
$results = '';
|
||||
foreach ($packets as $msg) {
|
||||
$results .= self::encodeOne($msg);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public static function encodeOne($packet): string
|
||||
{
|
||||
$message = self::encodePacket($packet);
|
||||
return strlen($message) . ':' . $message;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected. Possible binary contents are
|
||||
* decoded from their base64 representation
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
public static function decodePayload($data, $binaryType = null)
|
||||
{
|
||||
if (! preg_match('/^\d+:\d/', $data)) {
|
||||
return self::decodePayloadAsBinary($data, $binaryType);
|
||||
}
|
||||
|
||||
if ($data === '') {
|
||||
// parser error - ignoring payload
|
||||
return self::$err;
|
||||
}
|
||||
|
||||
$length = '';//, n, msg;
|
||||
|
||||
for ($i = 0, $l = strlen($data); $i < $l; $i++) {
|
||||
$chr = $data[$i];
|
||||
|
||||
if (':' != $chr) {
|
||||
$length .= $chr;
|
||||
} else {
|
||||
if ('' == $length || ($length != ($n = intval($length)))) {
|
||||
// parser error - ignoring payload
|
||||
return self::$err;
|
||||
}
|
||||
|
||||
$msg = substr($data, $i + 1);
|
||||
|
||||
if (isset($msg[0])) {
|
||||
$packet = self::decodePacket($msg);
|
||||
|
||||
if (self::$err['type'] == $packet['type'] && self::$err['data'] == $packet['data']) {
|
||||
// parser error in individual packet - ignoring payload
|
||||
return self::$err;
|
||||
}
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
// advance cursor
|
||||
$i += $n;
|
||||
$length = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($length !== '') {
|
||||
// parser error - ignoring payload
|
||||
echo new Exception('parser error');
|
||||
return self::$err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload) as binary.
|
||||
*
|
||||
* <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
|
||||
* 255><data>
|
||||
*
|
||||
* Example:
|
||||
* 1 3 255 1 2 3, if the binary contents are interpreted as 8-bit integers
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @return string {Buffer} encoded payload
|
||||
* @api private
|
||||
*/
|
||||
public static function encodePayloadAsBinary($packets): string
|
||||
{
|
||||
$results = '';
|
||||
foreach ($packets as $msg) {
|
||||
$results .= self::encodeOneAsBinary($msg);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public static function encodeOneAsBinary($p): string
|
||||
{
|
||||
$packet = self::encodePacket($p);
|
||||
$encodingLength = '' . strlen($packet);
|
||||
$sizeBuffer = chr(0);
|
||||
for ($i = 0; $i < strlen($encodingLength); $i++) {
|
||||
$sizeBuffer .= chr($encodingLength[$i]);
|
||||
}
|
||||
$sizeBuffer .= chr(255);
|
||||
return $sizeBuffer . $packet;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected. Strings are decoded by
|
||||
* interpreting each byte as a key code for entries marked to start with 0. See
|
||||
* description of encodePayloadAsBinary
|
||||
* @api public
|
||||
*/
|
||||
public static function decodePayloadAsBinary($data, $binaryType = null): array
|
||||
{
|
||||
$bufferTail = $data;
|
||||
$buffers = [];
|
||||
|
||||
while (strlen($bufferTail) > 0) {
|
||||
$strLen = '';
|
||||
$numberTooLong = false;
|
||||
for ($i = 1;; $i++) {
|
||||
$tail = ord($bufferTail[$i]);
|
||||
if ($tail === 255) {
|
||||
break;
|
||||
}
|
||||
// 310 = char length of Number.MAX_VALUE
|
||||
if (strlen($strLen) > 310) {
|
||||
$numberTooLong = true;
|
||||
break;
|
||||
}
|
||||
$strLen .= $tail;
|
||||
}
|
||||
if ($numberTooLong) {
|
||||
return self::$err;
|
||||
}
|
||||
$bufferTail = substr($bufferTail, strlen($strLen) + 1);
|
||||
|
||||
$msgLength = intval($strLen);
|
||||
|
||||
$msg = substr($bufferTail, 1, $msgLength + 1);
|
||||
$buffers[] = $msg;
|
||||
$bufferTail = substr($bufferTail, $msgLength + 1);
|
||||
}
|
||||
$packets = [];
|
||||
foreach ($buffers as $i => $buffer) {
|
||||
$packets[] = self::decodePacket($buffer);
|
||||
}
|
||||
return $packets;
|
||||
}
|
||||
}
|
||||
58
vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Request.php
vendored
Normal file
58
vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Request.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Protocols\Http;
|
||||
|
||||
class Request
|
||||
{
|
||||
public $onData = null;
|
||||
|
||||
public $onEnd = null;
|
||||
|
||||
public $onClose = null;
|
||||
|
||||
public $httpVersion = null;
|
||||
|
||||
public $headers = [];
|
||||
|
||||
public $rawHeaders = null;
|
||||
|
||||
public $method = null;
|
||||
|
||||
public $url = null;
|
||||
|
||||
public $connection = null;
|
||||
|
||||
public $_query = null;
|
||||
|
||||
public $res = null;
|
||||
|
||||
public $cleanup = null;
|
||||
|
||||
public function __construct($connection, $raw_head)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->parseHead($raw_head);
|
||||
}
|
||||
|
||||
public function parseHead($raw_head)
|
||||
{
|
||||
$header_data = explode("\r\n", $raw_head);
|
||||
list($this->method, $this->url, $protocol) = explode(' ', $header_data[0]);
|
||||
list($null, $this->httpVersion) = explode('/', $protocol);
|
||||
unset($header_data[0]);
|
||||
foreach ($header_data as $content) {
|
||||
if (empty($content)) {
|
||||
continue;
|
||||
}
|
||||
$this->rawHeaders[] = $content;
|
||||
list($key, $value) = explode(':', $content, 2);
|
||||
$this->headers[strtolower($key)] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy()
|
||||
{
|
||||
$this->onData = $this->onEnd = $this->onClose = null;
|
||||
$this->connection = null;
|
||||
}
|
||||
}
|
||||
189
vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Response.php
vendored
Normal file
189
vendor/workerman/phpsocket.io/src/Engine/Protocols/Http/Response.php
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Protocols\Http;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Response
|
||||
{
|
||||
public $statusCode = 200;
|
||||
|
||||
protected $_statusPhrase = null;
|
||||
|
||||
protected $_connection = null;
|
||||
|
||||
protected $_headers = [];
|
||||
|
||||
public $headersSent = false;
|
||||
|
||||
public $writable = true;
|
||||
|
||||
protected $_buffer = '';
|
||||
|
||||
public function __construct($connection)
|
||||
{
|
||||
$this->_connection = $connection;
|
||||
}
|
||||
|
||||
protected function initHeader()
|
||||
{
|
||||
$this->_headers['Connection'] = 'keep-alive';
|
||||
$this->_headers['Content-Type'] = 'Content-Type: text/html;charset=utf-8';
|
||||
}
|
||||
|
||||
public function writeHead($status_code, $reason_phrase = '', $headers = null)
|
||||
{
|
||||
if ($this->headersSent) {
|
||||
echo "header has already send\n";
|
||||
return false;
|
||||
}
|
||||
$this->statusCode = $status_code;
|
||||
if ($reason_phrase) {
|
||||
$this->_statusPhrase = $reason_phrase;
|
||||
}
|
||||
if ($headers) {
|
||||
foreach ($headers as $key => $val) {
|
||||
$this->_headers[$key] = $val;
|
||||
}
|
||||
}
|
||||
$this->_buffer = $this->getHeadBuffer();
|
||||
$this->headersSent = true;
|
||||
}
|
||||
|
||||
public function getHeadBuffer(): string
|
||||
{
|
||||
if (! $this->_statusPhrase) {
|
||||
$this->_statusPhrase = self::$codes[$this->statusCode] ?? '';
|
||||
}
|
||||
$head_buffer = "HTTP/1.1 $this->statusCode $this->_statusPhrase\r\n";
|
||||
if (! isset($this->_headers['Content-Length']) && ! isset($this->_headers['Transfer-Encoding'])) {
|
||||
$head_buffer .= "Transfer-Encoding: chunked\r\n";
|
||||
}
|
||||
if (! isset($this->_headers['Connection'])) {
|
||||
$head_buffer .= "Connection: keep-alive\r\n";
|
||||
}
|
||||
foreach ($this->_headers as $key => $val) {
|
||||
if ($key === 'Set-Cookie' && is_array($val)) {
|
||||
foreach ($val as $v) {
|
||||
$head_buffer .= "Set-Cookie: $v\r\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$head_buffer .= "$key: $val\r\n";
|
||||
}
|
||||
return $head_buffer . "\r\n";
|
||||
}
|
||||
|
||||
public function setHeader($key, $val)
|
||||
{
|
||||
$this->_headers[$key] = $val;
|
||||
}
|
||||
|
||||
public function getHeader($name)
|
||||
{
|
||||
return $this->_headers[$name] ?? '';
|
||||
}
|
||||
|
||||
public function removeHeader($name)
|
||||
{
|
||||
unset($this->_headers[$name]);
|
||||
}
|
||||
|
||||
public function write($chunk)
|
||||
{
|
||||
if (! isset($this->_headers['Content-Length'])) {
|
||||
$chunk = dechex(strlen($chunk)) . "\r\n" . $chunk . "\r\n";
|
||||
}
|
||||
if (! $this->headersSent) {
|
||||
$head_buffer = $this->getHeadBuffer();
|
||||
$this->_buffer = $head_buffer . $chunk;
|
||||
$this->headersSent = true;
|
||||
} else {
|
||||
$this->_buffer .= $chunk;
|
||||
}
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if (! $this->writable) {
|
||||
echo new Exception('unwirtable');
|
||||
return false;
|
||||
}
|
||||
if ($data !== null) {
|
||||
$this->write($data);
|
||||
}
|
||||
|
||||
if (! $this->headersSent) {
|
||||
$head_buffer = $this->getHeadBuffer();
|
||||
$this->_buffer = $head_buffer;
|
||||
$this->headersSent = true;
|
||||
}
|
||||
|
||||
if (! isset($this->_headers['Content-Length'])) {
|
||||
$ret = $this->_connection->send($this->_buffer . "0\r\n\r\n", true);
|
||||
$this->destroy();
|
||||
return $ret;
|
||||
}
|
||||
$ret = $this->_connection->send($this->_buffer, true);
|
||||
$this->destroy();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function destroy()
|
||||
{
|
||||
if (! empty($this->_connection->httpRequest)) {
|
||||
$this->_connection->httpRequest->destroy();
|
||||
}
|
||||
if (! empty($this->_connection)) {
|
||||
$this->_connection->httpResponse = $this->_connection->httpRequest = null;
|
||||
}
|
||||
$this->_connection = null;
|
||||
$this->writable = false;
|
||||
}
|
||||
|
||||
public static $codes = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
306 => '(Unused)',
|
||||
307 => 'Temporary Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Request Entity Too Large',
|
||||
414 => 'Request-URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Requested Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
];
|
||||
}
|
||||
170
vendor/workerman/phpsocket.io/src/Engine/Protocols/SocketIO.php
vendored
Normal file
170
vendor/workerman/phpsocket.io/src/Engine/Protocols/SocketIO.php
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Protocols;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Engine\Protocols\Http\Request;
|
||||
use PHPSocketIO\Engine\Protocols\Http\Response;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
class SocketIO
|
||||
{
|
||||
public static function input($http_buffer, $connection)
|
||||
{
|
||||
if (! empty($connection->hasReadedHead)) {
|
||||
return strlen($http_buffer);
|
||||
}
|
||||
$pos = strpos($http_buffer, "\r\n\r\n");
|
||||
if (! $pos) {
|
||||
if (strlen($http_buffer) >= $connection->maxPackageSize) {
|
||||
$connection->close("HTTP/1.1 400 bad request\r\n\r\nheader too long");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
$head_len = $pos + 4;
|
||||
$raw_head = substr($http_buffer, 0, $head_len);
|
||||
$raw_body = substr($http_buffer, $head_len);
|
||||
$req = new Request($connection, $raw_head);
|
||||
$res = new Response($connection);
|
||||
$connection->httpRequest = $req;
|
||||
$connection->httpResponse = $res;
|
||||
$connection->hasReadedHead = true;
|
||||
TcpConnection::$statistics['total_request']++;
|
||||
$connection->onClose = '\PHPSocketIO\Engine\Protocols\SocketIO::emitClose';
|
||||
if (isset($req->headers['upgrade']) && strtolower($req->headers['upgrade']) === 'websocket') {
|
||||
$connection->consumeRecvBuffer(strlen($http_buffer));
|
||||
WebSocket::dealHandshake($connection, $req, $res);
|
||||
self::cleanup($connection);
|
||||
return 0;
|
||||
}
|
||||
if (! empty($connection->onRequest)) {
|
||||
$connection->consumeRecvBuffer(strlen($http_buffer));
|
||||
self::emitRequest($connection, $req, $res);
|
||||
if ($req->method === 'GET' || $req->method === 'OPTIONS') {
|
||||
self::emitEnd($connection, $req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// POST
|
||||
if ('\PHPSocketIO\Engine\Protocols\SocketIO::onData' !== $connection->onMessage) {
|
||||
$connection->onMessage = '\PHPSocketIO\Engine\Protocols\SocketIO::onData';
|
||||
}
|
||||
if (! $raw_body) {
|
||||
return 0;
|
||||
}
|
||||
self::onData($connection, $raw_body);
|
||||
return 0;
|
||||
} else {
|
||||
if ($req->method === 'GET') {
|
||||
return $pos + 4;
|
||||
} elseif (isset($req->headers['content-length'])) {
|
||||
return $req->headers['content-length'];
|
||||
} else {
|
||||
$connection->close("HTTP/1.1 400 bad request\r\n\r\ntrunk not support");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function onData($connection, $data)
|
||||
{
|
||||
$req = $connection->httpRequest;
|
||||
self::emitData($connection, $req, $data);
|
||||
if ((isset($req->headers['content-length']) && $req->headers['content-length'] <= strlen($data))
|
||||
|| substr($data, -5) === "0\r\n\r\n"
|
||||
) {
|
||||
self::emitEnd($connection, $req);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function emitRequest($connection, $req, $res)
|
||||
{
|
||||
try {
|
||||
call_user_func($connection->onRequest, $req, $res);
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function emitClose($connection)
|
||||
{
|
||||
$req = $connection->httpRequest;
|
||||
if (isset($req->onClose)) {
|
||||
try {
|
||||
call_user_func($req->onClose, $req);
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
}
|
||||
$res = $connection->httpResponse;
|
||||
if (isset($res->onClose)) {
|
||||
try {
|
||||
call_user_func($res->onClose, $res);
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
}
|
||||
self::cleanup($connection);
|
||||
}
|
||||
|
||||
public static function cleanup($connection)
|
||||
{
|
||||
if (! empty($connection->onRequest)) {
|
||||
$connection->onRequest = null;
|
||||
}
|
||||
if (! empty($connection->onWebSocketConnect)) {
|
||||
$connection->onWebSocketConnect = null;
|
||||
}
|
||||
if (! empty($connection->httpRequest)) {
|
||||
$connection->httpRequest->destroy();
|
||||
$connection->httpRequest = null;
|
||||
}
|
||||
if (! empty($connection->httpResponse)) {
|
||||
$connection->httpResponse->destroy();
|
||||
$connection->httpResponse = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function emitData($connection, $req, $data)
|
||||
{
|
||||
if (isset($req->onData)) {
|
||||
try {
|
||||
call_user_func($req->onData, $req, $data);
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function emitEnd($connection, $req)
|
||||
{
|
||||
if (isset($req->onEnd)) {
|
||||
try {
|
||||
call_user_func($req->onEnd, $req);
|
||||
} catch (Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
}
|
||||
$connection->hasReadedHead = false;
|
||||
}
|
||||
|
||||
public static function encode($buffer, $connection)
|
||||
{
|
||||
if (! isset($connection->onRequest)) {
|
||||
$connection->httpResponse->setHeader('Content-Length', strlen($buffer));
|
||||
return $connection->httpResponse->getHeadBuffer() . $buffer;
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
public static function decode($http_buffer, $connection)
|
||||
{
|
||||
if (isset($connection->onRequest)) {
|
||||
return $http_buffer;
|
||||
} else {
|
||||
list($head, $body) = explode("\r\n\r\n", $http_buffer, 2);
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket.php
vendored
Normal file
84
vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket.php
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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 PHPSocketIO\Engine\Protocols;
|
||||
|
||||
use PHPSocketIO\Engine\Protocols\Http\Request;
|
||||
use PHPSocketIO\Engine\Protocols\Http\Response;
|
||||
use PHPSocketIO\Engine\Protocols\WebSocket\RFC6455;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
/**
|
||||
* WebSocket 协议服务端解包和打包
|
||||
*/
|
||||
class WebSocket
|
||||
{
|
||||
/**
|
||||
* 最小包头
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MIN_HEAD_LEN = 7;
|
||||
|
||||
/**
|
||||
* 检查包的完整性
|
||||
*
|
||||
* @param string $buffer
|
||||
*/
|
||||
public static function input($buffer, $connection)
|
||||
{
|
||||
if (strlen($buffer) < self::MIN_HEAD_LEN) {
|
||||
return 0;
|
||||
}
|
||||
// flash policy file
|
||||
if (0 === strpos($buffer, '<policy')) {
|
||||
$policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . "\0";
|
||||
$connection->send($policy_xml, true);
|
||||
$connection->consumeRecvBuffer(strlen($buffer));
|
||||
return 0;
|
||||
}
|
||||
// http head
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if (! $pos) {
|
||||
if (strlen($buffer) >= TcpConnection::$maxPackageSize) {
|
||||
$connection->close("HTTP/1.1 400 bad request\r\n\r\nheader too long");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
$req = new Request($connection, $buffer);
|
||||
$res = new Response($connection);
|
||||
$connection->consumeRecvBuffer(strlen($buffer));
|
||||
return self::dealHandshake($connection, $req, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理websocket握手
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
* @param $req
|
||||
* @param $res
|
||||
* @return int
|
||||
*/
|
||||
public static function dealHandshake($connection, $req, $res)
|
||||
{
|
||||
if (isset($req->headers['sec-websocket-key1'])) {
|
||||
$res->writeHead(400);
|
||||
$res->end("Not support");
|
||||
return 0;
|
||||
}
|
||||
$connection->protocol = 'PHPSocketIO\Engine\Protocols\WebSocket\RFC6455';
|
||||
return RFC6455::dealHandshake($connection, $req, $res);
|
||||
}
|
||||
}
|
||||
300
vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket/RFC6455.php
vendored
Normal file
300
vendor/workerman/phpsocket.io/src/Engine/Protocols/WebSocket/RFC6455.php
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
<?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 PHPSocketIO\Engine\Protocols\WebSocket;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\ProtocolInterface;
|
||||
|
||||
/**
|
||||
* WebSocket 协议服务端解包和打包
|
||||
*/
|
||||
class RFC6455 implements ProtocolInterface
|
||||
{
|
||||
/**
|
||||
* websocket头部最小长度
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MIN_HEAD_LEN = 6;
|
||||
|
||||
/**
|
||||
* websocket blob类型
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_BLOB = "\x81";
|
||||
|
||||
/**
|
||||
* websocket arraybuffer类型
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_ARRAYBUFFER = "\x82";
|
||||
|
||||
/**
|
||||
* 检查包的完整性
|
||||
*
|
||||
* @param string $buffer
|
||||
*/
|
||||
public static function input($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
// 数据长度
|
||||
$recv_len = strlen($buffer);
|
||||
// 长度不够
|
||||
if ($recv_len < self::MIN_HEAD_LEN) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// $connection->websocketCurrentFrameLength有值说明当前fin为0,则缓冲websocket帧数据
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
// 如果当前帧数据未收全,则继续收
|
||||
if ($connection->websocketCurrentFrameLength > $recv_len) {
|
||||
// 返回0,因为不清楚完整的数据包长度,需要等待fin=1的帧
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$data_len = ord($buffer[1]) & 127;
|
||||
$firstbyte = ord($buffer[0]);
|
||||
$is_fin_frame = $firstbyte >> 7;
|
||||
$opcode = $firstbyte & 0xf;
|
||||
switch ($opcode) {
|
||||
// 附加数据帧 @todo 实现附加数据帧
|
||||
case 0x1:
|
||||
case 0x2:
|
||||
case 0x0:
|
||||
break;
|
||||
// 文本数据帧
|
||||
// 二进制数据帧
|
||||
// 关闭的包
|
||||
case 0x8:
|
||||
// 如果有设置onWebSocketClose回调,尝试执行
|
||||
if (isset($connection->onWebSocketClose)) {
|
||||
call_user_func($connection->onWebSocketClose, $connection);
|
||||
} // 默认行为是关闭连接
|
||||
else {
|
||||
$connection->close();
|
||||
}
|
||||
return 0;
|
||||
// ping的包
|
||||
case 0x9:
|
||||
// 如果有设置onWebSocketPing回调,尝试执行
|
||||
if (isset($connection->onWebSocketPing)) {
|
||||
call_user_func($connection->onWebSocketPing, $connection);
|
||||
} // 默认发送pong
|
||||
else {
|
||||
$connection->send(pack('H*', '8a00'), true);
|
||||
}
|
||||
// 从接受缓冲区中消费掉该数据包
|
||||
if (! $data_len) {
|
||||
$connection->consumeRecvBuffer(self::MIN_HEAD_LEN);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
// pong的包
|
||||
case 0xa:
|
||||
// 如果有设置onWebSocketPong回调,尝试执行
|
||||
if (isset($connection->onWebSocketPong)) {
|
||||
call_user_func($connection->onWebSocketPong, $connection);
|
||||
}
|
||||
// 从接受缓冲区中消费掉该数据包
|
||||
if (! $data_len) {
|
||||
$connection->consumeRecvBuffer(self::MIN_HEAD_LEN);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
// 错误的opcode
|
||||
default:
|
||||
echo "error opcode $opcode and close websocket connection\n";
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// websocket二进制数据
|
||||
$head_len = self::MIN_HEAD_LEN;
|
||||
if ($data_len === 126) {
|
||||
$head_len = 8;
|
||||
if ($head_len > $recv_len) {
|
||||
return 0;
|
||||
}
|
||||
$pack = unpack('ntotal_len', substr($buffer, 2, 2));
|
||||
$data_len = $pack['total_len'];
|
||||
} elseif ($data_len === 127) {
|
||||
$head_len = 14;
|
||||
if ($head_len > $recv_len) {
|
||||
return 0;
|
||||
}
|
||||
$arr = unpack('N2', substr($buffer, 2, 8));
|
||||
$data_len = $arr[1] * 4294967296 + $arr[2];
|
||||
}
|
||||
$current_frame_length = $head_len + $data_len;
|
||||
if ($is_fin_frame) {
|
||||
return $current_frame_length;
|
||||
} else {
|
||||
$connection->websocketCurrentFrameLength = $current_frame_length;
|
||||
}
|
||||
}
|
||||
|
||||
// 收到的数据刚好是一个frame
|
||||
if ($connection->websocketCurrentFrameLength == $recv_len) {
|
||||
self::decode($buffer, $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
return 0;
|
||||
} // 收到的数据大于一个frame
|
||||
elseif ($connection->websocketCurrentFrameLength < $recv_len) {
|
||||
self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$current_frame_length = $connection->websocketCurrentFrameLength;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
// 继续读取下一个frame
|
||||
return self::input(substr($buffer, $current_frame_length), $connection);
|
||||
} // 收到的数据不足一个frame
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打包
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
$len = strlen($buffer);
|
||||
if (empty($connection->websocketHandshake)) {
|
||||
// 默认是utf8文本格式
|
||||
$connection->websocketType = self::BINARY_TYPE_BLOB;
|
||||
}
|
||||
|
||||
$first_byte = $connection->websocketType;
|
||||
|
||||
if ($len <= 125) {
|
||||
$encode_buffer = $first_byte . chr($len) . $buffer;
|
||||
} elseif ($len <= 65535) {
|
||||
$encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer;
|
||||
} else {
|
||||
$encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer;
|
||||
}
|
||||
|
||||
// 还没握手不能发数据,先将数据缓冲起来,等握手完毕后发送
|
||||
if (empty($connection->websocketHandshake)) {
|
||||
if (empty($connection->websocketTmpData)) {
|
||||
// 临时数据缓冲
|
||||
$connection->websocketTmpData = '';
|
||||
}
|
||||
$connection->websocketTmpData .= $encode_buffer;
|
||||
// 返回空,阻止发送
|
||||
return '';
|
||||
}
|
||||
|
||||
return $encode_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
$masks = $data = $decoded = null;
|
||||
$len = ord($buffer[1]) & 127;
|
||||
if ($len === 126) {
|
||||
$masks = substr($buffer, 4, 4);
|
||||
$data = substr($buffer, 8);
|
||||
} elseif ($len === 127) {
|
||||
$masks = substr($buffer, 10, 4);
|
||||
$data = substr($buffer, 14);
|
||||
} else {
|
||||
$masks = substr($buffer, 2, 4);
|
||||
$data = substr($buffer, 6);
|
||||
}
|
||||
for ($index = 0; $index < strlen($data); $index++) {
|
||||
$decoded .= $data[$index] ^ $masks[$index % 4];
|
||||
}
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
$connection->websocketDataBuffer .= $decoded;
|
||||
return $connection->websocketDataBuffer;
|
||||
} else {
|
||||
$decoded = $connection->websocketDataBuffer . $decoded;
|
||||
$connection->websocketDataBuffer = '';
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理websocket握手
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
* @param $req
|
||||
* @param $res
|
||||
* @return int
|
||||
*/
|
||||
public static function dealHandshake($connection, $req, $res)
|
||||
{
|
||||
$headers = [];
|
||||
if (isset($connection->onWebSocketConnect)) {
|
||||
try {
|
||||
call_user_func_array($connection->onWebSocketConnect, [$connection, $req, $res]);
|
||||
} catch (\Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
if (! $res->writable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($req->headers['sec-websocket-key'])) {
|
||||
$sec_websocket_key = $req->headers['sec-websocket-key'];
|
||||
} else {
|
||||
$res->writeHead(400);
|
||||
$res->end('<b>400 Bad Request</b><br>Upgrade to websocket but Sec-WebSocket-Key not found.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 标记已经握手
|
||||
$connection->websocketHandshake = true;
|
||||
// 缓冲fin为0的包,直到fin为1
|
||||
$connection->websocketDataBuffer = '';
|
||||
// 当前数据帧的长度,可能是fin为0的帧,也可能是fin为1的帧
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
// 当前帧的数据缓冲
|
||||
$connection->websocketCurrentFrameBuffer = '';
|
||||
// blob or arraybuffer
|
||||
$connection->websocketType = self::BINARY_TYPE_BLOB;
|
||||
|
||||
$sec_websocket_accept = base64_encode(sha1($sec_websocket_key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
|
||||
$headers['Content-Length'] = 0;
|
||||
$headers['Upgrade'] = 'websocket';
|
||||
$headers['Sec-WebSocket-Version'] = 13;
|
||||
$headers['Connection'] = 'Upgrade';
|
||||
$headers['Sec-WebSocket-Accept'] = $sec_websocket_accept;
|
||||
$res->writeHead(101, '', $headers);
|
||||
$res->end();
|
||||
|
||||
// 握手后有数据要发送
|
||||
if (! empty($connection->websocketTmpData)) {
|
||||
$connection->send($connection->websocketTmpData, true);
|
||||
$connection->websocketTmpData = '';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
360
vendor/workerman/phpsocket.io/src/Engine/Socket.php
vendored
Normal file
360
vendor/workerman/phpsocket.io/src/Engine/Socket.php
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine;
|
||||
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use Workerman\Timer;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Socket extends Emitter
|
||||
{
|
||||
public $id = 0;
|
||||
public $server = null;
|
||||
public $upgrading = false;
|
||||
public $upgraded = false;
|
||||
public $readyState = 'opening';
|
||||
public $writeBuffer = [];
|
||||
public $packetsFn = [];
|
||||
public $sentCallbackFn = [];
|
||||
public $request = null;
|
||||
public $remoteAddress = '';
|
||||
public $checkIntervalTimer;
|
||||
public $upgradeTimeoutTimer = null;
|
||||
public $pingTimeoutTimer = null;
|
||||
public $upgradeTransport = null;
|
||||
public $transport = null;
|
||||
|
||||
public function __construct($id, $server, $transport, $req)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->server = $server;
|
||||
$this->request = $req;
|
||||
$this->remoteAddress = $req->connection->getRemoteIp() . ':' . $req->connection->getRemotePort();
|
||||
$this->setTransport($transport);
|
||||
$this->onOpen();
|
||||
Debug::debug('Engine/Socket __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Engine/Socket __destruct');
|
||||
}
|
||||
|
||||
public function maybeUpgrade(object $transport): void
|
||||
{
|
||||
$this->upgrading = true;
|
||||
$this->upgradeTimeoutTimer = Timer::add(
|
||||
$this->server->upgradeTimeout,
|
||||
[$this, 'upgradeTimeoutCallback'],
|
||||
[$transport],
|
||||
false
|
||||
);
|
||||
$this->upgradeTransport = $transport;
|
||||
$transport->on('packet', [$this, 'onUpgradePacket']);
|
||||
$transport->once('close', [$this, 'onUpgradeTransportClose']);
|
||||
$transport->once('error', [$this, 'onUpgradeTransportError']);
|
||||
$this->once('close', [$this, 'onUpgradeTransportClose']);
|
||||
}
|
||||
|
||||
public function onUpgradePacket(array $packet): void
|
||||
{
|
||||
if (empty($this->upgradeTransport)) {
|
||||
$this->onError('upgradeTransport empty');
|
||||
return;
|
||||
}
|
||||
if ('ping' === $packet['type'] && (isset($packet['data']) && 'probe' === $packet['data'])) {
|
||||
$this->upgradeTransport->send([['type' => 'pong', 'data' => 'probe']]);
|
||||
if ($this->checkIntervalTimer) {
|
||||
Timer::del($this->checkIntervalTimer);
|
||||
}
|
||||
$this->checkIntervalTimer = Timer::add(0.5, [$this, 'check']);
|
||||
} elseif ('upgrade' === $packet['type'] && $this->readyState !== 'closed') {
|
||||
$this->upgradeCleanup();
|
||||
$this->upgraded = true;
|
||||
$this->clearTransport();
|
||||
$this->transport->destroy();
|
||||
$this->setTransport($this->upgradeTransport);
|
||||
$this->emit('upgrade', $this->upgradeTransport);
|
||||
$this->upgradeTransport = null;
|
||||
$this->setPingTimeout();
|
||||
$this->flush();
|
||||
if ($this->readyState === 'closing') {
|
||||
$this->transport->close([$this, 'onClose']);
|
||||
}
|
||||
} else {
|
||||
$this->upgradeCleanup();
|
||||
$this->upgradeTransport->close();
|
||||
$this->upgradeTransport = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function upgradeCleanup(): void
|
||||
{
|
||||
$this->upgrading = false;
|
||||
Timer::del($this->checkIntervalTimer);
|
||||
Timer::del($this->upgradeTimeoutTimer);
|
||||
if (! empty($this->upgradeTransport)) {
|
||||
$this->upgradeTransport->removeListener('packet', [$this, 'onUpgradePacket']);
|
||||
$this->upgradeTransport->removeListener('close', [$this, 'onUpgradeTransportClose']);
|
||||
$this->upgradeTransport->removeListener('error', [$this, 'onUpgradeTransportError']);
|
||||
}
|
||||
$this->removeListener('close', [$this, 'onUpgradeTransportClose']);
|
||||
}
|
||||
|
||||
public function onUpgradeTransportClose(): void
|
||||
{
|
||||
$this->onUpgradeTransportError('transport closed');
|
||||
}
|
||||
|
||||
public function onUpgradeTransportError($err): void
|
||||
{
|
||||
$this->upgradeCleanup();
|
||||
if ($this->upgradeTransport) {
|
||||
$this->upgradeTransport->close();
|
||||
$this->upgradeTransport = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function upgradeTimeoutCallback(object $transport): void
|
||||
{
|
||||
$this->upgradeCleanup();
|
||||
if ('open' === $transport->readyState) {
|
||||
$transport->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function setTransport(object $transport)
|
||||
{
|
||||
$this->transport = $transport;
|
||||
$this->transport->once('error', [$this, 'onError']);
|
||||
$this->transport->on('packet', [$this, 'onPacket']);
|
||||
$this->transport->on('drain', [$this, 'flush']);
|
||||
$this->transport->once('close', [$this, 'onClose']);
|
||||
//this function will manage packet events (also message callbacks)
|
||||
$this->setupSendCallback();
|
||||
}
|
||||
|
||||
public function onOpen(): void
|
||||
{
|
||||
$this->readyState = 'open';
|
||||
|
||||
$this->transport->sid = $this->id;
|
||||
$this->sendPacket(
|
||||
'open',
|
||||
json_encode(
|
||||
[
|
||||
'sid' => $this->id,
|
||||
'upgrades' => $this->getAvailableUpgrades(),
|
||||
'pingInterval' => $this->server->pingInterval * 1000,
|
||||
'pingTimeout' => $this->server->pingTimeout * 1000
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$this->emit('open');
|
||||
$this->setPingTimeout();
|
||||
}
|
||||
|
||||
public function onPacket(array $packet)
|
||||
{
|
||||
if ('open' === $this->readyState) {
|
||||
// export packet event
|
||||
$this->emit('packet', $packet);
|
||||
|
||||
// Reset ping timeout on any packet, incoming data is a good sign of
|
||||
// other side's liveness
|
||||
$this->setPingTimeout();
|
||||
switch ($packet['type']) {
|
||||
case 'ping':
|
||||
$this->sendPacket('pong');
|
||||
$this->emit('heartbeat');
|
||||
break;
|
||||
case 'error':
|
||||
$this->onClose('parse error');
|
||||
break;
|
||||
case 'message':
|
||||
$this->emit('data', $packet['data']);
|
||||
$this->emit('message', $packet['data']);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
echo('packet received with closed socket');
|
||||
}
|
||||
}
|
||||
|
||||
public function check(): void
|
||||
{
|
||||
if ('polling' == $this->transport->name && $this->transport->writable) {
|
||||
$this->transport->send([['type' => 'noop']]);
|
||||
}
|
||||
}
|
||||
|
||||
public function onError($err): void
|
||||
{
|
||||
$this->onClose('transport error', $err);
|
||||
}
|
||||
|
||||
public function setPingTimeout(): void
|
||||
{
|
||||
if ($this->pingTimeoutTimer) {
|
||||
Timer::del($this->pingTimeoutTimer);
|
||||
}
|
||||
$this->pingTimeoutTimer = Timer::add(
|
||||
$this->server->pingInterval + $this->server->pingTimeout,
|
||||
[$this, 'pingTimeoutCallback'],
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public function pingTimeoutCallback(): void
|
||||
{
|
||||
$this->transport->close();
|
||||
$this->onClose('ping timeout');
|
||||
}
|
||||
|
||||
public function clearTransport(): void
|
||||
{
|
||||
$this->transport->close();
|
||||
Timer::del($this->pingTimeoutTimer);
|
||||
}
|
||||
|
||||
public function onClose(string $reason = '', ?string $description = null): void
|
||||
{
|
||||
if ('closed' !== $this->readyState) {
|
||||
Timer::del($this->pingTimeoutTimer);
|
||||
|
||||
if (! empty($this->checkIntervalTimer)) {
|
||||
Timer::del($this->checkIntervalTimer);
|
||||
}
|
||||
|
||||
$this->checkIntervalTimer = null;
|
||||
|
||||
if (! empty($this->checkIntervalTimer)) {
|
||||
Timer::del($this->upgradeTimeoutTimer);
|
||||
}
|
||||
|
||||
// clean writeBuffer in next tick, so developers can still
|
||||
// grab the writeBuffer on 'close' event
|
||||
$this->writeBuffer = [];
|
||||
$this->packetsFn = [];
|
||||
$this->sentCallbackFn = [];
|
||||
$this->clearTransport();
|
||||
$this->readyState = 'closed';
|
||||
$this->emit('close', $this->id, $reason, $description);
|
||||
$this->server = null;
|
||||
$this->request = null;
|
||||
$this->upgradeTransport = null;
|
||||
$this->removeAllListeners();
|
||||
if (! empty($this->transport)) {
|
||||
$this->transport->removeAllListeners();
|
||||
$this->transport = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function send($data, $options, ?callable $callback): Socket
|
||||
{
|
||||
$this->sendPacket('message', $data, $callback);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function write($data, ?array $options = [], ?callable $callback = null): Socket
|
||||
{
|
||||
return $this->send($data, $options, $callback);
|
||||
}
|
||||
|
||||
public function sendPacket(string $type, $data = null, $callback = null): void
|
||||
{
|
||||
if ('closing' !== $this->readyState) {
|
||||
$packet = [
|
||||
'type' => $type
|
||||
];
|
||||
if ($data !== null) {
|
||||
$packet['data'] = $data;
|
||||
}
|
||||
// exports packetCreate event
|
||||
$this->emit('packetCreate', $packet);
|
||||
$this->writeBuffer[] = $packet;
|
||||
//add send callback to object
|
||||
if ($callback) {
|
||||
$this->packetsFn[] = $callback;
|
||||
}
|
||||
$this->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
{
|
||||
if ('closed' !== $this->readyState && $this->transport->writable
|
||||
&& $this->writeBuffer
|
||||
) {
|
||||
$this->emit('flush', $this->writeBuffer);
|
||||
$this->server->emit('flush', $this, $this->writeBuffer);
|
||||
$wbuf = $this->writeBuffer;
|
||||
$this->writeBuffer = [];
|
||||
if ($this->packetsFn) {
|
||||
if (! empty($this->transport->supportsFraming)) {
|
||||
$this->sentCallbackFn[] = $this->packetsFn;
|
||||
} else {
|
||||
// @todo check
|
||||
$this->sentCallbackFn[] = $this->packetsFn;
|
||||
}
|
||||
}
|
||||
$this->packetsFn = [];
|
||||
$this->transport->send($wbuf);
|
||||
$this->emit('drain');
|
||||
if ($this->server) {
|
||||
$this->server->emit('drain', $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvailableUpgrades(): array
|
||||
{
|
||||
return ['websocket'];
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
if ('open' !== $this->readyState) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->readyState = 'closing';
|
||||
|
||||
if ($this->writeBuffer) {
|
||||
$this->once('drain', [$this, 'closeTransport']);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closeTransport();
|
||||
}
|
||||
|
||||
public function closeTransport(): void
|
||||
{
|
||||
$this->transport->close([$this, 'onClose']);
|
||||
}
|
||||
|
||||
public function setupSendCallback(): void
|
||||
{
|
||||
//the message was sent successfully, execute the callback
|
||||
$this->transport->on('drain', [$this, 'onDrainCallback']);
|
||||
}
|
||||
|
||||
public function onDrainCallback(): void
|
||||
{
|
||||
if ($this->sentCallbackFn) {
|
||||
$seqFn = array_shift($this->sentCallbackFn);
|
||||
if (is_callable($seqFn)) {
|
||||
echo('executing send callback');
|
||||
call_user_func($seqFn, $this->transport);
|
||||
} elseif (is_array($seqFn)) {
|
||||
echo('executing batch send callback');
|
||||
foreach ($seqFn as $fn) {
|
||||
call_user_func($fn, $this->transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
76
vendor/workerman/phpsocket.io/src/Engine/Transport.php
vendored
Normal file
76
vendor/workerman/phpsocket.io/src/Engine/Transport.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine;
|
||||
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Transport extends Emitter
|
||||
{
|
||||
public $readyState = 'opening';
|
||||
public $req = null;
|
||||
public $res = null;
|
||||
public $shouldClose = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Debug::debug('Transport __construct no access !!!!');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Transport __destruct');
|
||||
}
|
||||
|
||||
public function noop()
|
||||
{
|
||||
}
|
||||
|
||||
public function onRequest($req)
|
||||
{
|
||||
$this->req = $req;
|
||||
}
|
||||
|
||||
public function close(?callable $fn = null): void
|
||||
{
|
||||
$this->readyState = 'closing';
|
||||
$fn = $fn ?: [$this, 'noop'];
|
||||
$this->doClose($fn);
|
||||
}
|
||||
|
||||
public function onError(string $msg, ?string $desc = '')
|
||||
{
|
||||
if ($this->listeners('error')) {
|
||||
$this->emit('error', "TransportError: {$desc}");
|
||||
} else {
|
||||
echo("ignored transport error $msg $desc\n");
|
||||
}
|
||||
}
|
||||
|
||||
public function onPacket($packet): void
|
||||
{
|
||||
$this->emit('packet', $packet);
|
||||
}
|
||||
|
||||
public function onData($data)
|
||||
{
|
||||
$this->onPacket(Parser::decodePacket($data));
|
||||
}
|
||||
|
||||
public function onClose()
|
||||
{
|
||||
$this->req = $this->res = null;
|
||||
$this->readyState = 'closed';
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function destroy(): void
|
||||
{
|
||||
$this->req = null;
|
||||
$this->res = null;
|
||||
$this->readyState = 'closed';
|
||||
$this->removeAllListeners();
|
||||
$this->shouldClose = null;
|
||||
}
|
||||
}
|
||||
176
vendor/workerman/phpsocket.io/src/Engine/Transports/Polling.php
vendored
Normal file
176
vendor/workerman/phpsocket.io/src/Engine/Transports/Polling.php
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Transports;
|
||||
|
||||
use PHPSocketIO\Engine\Transport;
|
||||
use PHPSocketIO\Engine\Parser;
|
||||
|
||||
class Polling extends Transport
|
||||
{
|
||||
public $name = 'polling';
|
||||
public $chunks = '';
|
||||
public $shouldClose = null;
|
||||
public $writable = false;
|
||||
public $supportsBinary = null;
|
||||
public $dataRes = null;
|
||||
public $dataReq = null;
|
||||
|
||||
public function onRequest($req)
|
||||
{
|
||||
$res = $req->res;
|
||||
|
||||
if ('GET' === $req->method) {
|
||||
$this->onPollRequest($req, $res);
|
||||
} elseif ('POST' === $req->method) {
|
||||
$this->onDataRequest($req, $res);
|
||||
} else {
|
||||
$res->writeHead(500);
|
||||
$res->end();
|
||||
}
|
||||
}
|
||||
|
||||
public function onPollRequest(object $req, object $res): void
|
||||
{
|
||||
if ($this->req) {
|
||||
$this->onError('overlap from client');
|
||||
$res->writeHead(500);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->req = $req;
|
||||
$this->res = $res;
|
||||
|
||||
$req->onClose = [$this, 'pollRequestOnClose'];
|
||||
$req->cleanup = [$this, 'pollRequestClean'];
|
||||
|
||||
$this->writable = true;
|
||||
$this->emit('drain');
|
||||
|
||||
if ($this->writable && $this->shouldClose) {
|
||||
echo('triggering empty send to append close packet');
|
||||
$this->send([['type' => 'noop']]);
|
||||
}
|
||||
}
|
||||
|
||||
public function pollRequestOnClose(): void
|
||||
{
|
||||
$this->onError('poll connection closed prematurely');
|
||||
$this->pollRequestClean();
|
||||
}
|
||||
|
||||
public function pollRequestClean(): void
|
||||
{
|
||||
if (isset($this->req)) {
|
||||
$this->req = null;
|
||||
$this->res = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function onDataRequest($req, $res): void
|
||||
{
|
||||
if (isset($this->dataReq)) {
|
||||
$this->onError('data request overlap from client');
|
||||
$res->writeHead(500);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dataReq = $req;
|
||||
$this->dataRes = $res;
|
||||
$req->onClose = [$this, 'dataRequestOnClose'];
|
||||
$req->onData = [$this, 'dataRequestOnData'];
|
||||
$req->onEnd = [$this, 'dataRequestOnEnd'];
|
||||
}
|
||||
|
||||
public function dataRequestCleanup(): void
|
||||
{
|
||||
$this->chunks = '';
|
||||
$this->dataReq = null;
|
||||
$this->dataRes = null;
|
||||
}
|
||||
|
||||
public function dataRequestOnClose(): void
|
||||
{
|
||||
$this->dataRequestCleanup();
|
||||
$this->onError('data request connection closed prematurely');
|
||||
}
|
||||
|
||||
public function dataRequestOnData($req, $data): void
|
||||
{
|
||||
$this->chunks .= $data;
|
||||
}
|
||||
|
||||
public function dataRequestOnEnd(): void
|
||||
{
|
||||
$this->onData($this->chunks);
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'text/html',
|
||||
'Content-Length' => 2,
|
||||
'X-XSS-Protection' => '0',
|
||||
];
|
||||
|
||||
$this->dataRes->writeHead(200, '', $this->headers($this->dataReq, $headers));
|
||||
$this->dataRes->end('ok');
|
||||
$this->dataRequestCleanup();
|
||||
}
|
||||
|
||||
public function onData($data)
|
||||
{
|
||||
$packets = Parser::decodePayload($data);
|
||||
if (isset($packets['type'])) {
|
||||
if ('close' === $packets['type']) {
|
||||
$this->onClose();
|
||||
return false;
|
||||
} else {
|
||||
$packets = [$packets];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($packets as $packet) {
|
||||
$this->onPacket($packet);
|
||||
}
|
||||
}
|
||||
|
||||
public function onClose()
|
||||
{
|
||||
if ($this->writable) {
|
||||
$this->send([['type' => 'noop']]);
|
||||
}
|
||||
parent::onClose();
|
||||
}
|
||||
|
||||
public function send($packets): void
|
||||
{
|
||||
$this->writable = false;
|
||||
if ($this->shouldClose) {
|
||||
echo('appending close packet to payload');
|
||||
$packets[] = ['type' => 'close'];
|
||||
call_user_func($this->shouldClose);
|
||||
$this->shouldClose = null;
|
||||
}
|
||||
$data = Parser::encodePayload($packets, $this->supportsBinary);
|
||||
$this->write($data);
|
||||
}
|
||||
|
||||
public function write($data): void
|
||||
{
|
||||
$this->doWrite($data);
|
||||
if (! empty($this->req->cleanup)) {
|
||||
call_user_func($this->req->cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
public function doClose(callable $fn): void
|
||||
{
|
||||
if (! empty($this->dataReq)) {
|
||||
$this->dataReq->destroy();
|
||||
}
|
||||
|
||||
if ($this->writable) {
|
||||
$this->send([['type' => 'close']]);
|
||||
call_user_func($fn);
|
||||
} else {
|
||||
$this->shouldClose = $fn;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
vendor/workerman/phpsocket.io/src/Engine/Transports/PollingJsonp.php
vendored
Normal file
60
vendor/workerman/phpsocket.io/src/Engine/Transports/PollingJsonp.php
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Transports;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class PollingJsonp extends Polling
|
||||
{
|
||||
public $head = null;
|
||||
public $foot = ');';
|
||||
|
||||
public function __construct($req)
|
||||
{
|
||||
$this->head = '___eio[' . (isset($req['_query']['j']) ? preg_replace('/[^0-9]/', '', $req['_query']['j']) : '') . '](';
|
||||
Debug::debug('PollingJsonp __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('PollingJsonp __destruct');
|
||||
}
|
||||
|
||||
public function onData($data)
|
||||
{
|
||||
$parsed_data = null;
|
||||
parse_str($data, $parsed_data);
|
||||
$data = $parsed_data['d'];
|
||||
call_user_func(array(get_parent_class($this), 'onData'), preg_replace('/\\\\n/', '\\n', $data));
|
||||
}
|
||||
|
||||
public function doWrite($data): void
|
||||
{
|
||||
$js = json_encode($data);
|
||||
|
||||
$data = $this->head . $js . $this->foot;
|
||||
|
||||
// explicit UTF-8 is required for pages not served under utf
|
||||
$headers = [
|
||||
'Content-Type' => 'text/javascript; charset=UTF-8',
|
||||
'Content-Length' => strlen($data),
|
||||
'X-XSS-Protection' => '0'
|
||||
];
|
||||
if (empty($this->res)) {
|
||||
echo new Exception('empty $this->res');
|
||||
return;
|
||||
}
|
||||
$this->res->writeHead(200, '', $this->headers($headers));
|
||||
$this->res->end($data);
|
||||
}
|
||||
|
||||
public function headers(array $headers = []): array
|
||||
{
|
||||
$listeners = $this->listeners('headers');
|
||||
foreach ($listeners as $listener) {
|
||||
$listener($headers);
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
66
vendor/workerman/phpsocket.io/src/Engine/Transports/PollingXHR.php
vendored
Normal file
66
vendor/workerman/phpsocket.io/src/Engine/Transports/PollingXHR.php
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Transports;
|
||||
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class PollingXHR extends Polling
|
||||
{
|
||||
public $sid = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Debug::debug('PollingXHR __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('PollingXHR __destruct');
|
||||
}
|
||||
|
||||
public function onRequest($req)
|
||||
{
|
||||
if ('OPTIONS' === $req->method) {
|
||||
$res = $req->res;
|
||||
$headers = $this->headers($req);
|
||||
$headers['Access-Control-Allow-Headers'] = 'Content-Type';
|
||||
$res->writeHead(200, '', $headers);
|
||||
$res->end();
|
||||
} else {
|
||||
parent::onRequest($req);
|
||||
}
|
||||
}
|
||||
|
||||
public function doWrite($data)
|
||||
{
|
||||
// explicit UTF-8 is required for pages not served under utf todo
|
||||
$content_type = preg_match('/^\d+:/', $data) ? 'text/plain; charset=UTF-8' : 'application/octet-stream';
|
||||
$content_length = strlen($data);
|
||||
$headers = [
|
||||
'Content-Type' => $content_type,
|
||||
'Content-Length' => $content_length,
|
||||
'X-XSS-Protection' => '0',
|
||||
];
|
||||
if (empty($this->res)) {
|
||||
echo new \Exception('empty this->res');
|
||||
return;
|
||||
}
|
||||
$this->res->writeHead(200, '', $this->headers($this->req, $headers));
|
||||
$this->res->end($data);
|
||||
}
|
||||
|
||||
public function headers(object $req, ?array $headers = []): array
|
||||
{
|
||||
if (isset($req->headers['origin'])) {
|
||||
$headers['Access-Control-Allow-Credentials'] = 'true';
|
||||
$headers['Access-Control-Allow-Origin'] = $req->headers['origin'];
|
||||
} else {
|
||||
$headers['Access-Control-Allow-Origin'] = '*';
|
||||
}
|
||||
$listeners = $this->listeners('headers');
|
||||
foreach ($listeners as $listener) {
|
||||
$listener($headers);
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
64
vendor/workerman/phpsocket.io/src/Engine/Transports/WebSocket.php
vendored
Normal file
64
vendor/workerman/phpsocket.io/src/Engine/Transports/WebSocket.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Engine\Transports;
|
||||
|
||||
use PHPSocketIO\Engine\Transport;
|
||||
use PHPSocketIO\Engine\Parser;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class WebSocket extends Transport
|
||||
{
|
||||
public $sid = null;
|
||||
public $writable = true;
|
||||
public $supportsFraming = true;
|
||||
public $supportsBinary = true;
|
||||
public $name = 'websocket';
|
||||
public $socket = null;
|
||||
|
||||
public function __construct($req)
|
||||
{
|
||||
$this->socket = $req->connection;
|
||||
$this->socket->onMessage = [$this, 'onData2'];
|
||||
$this->socket->onClose = [$this, 'onClose'];
|
||||
$this->socket->onError = [$this, 'onError2'];
|
||||
Debug::debug('WebSocket __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('WebSocket __destruct');
|
||||
}
|
||||
|
||||
public function onData2($connection, $data): void
|
||||
{
|
||||
call_user_func(array(get_parent_class($this), 'onData'), $data);
|
||||
|
||||
}
|
||||
|
||||
public function onError2($conection, $code, $msg): void
|
||||
{
|
||||
call_user_func(array(get_parent_class($this), 'onData'), $code, $msg);
|
||||
}
|
||||
|
||||
public function send(array $packets): void
|
||||
{
|
||||
foreach ($packets as $packet) {
|
||||
$data = Parser::encodePacket($packet);
|
||||
if ($this->socket) {
|
||||
$this->socket->send($data);
|
||||
$this->emit('drain');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function doClose(callable $fn = null): void
|
||||
{
|
||||
if ($this->socket) {
|
||||
$this->socket->close();
|
||||
$this->socket = null;
|
||||
if (! empty($fn)) {
|
||||
call_user_func($fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
vendor/workerman/phpsocket.io/src/Event/Emitter.php
vendored
Normal file
96
vendor/workerman/phpsocket.io/src/Event/Emitter.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Event;
|
||||
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Emitter
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
Debug::debug('Emitter __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Emitter __destruct');
|
||||
}
|
||||
|
||||
/**
|
||||
* [event=>[[listener1, once?], [listener2,once?], ..], ..]
|
||||
*/
|
||||
protected $_eventListenerMap = [];
|
||||
|
||||
public function on($event_name, $listener): Emitter
|
||||
{
|
||||
$this->emit('newListener', $event_name, $listener);
|
||||
$this->_eventListenerMap[$event_name][] = [$listener, 0];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function once($event_name, $listener): Emitter
|
||||
{
|
||||
$this->_eventListenerMap[$event_name][] = [$listener, 1];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeListener($event_name, $listener): Emitter
|
||||
{
|
||||
if (! isset($this->_eventListenerMap[$event_name])) {
|
||||
return $this;
|
||||
}
|
||||
foreach ($this->_eventListenerMap[$event_name] as $key => $item) {
|
||||
if ($item[0] === $listener) {
|
||||
$this->emit('removeListener', $event_name, $listener);
|
||||
unset($this->_eventListenerMap[$event_name][$key]);
|
||||
}
|
||||
}
|
||||
if (empty($this->_eventListenerMap[$event_name])) {
|
||||
unset($this->_eventListenerMap[$event_name]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAllListeners($event_name = null): Emitter
|
||||
{
|
||||
$this->emit('removeListener', $event_name);
|
||||
if (null === $event_name) {
|
||||
$this->_eventListenerMap = [];
|
||||
return $this;
|
||||
}
|
||||
unset($this->_eventListenerMap[$event_name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function listeners($event_name): array
|
||||
{
|
||||
if (empty($this->_eventListenerMap[$event_name])) {
|
||||
return [];
|
||||
}
|
||||
$listeners = [];
|
||||
foreach ($this->_eventListenerMap[$event_name] as $item) {
|
||||
$listeners[] = $item[0];
|
||||
}
|
||||
return $listeners;
|
||||
}
|
||||
|
||||
public function emit($event_name = null)
|
||||
{
|
||||
if (empty($event_name) || empty($this->_eventListenerMap[$event_name])) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->_eventListenerMap[$event_name] as $key => $item) {
|
||||
$args = func_get_args();
|
||||
unset($args[0]);
|
||||
call_user_func_array($item[0], $args);
|
||||
// once ?
|
||||
if ($item[1]) {
|
||||
unset($this->_eventListenerMap[$event_name][$key]);
|
||||
if (empty($this->_eventListenerMap[$event_name])) {
|
||||
unset($this->_eventListenerMap[$event_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
158
vendor/workerman/phpsocket.io/src/Nsp.php
vendored
Normal file
158
vendor/workerman/phpsocket.io/src/Nsp.php
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use PHPSocketIO\Parser\Parser;
|
||||
|
||||
class Nsp extends Emitter
|
||||
{
|
||||
public $adapter;
|
||||
public $name = null;
|
||||
public $server = null;
|
||||
public $rooms = [];
|
||||
public $flags = [];
|
||||
public $sockets = [];
|
||||
public $connected = [];
|
||||
public $fns = [];
|
||||
public $ids = 0;
|
||||
public $acks = [];
|
||||
public static $events = [
|
||||
'connect' => 'connect', // for symmetry with client
|
||||
'connection' => 'connection',
|
||||
'newListener' => 'newListener'
|
||||
];
|
||||
|
||||
public function __construct($server, $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->server = $server;
|
||||
$this->initAdapter();
|
||||
Debug::debug('Nsp __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Nsp __destruct');
|
||||
}
|
||||
|
||||
public function initAdapter()
|
||||
{
|
||||
$adapter_name = $this->server->adapter();
|
||||
$this->adapter = new $adapter_name($this);
|
||||
}
|
||||
|
||||
public function to($name): Nsp
|
||||
{
|
||||
if (! isset($this->rooms[$name])) {
|
||||
$this->rooms[$name] = $name;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function in($name): Nsp
|
||||
{
|
||||
return $this->to($name);
|
||||
}
|
||||
|
||||
public function add($client, $nsp, $fn)
|
||||
{
|
||||
$socket_name = $this->server->socket();
|
||||
$socket = new $socket_name($this, $client);
|
||||
if ('open' === $client->conn->readyState) {
|
||||
$this->sockets[$socket->id] = $socket;
|
||||
$socket->onconnect();
|
||||
if (! empty($fn)) {
|
||||
call_user_func($fn, $socket, $nsp);
|
||||
}
|
||||
$this->emit('connect', $socket);
|
||||
$this->emit('connection', $socket);
|
||||
} else {
|
||||
echo('next called after client was closed - ignoring socket');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a client. Called by each `Socket`.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function remove($socket)
|
||||
{
|
||||
// todo $socket->id
|
||||
unset($this->sockets[$socket->id]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
* @param null $ev
|
||||
* @return Nsp|void {Namespace} self
|
||||
* @api public
|
||||
*/
|
||||
public function emit($ev = null)
|
||||
{
|
||||
$args = func_get_args();
|
||||
if (isset(self::$events[$ev])) {
|
||||
call_user_func_array([get_parent_class(__CLASS__), 'emit'], $args);
|
||||
} else {
|
||||
// set up packet object
|
||||
|
||||
$parserType = Parser::EVENT; // default
|
||||
//if (self::hasBin($args)) { $parserType = Parser::BINARY_EVENT; } // binary
|
||||
|
||||
$packet = ['type' => $parserType, 'data' => $args];
|
||||
|
||||
if (is_callable(end($args))) {
|
||||
echo('Callbacks are not supported when broadcasting');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->adapter->broadcast(
|
||||
$packet,
|
||||
[
|
||||
'rooms' => $this->rooms,
|
||||
'flags' => $this->flags
|
||||
]
|
||||
);
|
||||
|
||||
$this->rooms = [];
|
||||
$this->flags = [];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function send(): Nsp
|
||||
{
|
||||
$args = func_get_args();
|
||||
array_unshift($args, 'message');
|
||||
$this->emit($args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function write()
|
||||
{
|
||||
$args = func_get_args();
|
||||
return call_user_func_array([$this, 'send'], $args);
|
||||
}
|
||||
|
||||
public function clients($fn): Nsp
|
||||
{
|
||||
$this->adapter->clients($this->rooms, $fn);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @return Nsp {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
public function compress($compress): Nsp
|
||||
{
|
||||
$this->flags['compress'] = $compress;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
113
vendor/workerman/phpsocket.io/src/Parser/Decoder.php
vendored
Normal file
113
vendor/workerman/phpsocket.io/src/Parser/Decoder.php
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Parser;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Decoder extends Emitter
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
Debug::debug('Decoder __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Decoder __destruct');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function add($obj): void
|
||||
{
|
||||
if (is_string($obj)) {
|
||||
$packet = self::decodeString($obj);
|
||||
$this->emit('decoded', $packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function decodeString($str): array
|
||||
{
|
||||
$p = [];
|
||||
$i = 0;
|
||||
|
||||
// look up type
|
||||
$p['type'] = $str[0];
|
||||
if (! isset(Parser::$types[$p['type']])) {
|
||||
return self::error();
|
||||
}
|
||||
|
||||
// look up attachments if type binary
|
||||
if (Parser::BINARY_EVENT == $p['type'] || Parser::BINARY_ACK == $p['type']) {
|
||||
$buf = '';
|
||||
while ($str[++$i] != '-') {
|
||||
$buf .= $str[$i];
|
||||
if ($i == strlen($str)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($buf != intval($buf) || $str[$i] != '-') {
|
||||
throw new Exception('Illegal attachments');
|
||||
}
|
||||
$p['attachments'] = intval($buf);
|
||||
}
|
||||
|
||||
// look up namespace (if any)
|
||||
if (isset($str[$i + 1]) && '/' === $str[$i + 1]) {
|
||||
$p['nsp'] = '';
|
||||
while (++$i) {
|
||||
if ($i === strlen($str)) {
|
||||
break;
|
||||
}
|
||||
$c = $str[$i];
|
||||
if (',' === $c) {
|
||||
break;
|
||||
}
|
||||
$p['nsp'] .= $c;
|
||||
}
|
||||
} else {
|
||||
$p['nsp'] = '/';
|
||||
}
|
||||
|
||||
// look up id
|
||||
if (isset($str[$i + 1])) {
|
||||
$next = $str[$i + 1];
|
||||
if ('' !== $next && strval((int)$next) === strval($next)) {
|
||||
$p['id'] = '';
|
||||
while (++$i) {
|
||||
$c = $str[$i];
|
||||
if (null == $c || strval((int)$c) != strval($c)) {
|
||||
--$i;
|
||||
break;
|
||||
}
|
||||
$p['id'] .= $str[$i];
|
||||
if ($i == strlen($str)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$p['id'] = (int)$p['id'];
|
||||
}
|
||||
}
|
||||
|
||||
// look up json data
|
||||
if (isset($str[++$i])) {
|
||||
$p['data'] = json_decode(substr($str, $i), true);
|
||||
}
|
||||
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function error(): array
|
||||
{
|
||||
return [
|
||||
'type' => Parser::ERROR,
|
||||
'data' => 'parser error'
|
||||
];
|
||||
}
|
||||
}
|
||||
72
vendor/workerman/phpsocket.io/src/Parser/Encoder.php
vendored
Normal file
72
vendor/workerman/phpsocket.io/src/Parser/Encoder.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Parser;
|
||||
|
||||
use Exception;
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use PHPSocketIO\Debug;
|
||||
|
||||
class Encoder extends Emitter
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
Debug::debug('Encoder __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('Encoder __destruct');
|
||||
}
|
||||
|
||||
public function encode($obj): array
|
||||
{
|
||||
if (Parser::BINARY_EVENT == $obj['type'] || Parser::BINARY_ACK == $obj['type']) {
|
||||
echo new Exception("not support BINARY_EVENT BINARY_ACK");
|
||||
return [];
|
||||
} else {
|
||||
$encoding = self::encodeAsString($obj);
|
||||
return [$encoding];
|
||||
}
|
||||
}
|
||||
|
||||
public static function encodeAsString($obj): string
|
||||
{
|
||||
$str = '';
|
||||
$nsp = false;
|
||||
|
||||
// first is type
|
||||
$str .= $obj['type'];
|
||||
|
||||
// attachments if we have them
|
||||
if (Parser::BINARY_EVENT == $obj['type'] || Parser::BINARY_ACK == $obj['type']) {
|
||||
$str .= $obj['attachments'];
|
||||
$str .= '-';
|
||||
}
|
||||
|
||||
// if we have a namespace other than `/`
|
||||
// we append it followed by a comma `,`
|
||||
if (! empty($obj['nsp']) && '/' !== $obj['nsp']) {
|
||||
$nsp = true;
|
||||
$str .= $obj['nsp'];
|
||||
}
|
||||
|
||||
// immediately followed by the id
|
||||
if (isset($obj['id'])) {
|
||||
if ($nsp) {
|
||||
$str .= ',';
|
||||
$nsp = false;
|
||||
}
|
||||
$str .= $obj['id'];
|
||||
}
|
||||
|
||||
// json data
|
||||
if (isset($obj['data'])) {
|
||||
if ($nsp) {
|
||||
$str .= ',';
|
||||
}
|
||||
$str .= json_encode($obj['data']);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
65
vendor/workerman/phpsocket.io/src/Parser/Parser.php
vendored
Normal file
65
vendor/workerman/phpsocket.io/src/Parser/Parser.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO\Parser;
|
||||
|
||||
class Parser
|
||||
{
|
||||
/**
|
||||
* Packet type `connect`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const CONNECT = 0;
|
||||
|
||||
/**
|
||||
* Packet type `disconnect`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const DISCONNECT = 1;
|
||||
|
||||
/**
|
||||
* Packet type `event`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const EVENT = 2;
|
||||
|
||||
/**
|
||||
* Packet type `ack`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const ACK = 3;
|
||||
|
||||
/**
|
||||
* Packet type `error`.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const ERROR = 4;
|
||||
|
||||
/**
|
||||
* Packet type 'binary event'
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const BINARY_EVENT = 5;
|
||||
|
||||
/**
|
||||
* Packet type `binary ack`. For acks with binary arguments.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
const BINARY_ACK = 6;
|
||||
|
||||
public static $types = [
|
||||
'CONNECT',
|
||||
'DISCONNECT',
|
||||
'EVENT',
|
||||
'BINARY_EVENT',
|
||||
'ACK',
|
||||
'BINARY_ACK',
|
||||
'ERROR'
|
||||
];
|
||||
}
|
||||
468
vendor/workerman/phpsocket.io/src/Socket.php
vendored
Normal file
468
vendor/workerman/phpsocket.io/src/Socket.php
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use PHPSocketIO\Event\Emitter;
|
||||
use PHPSocketIO\Parser\Parser;
|
||||
|
||||
class Socket extends Emitter
|
||||
{
|
||||
public $nsp = null;
|
||||
public $server = null;
|
||||
public $adapter = null;
|
||||
public $id = null;
|
||||
public $path = '/';
|
||||
public $request = null;
|
||||
public $client = null;
|
||||
public $conn = null;
|
||||
public $rooms = [];
|
||||
public $_rooms = [];
|
||||
public $flags = [];
|
||||
public $acks = [];
|
||||
public $connected = true;
|
||||
public $disconnected = false;
|
||||
public $handshake = [];
|
||||
public $userId = null;
|
||||
public $isGuest = false;
|
||||
public $addedUser = null;
|
||||
public $username = null;
|
||||
|
||||
|
||||
public static $events = [
|
||||
'error' => 'error',
|
||||
'connect' => 'connect',
|
||||
'disconnect' => 'disconnect',
|
||||
'newListener' => 'newListener',
|
||||
'removeListener' => 'removeListener'
|
||||
];
|
||||
|
||||
public static $flagsMap = [
|
||||
'json' => 'json',
|
||||
'volatile' => 'volatile',
|
||||
'broadcast' => 'broadcast'
|
||||
];
|
||||
|
||||
public function __construct($nsp, $client)
|
||||
{
|
||||
$this->nsp = $nsp;
|
||||
$this->server = $nsp->server;
|
||||
$this->adapter = $this->nsp->adapter;
|
||||
$this->id = ($nsp->name !== '/') ? $nsp->name . '#' . $client->id : $client->id;
|
||||
$this->request = $client->request;
|
||||
$this->client = $client;
|
||||
$this->conn = $client->conn;
|
||||
$this->handshake = $this->buildHandshake();
|
||||
Debug::debug('IO Socket __construct');
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
Debug::debug('IO Socket __destruct');
|
||||
}
|
||||
|
||||
public function buildHandshake(): array
|
||||
{
|
||||
//todo check this->request->_query
|
||||
$info = ! empty($this->request->url) ? parse_url($this->request->url) : [];
|
||||
$query = [];
|
||||
if (isset($info['query'])) {
|
||||
parse_str($info['query'], $query);
|
||||
}
|
||||
return [
|
||||
'headers' => $this->request->headers ?? [],
|
||||
'time' => date('D M d Y H:i:s') . ' GMT',
|
||||
'address' => $this->conn->remoteAddress,
|
||||
'xdomain' => isset($this->request->headers['origin']),
|
||||
'secure' => ! empty($this->request->connection->encrypted),
|
||||
'issued' => time(),
|
||||
'url' => $this->request->url ?? '',
|
||||
'query' => $query,
|
||||
];
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name === 'broadcast') {
|
||||
$this->flags['broadcast'] = true;
|
||||
return $this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function emit($ev = null)
|
||||
{
|
||||
$args = func_get_args();
|
||||
if (isset(self::$events[$ev])) {
|
||||
call_user_func_array(array(get_parent_class(__CLASS__), 'emit'), $args);
|
||||
} else {
|
||||
$packet = [];
|
||||
$packet['type'] = Parser::EVENT;
|
||||
$packet['data'] = $args;
|
||||
$flags = $this->flags;
|
||||
// access last argument to see if it's an ACK callback
|
||||
if (is_callable(end($args))) {
|
||||
if ($this->_rooms || isset($flags['broadcast'])) {
|
||||
throw new Exception('Callbacks are not supported when broadcasting');
|
||||
}
|
||||
echo('emitting packet with ack id ' . $this->nsp->ids);
|
||||
$this->acks[$this->nsp->ids] = array_pop($args);
|
||||
$packet['id'] = $this->nsp->ids++;
|
||||
}
|
||||
|
||||
if ($this->_rooms || ! empty($flags['broadcast'])) {
|
||||
$this->adapter->broadcast(
|
||||
$packet,
|
||||
[
|
||||
'except' => [$this->id => $this->id],
|
||||
'rooms' => $this->_rooms,
|
||||
'flags' => $flags
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// dispatch packet
|
||||
$this->packet($packet);
|
||||
}
|
||||
|
||||
// reset flags
|
||||
$this->_rooms = [];
|
||||
$this->flags = [];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Targets a room when broadcasting.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return Socket {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
public function to($name): Socket
|
||||
{
|
||||
if (! isset($this->_rooms[$name])) {
|
||||
$this->_rooms[$name] = $name;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function in($name): Socket
|
||||
{
|
||||
return $this->to($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a `message` event.
|
||||
*
|
||||
* @return Socket {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
public function send(): Socket
|
||||
{
|
||||
$args = func_get_args();
|
||||
array_unshift($args, 'message');
|
||||
call_user_func_array([$this, 'emit'], $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function write(): Socket
|
||||
{
|
||||
$args = func_get_args();
|
||||
array_unshift($args, 'message');
|
||||
call_user_func_array([$this, 'emit'], $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @param {Object} options
|
||||
* @api private
|
||||
*/
|
||||
public function packet($packet, $preEncoded = false)
|
||||
{
|
||||
if (! $this->nsp || ! $this->client) {
|
||||
return;
|
||||
}
|
||||
$packet['nsp'] = $this->nsp->name;
|
||||
$this->client->packet($packet, $preEncoded, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @return Socket {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
public function join($room): Socket
|
||||
{
|
||||
if (! $this->connected) {
|
||||
return $this;
|
||||
}
|
||||
if (isset($this->rooms[$room])) {
|
||||
return $this;
|
||||
}
|
||||
$this->adapter->add($this->id, $room);
|
||||
$this->rooms[$room] = $room;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves a room.
|
||||
*
|
||||
* @param {String} room
|
||||
* @return Socket {Socket} self
|
||||
* @api private
|
||||
*/
|
||||
public function leave($room): Socket
|
||||
{
|
||||
$this->adapter->del($this->id, $room);
|
||||
unset($this->rooms[$room]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave all rooms.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
public function leaveAll()
|
||||
{
|
||||
$this->adapter->delAll($this->id);
|
||||
$this->rooms = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by `Namespace` upon succesful
|
||||
* middleware execution (ie: authorization).
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function onconnect()
|
||||
{
|
||||
$this->nsp->connected[$this->id] = $this;
|
||||
$this->join($this->id);
|
||||
$this->packet(
|
||||
[
|
||||
'type' => Parser::CONNECT
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with each packet. Called by `Client`.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @throws Exception
|
||||
* @api private
|
||||
*/
|
||||
public function onpacket($packet)
|
||||
{
|
||||
switch ($packet['type']) {
|
||||
case Parser::BINARY_EVENT:
|
||||
case Parser::EVENT:
|
||||
$this->onevent($packet);
|
||||
break;
|
||||
case Parser::BINARY_ACK:
|
||||
case Parser::ACK:
|
||||
$this->onack($packet);
|
||||
break;
|
||||
case Parser::DISCONNECT:
|
||||
$this->ondisconnect();
|
||||
break;
|
||||
case Parser::ERROR:
|
||||
$this->emit('error', $packet['data']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon event packet.
|
||||
*
|
||||
* @param {Object} packet object
|
||||
* @api private
|
||||
*/
|
||||
public function onevent($packet)
|
||||
{
|
||||
$args = $packet['data'] ?? [];
|
||||
if (! empty($packet['id']) || (isset($packet['id']) && $packet['id'] === 0)) {
|
||||
$args[] = $this->ack($packet['id']);
|
||||
}
|
||||
call_user_func_array(array(get_parent_class(__CLASS__), 'emit'), $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an ack callback to emit with an event.
|
||||
*
|
||||
* @param {Number} packet id
|
||||
* @api private
|
||||
*/
|
||||
public function ack($id): Closure
|
||||
{
|
||||
$sent = false;
|
||||
return function () use (&$sent, $id) {
|
||||
$self = $this;
|
||||
// prevent double callbacks
|
||||
if ($sent) {
|
||||
return;
|
||||
}
|
||||
$args = func_get_args();
|
||||
$type = $this->hasBin($args) ? Parser::BINARY_ACK : Parser::ACK;
|
||||
$self->packet(
|
||||
[
|
||||
'id' => $id,
|
||||
'type' => $type,
|
||||
'data' => $args
|
||||
]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon ack packet.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
public function onack($packet)
|
||||
{
|
||||
$ack = $this->acks[$packet['id']];
|
||||
if (is_callable($ack)) {
|
||||
call_user_func($ack, $packet['data']);
|
||||
unset($this->acks[$packet['id']]);
|
||||
} else {
|
||||
echo('bad ack ' . $packet['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon client disconnect packet.
|
||||
*
|
||||
* @throws Exception
|
||||
* @api private
|
||||
*/
|
||||
public function ondisconnect()
|
||||
{
|
||||
$this->onclose('client namespace disconnect');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a client error.
|
||||
*
|
||||
* @throws Exception
|
||||
* @api private
|
||||
*/
|
||||
public function onerror($err)
|
||||
{
|
||||
if ($this->listeners('error')) {
|
||||
$this->emit('error', $err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon closing. Called by `Client`.
|
||||
*
|
||||
* @param {String} reason
|
||||
* @param {Error} optional error object
|
||||
* @throws Exception
|
||||
* @api private
|
||||
*/
|
||||
public function onclose($reason)
|
||||
{
|
||||
if (! $this->connected) {
|
||||
return $this;
|
||||
}
|
||||
$this->emit('disconnect', $reason);
|
||||
$this->leaveAll();
|
||||
$this->nsp->remove($this);
|
||||
$this->client->remove($this);
|
||||
$this->connected = false;
|
||||
$this->disconnected = true;
|
||||
unset($this->nsp->connected[$this->id]);
|
||||
// ....
|
||||
$this->nsp = null;
|
||||
$this->server = null;
|
||||
$this->adapter = null;
|
||||
$this->request = null;
|
||||
$this->client = null;
|
||||
$this->conn = null;
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an `error` packet.
|
||||
*
|
||||
* @param {Object} error object
|
||||
* @api private
|
||||
*/
|
||||
|
||||
public function error($err)
|
||||
{
|
||||
$this->packet(
|
||||
[
|
||||
'type' => Parser::ERROR, 'data' => $err
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects this client.
|
||||
*
|
||||
* @param bool $close
|
||||
* @return Socket {Socket} self
|
||||
* @throws Exception
|
||||
* @api public
|
||||
*/
|
||||
public function disconnect(bool $close = false): Socket
|
||||
{
|
||||
if (! $this->connected) {
|
||||
return $this;
|
||||
}
|
||||
if ($close) {
|
||||
$this->client->disconnect();
|
||||
} else {
|
||||
$this->packet(
|
||||
[
|
||||
'type' => Parser::DISCONNECT
|
||||
]
|
||||
);
|
||||
$this->onclose('server namespace disconnect');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @param {Boolean} if `true`, compresses the sending data
|
||||
* @return Socket {Socket} self
|
||||
* @api public
|
||||
*/
|
||||
public function compress($compress): Socket
|
||||
{
|
||||
$this->flags['compress'] = $compress;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function hasBin($args): bool
|
||||
{
|
||||
$hasBin = false;
|
||||
|
||||
array_walk_recursive(
|
||||
$args,
|
||||
function ($item, $key) use ($hasBin) {
|
||||
if (! ctype_print($item)) {
|
||||
$hasBin = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return $hasBin;
|
||||
}
|
||||
}
|
||||
183
vendor/workerman/phpsocket.io/src/SocketIO.php
vendored
Normal file
183
vendor/workerman/phpsocket.io/src/SocketIO.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSocketIO;
|
||||
|
||||
use Workerman\Worker;
|
||||
use PHPSocketIO\Engine\Engine;
|
||||
|
||||
class SocketIO
|
||||
{
|
||||
public $worker;
|
||||
public $sockets;
|
||||
public $nsps = [];
|
||||
protected $_nsp = null;
|
||||
protected $_socket = null;
|
||||
protected $_adapter = null;
|
||||
public $engine = null;
|
||||
protected $_origins = '*:*';
|
||||
protected $_path = null;
|
||||
|
||||
public function __construct($port = null, $opts = [])
|
||||
{
|
||||
$nsp = $opts['nsp'] ?? '\PHPSocketIO\Nsp';
|
||||
$this->nsp($nsp);
|
||||
|
||||
$socket = $opts['socket'] ?? '\PHPSocketIO\Socket';
|
||||
$this->socket($socket);
|
||||
|
||||
$adapter = $opts['adapter'] ?? '\PHPSocketIO\DefaultAdapter';
|
||||
$this->adapter($adapter);
|
||||
if (isset($opts['origins'])) {
|
||||
$this->origins($opts['origins']);
|
||||
}
|
||||
|
||||
unset($opts['nsp'], $opts['socket'], $opts['adapter'], $opts['origins']);
|
||||
|
||||
$this->sockets = $this->of('/');
|
||||
|
||||
if (! class_exists('Protocols\SocketIO')) {
|
||||
class_alias('PHPSocketIO\Engine\Protocols\SocketIO', 'Protocols\SocketIO');
|
||||
}
|
||||
if ($port) {
|
||||
$host = '0.0.0.0';
|
||||
if (isset($opts['host'])) {
|
||||
$ip = trim($opts['host'], '[]');
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
$host = (strpos($ip, ':') !== false) ? "[$ip]" : $ip;
|
||||
}
|
||||
}
|
||||
$worker = new Worker('SocketIO://' . $host . ':' . $port, $opts);
|
||||
$worker->name = 'PHPSocketIO';
|
||||
|
||||
if (isset($opts['ssl'])) {
|
||||
$worker->transport = 'ssl';
|
||||
}
|
||||
|
||||
$this->attach($worker);
|
||||
}
|
||||
}
|
||||
|
||||
public function nsp($v = null)
|
||||
{
|
||||
if (empty($v)) {
|
||||
return $this->_nsp;
|
||||
}
|
||||
$this->_nsp = $v;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function socket($v = null)
|
||||
{
|
||||
if (empty($v)) {
|
||||
return $this->_socket;
|
||||
}
|
||||
$this->_socket = $v;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function adapter($v = null)
|
||||
{
|
||||
if (empty($v)) {
|
||||
return $this->_adapter;
|
||||
}
|
||||
$this->_adapter = $v;
|
||||
foreach ($this->nsps as $nsp) {
|
||||
$nsp->initAdapter();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function origins($v = null)
|
||||
{
|
||||
if ($v === null) {
|
||||
return $this->_origins;
|
||||
}
|
||||
$this->_origins = $v;
|
||||
if (isset($this->engine)) {
|
||||
$this->engine->origins = $this->_origins;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function attach($srv, $opts = []): SocketIO
|
||||
{
|
||||
$engine = new Engine();
|
||||
$engine->attach($srv, $opts);
|
||||
|
||||
// Export http server
|
||||
$this->worker = $srv;
|
||||
|
||||
// bind to engine events
|
||||
$this->bind($engine);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function bind($engine): SocketIO
|
||||
{
|
||||
$this->engine = $engine;
|
||||
$this->engine->on('connection', [$this, 'onConnection']);
|
||||
$this->engine->origins = $this->_origins;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function of($name, $fn = null)
|
||||
{
|
||||
if ($name[0] !== '/') {
|
||||
$name = "/$name";
|
||||
}
|
||||
if (empty($this->nsps[$name])) {
|
||||
$nsp_name = $this->nsp();
|
||||
$this->nsps[$name] = new $nsp_name($this, $name);
|
||||
}
|
||||
if ($fn) {
|
||||
$this->nsps[$name]->on('connect', $fn);
|
||||
}
|
||||
return $this->nsps[$name];
|
||||
}
|
||||
|
||||
public function onConnection($engine_socket): SocketIO
|
||||
{
|
||||
$client = new Client($this, $engine_socket);
|
||||
$client->connect('/');
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function on()
|
||||
{
|
||||
$args = array_pad(func_get_args(), 2, null);
|
||||
|
||||
if ($args[0] === 'workerStart') {
|
||||
$this->worker->onWorkerStart = $args[1];
|
||||
} elseif ($args[0] === 'workerStop') {
|
||||
$this->worker->onWorkerStop = $args[1];
|
||||
} elseif ($args[0] !== null) {
|
||||
return call_user_func_array([$this->sockets, 'on'], $args);
|
||||
}
|
||||
}
|
||||
|
||||
public function in()
|
||||
{
|
||||
return call_user_func_array([$this->sockets, 'in'], func_get_args());
|
||||
}
|
||||
|
||||
public function to()
|
||||
{
|
||||
return call_user_func_array([$this->sockets, 'to'], func_get_args());
|
||||
}
|
||||
|
||||
public function emit()
|
||||
{
|
||||
return call_user_func_array([$this->sockets, 'emit'], func_get_args());
|
||||
}
|
||||
|
||||
public function send()
|
||||
{
|
||||
return call_user_func_array([$this->sockets, 'send'], func_get_args());
|
||||
}
|
||||
|
||||
public function write()
|
||||
{
|
||||
return call_user_func_array([$this->sockets, 'write'], func_get_args());
|
||||
}
|
||||
}
|
||||
4
vendor/workerman/workerman/.github/FUNDING.yml
vendored
Normal file
4
vendor/workerman/workerman/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: workerman
|
||||
patreon: walkor
|
||||
6
vendor/workerman/workerman/.gitignore
vendored
Normal file
6
vendor/workerman/workerman/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
logs
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
.idea
|
||||
.DS_Store
|
||||
69
vendor/workerman/workerman/Autoloader.php
vendored
Normal file
69
vendor/workerman/workerman/Autoloader.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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 Workerman;
|
||||
|
||||
/**
|
||||
* Autoload.
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/**
|
||||
* Autoload root path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_autoloadRootPath = '';
|
||||
|
||||
/**
|
||||
* Set autoload root path.
|
||||
*
|
||||
* @param string $root_path
|
||||
* @return void
|
||||
*/
|
||||
public static function setRootPath($root_path)
|
||||
{
|
||||
self::$_autoloadRootPath = $root_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load files by namespace.
|
||||
*
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function loadByNamespace($name)
|
||||
{
|
||||
$class_path = \str_replace('\\', \DIRECTORY_SEPARATOR, $name);
|
||||
if (\strpos($name, 'Workerman\\') === 0) {
|
||||
$class_file = __DIR__ . \substr($class_path, \strlen('Workerman')) . '.php';
|
||||
} else {
|
||||
if (self::$_autoloadRootPath) {
|
||||
$class_file = self::$_autoloadRootPath . \DIRECTORY_SEPARATOR . $class_path . '.php';
|
||||
}
|
||||
if (empty($class_file) || !\is_file($class_file)) {
|
||||
$class_file = __DIR__ . \DIRECTORY_SEPARATOR . '..' . \DIRECTORY_SEPARATOR . "$class_path.php";
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_file($class_file)) {
|
||||
require_once($class_file);
|
||||
if (\class_exists($name, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
\spl_autoload_register('\Workerman\Autoloader::loadByNamespace');
|
||||
382
vendor/workerman/workerman/Connection/AsyncTcpConnection.php
vendored
Normal file
382
vendor/workerman/workerman/Connection/AsyncTcpConnection.php
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
<?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 Workerman\Connection;
|
||||
|
||||
use StdClass;
|
||||
use Workerman\Events\EventInterface;
|
||||
use Workerman\Lib\Timer;
|
||||
use Workerman\Worker;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* AsyncTcpConnection.
|
||||
*/
|
||||
class AsyncTcpConnection extends TcpConnection
|
||||
{
|
||||
/**
|
||||
* Emitted when socket connection is successfully established.
|
||||
*
|
||||
* @var callable|null
|
||||
*/
|
||||
public $onConnect = null;
|
||||
|
||||
/**
|
||||
* Transport layer protocol.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $transport = 'tcp';
|
||||
|
||||
/**
|
||||
* Status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_status = self::STATUS_INITIAL;
|
||||
|
||||
/**
|
||||
* Remote host.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_remoteHost = '';
|
||||
|
||||
/**
|
||||
* Remote port.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_remotePort = 80;
|
||||
|
||||
/**
|
||||
* Connect start time.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $_connectStartTime = 0;
|
||||
|
||||
/**
|
||||
* Remote URI.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_remoteURI = '';
|
||||
|
||||
/**
|
||||
* Context option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_contextOption = null;
|
||||
|
||||
/**
|
||||
* Reconnect timer.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_reconnectTimer = null;
|
||||
|
||||
|
||||
/**
|
||||
* PHP built-in protocols.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_builtinTransports = array(
|
||||
'tcp' => 'tcp',
|
||||
'udp' => 'udp',
|
||||
'unix' => 'unix',
|
||||
'ssl' => 'ssl',
|
||||
'sslv2' => 'sslv2',
|
||||
'sslv3' => 'sslv3',
|
||||
'tls' => 'tls'
|
||||
);
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $remote_address
|
||||
* @param array $context_option
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($remote_address, ?array $context_option = array())
|
||||
{
|
||||
$address_info = \parse_url($remote_address);
|
||||
if (!$address_info) {
|
||||
list($scheme, $this->_remoteAddress) = \explode(':', $remote_address, 2);
|
||||
if('unix' === strtolower($scheme)) {
|
||||
$this->_remoteAddress = substr($remote_address, strpos($remote_address, '/') + 2);
|
||||
}
|
||||
if (!$this->_remoteAddress) {
|
||||
Worker::safeEcho(new \Exception('bad remote_address'));
|
||||
}
|
||||
} else {
|
||||
if (!isset($address_info['port'])) {
|
||||
$address_info['port'] = 0;
|
||||
}
|
||||
if (!isset($address_info['path'])) {
|
||||
$address_info['path'] = '/';
|
||||
}
|
||||
if (!isset($address_info['query'])) {
|
||||
$address_info['query'] = '';
|
||||
} else {
|
||||
$address_info['query'] = '?' . $address_info['query'];
|
||||
}
|
||||
$this->_remoteHost = $address_info['host'];
|
||||
$this->_remotePort = $address_info['port'];
|
||||
$this->_remoteURI = "{$address_info['path']}{$address_info['query']}";
|
||||
$scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp';
|
||||
$this->_remoteAddress = 'unix' === strtolower($scheme)
|
||||
? substr($remote_address, strpos($remote_address, '/') + 2)
|
||||
: $this->_remoteHost . ':' . $this->_remotePort;
|
||||
}
|
||||
|
||||
$this->id = $this->_id = self::$_idRecorder++;
|
||||
if(\PHP_INT_MAX === self::$_idRecorder){
|
||||
self::$_idRecorder = 0;
|
||||
}
|
||||
// Check application layer protocol class.
|
||||
if (!isset(self::$_builtinTransports[$scheme])) {
|
||||
$scheme = \ucfirst($scheme);
|
||||
$this->protocol = '\\Protocols\\' . $scheme;
|
||||
if (!\class_exists($this->protocol)) {
|
||||
$this->protocol = "\\Workerman\\Protocols\\$scheme";
|
||||
if (!\class_exists($this->protocol)) {
|
||||
throw new Exception("class \\Protocols\\$scheme not exist");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->transport = self::$_builtinTransports[$scheme];
|
||||
}
|
||||
|
||||
// For statistics.
|
||||
++self::$statistics['connection_count'];
|
||||
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
|
||||
$this->maxPackageSize = self::$defaultMaxPackageSize;
|
||||
$this->_contextOption = $context_option;
|
||||
$this->context = new StdClass;
|
||||
static::$connections[$this->_id] = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do connect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING &&
|
||||
$this->_status !== self::STATUS_CLOSED) {
|
||||
return;
|
||||
}
|
||||
$this->_status = self::STATUS_CONNECTING;
|
||||
$this->_connectStartTime = \microtime(true);
|
||||
set_error_handler(function() {
|
||||
return false;
|
||||
});
|
||||
if ($this->transport !== 'unix') {
|
||||
if (!$this->_remotePort) {
|
||||
$this->_remotePort = $this->transport === 'ssl' ? 443 : 80;
|
||||
$this->_remoteAddress = $this->_remoteHost.':'.$this->_remotePort;
|
||||
}
|
||||
// Open socket connection asynchronously.
|
||||
if ($this->_contextOption) {
|
||||
$context = \stream_context_create($this->_contextOption);
|
||||
$this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
|
||||
$errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT, $context);
|
||||
} else {
|
||||
$this->_socket = \stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}",
|
||||
$errno, $errstr, 0, \STREAM_CLIENT_ASYNC_CONNECT);
|
||||
}
|
||||
} else {
|
||||
$this->_socket = \stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0,
|
||||
\STREAM_CLIENT_ASYNC_CONNECT);
|
||||
}
|
||||
restore_error_handler();
|
||||
// If failed attempt to emit onError callback.
|
||||
if (!$this->_socket || !\is_resource($this->_socket)) {
|
||||
$this->emitError(\WORKERMAN_CONNECT_FAIL, $errstr);
|
||||
if ($this->_status === self::STATUS_CLOSING) {
|
||||
$this->destroy();
|
||||
}
|
||||
if ($this->_status === self::STATUS_CLOSED) {
|
||||
$this->onConnect = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Add socket to global event loop waiting connection is successfully established or faild.
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection'));
|
||||
// For windows.
|
||||
if(\DIRECTORY_SEPARATOR === '\\') {
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect.
|
||||
*
|
||||
* @param int $after
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect($after = 0)
|
||||
{
|
||||
$this->_status = self::STATUS_INITIAL;
|
||||
static::$connections[$this->_id] = $this;
|
||||
if ($this->_reconnectTimer) {
|
||||
Timer::del($this->_reconnectTimer);
|
||||
}
|
||||
if ($after > 0) {
|
||||
$this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false);
|
||||
return;
|
||||
}
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelReconnect.
|
||||
*/
|
||||
public function cancelReconnect()
|
||||
{
|
||||
if ($this->_reconnectTimer) {
|
||||
Timer::del($this->_reconnectTimer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteHost()
|
||||
{
|
||||
return $this->_remoteHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteURI()
|
||||
{
|
||||
return $this->_remoteURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to emit onError callback.
|
||||
*
|
||||
* @param int $code
|
||||
* @param string $msg
|
||||
* @return void
|
||||
*/
|
||||
protected function emitError($code, $msg)
|
||||
{
|
||||
$this->_status = self::STATUS_CLOSING;
|
||||
if ($this->onError) {
|
||||
try {
|
||||
\call_user_func($this->onError, $this, $code, $msg);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check connection is successfully established or faild.
|
||||
*
|
||||
* @param resource $socket
|
||||
* @return void
|
||||
*/
|
||||
public function checkConnection()
|
||||
{
|
||||
// Remove EV_EXPECT for windows.
|
||||
if(\DIRECTORY_SEPARATOR === '\\') {
|
||||
Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT);
|
||||
}
|
||||
|
||||
// Remove write listener.
|
||||
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
|
||||
|
||||
if ($this->_status !== self::STATUS_CONNECTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check socket state.
|
||||
if ($address = \stream_socket_get_name($this->_socket, true)) {
|
||||
// Nonblocking.
|
||||
\stream_set_blocking($this->_socket, false);
|
||||
// Compatible with hhvm
|
||||
if (\function_exists('stream_set_read_buffer')) {
|
||||
\stream_set_read_buffer($this->_socket, 0);
|
||||
}
|
||||
// Try to open keepalive for tcp and disable Nagle algorithm.
|
||||
if (\function_exists('socket_import_stream') && $this->transport === 'tcp') {
|
||||
$raw_socket = \socket_import_stream($this->_socket);
|
||||
\socket_set_option($raw_socket, \SOL_SOCKET, \SO_KEEPALIVE, 1);
|
||||
\socket_set_option($raw_socket, \SOL_TCP, \TCP_NODELAY, 1);
|
||||
}
|
||||
|
||||
// SSL handshake.
|
||||
if ($this->transport === 'ssl') {
|
||||
$this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket);
|
||||
if ($this->_sslHandshakeCompleted === false) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// There are some data waiting to send.
|
||||
if ($this->_sendBuffer) {
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
|
||||
}
|
||||
}
|
||||
|
||||
// Register a listener waiting read event.
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
|
||||
|
||||
$this->_status = self::STATUS_ESTABLISHED;
|
||||
$this->_remoteAddress = $address;
|
||||
|
||||
// Try to emit onConnect callback.
|
||||
if ($this->onConnect) {
|
||||
try {
|
||||
\call_user_func($this->onConnect, $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
// Try to emit protocol::onConnect
|
||||
if ($this->protocol && \method_exists($this->protocol, 'onConnect')) {
|
||||
try {
|
||||
\call_user_func(array($this->protocol, 'onConnect'), $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Connection failed.
|
||||
$this->emitError(\WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(\microtime(true) - $this->_connectStartTime, 4) . ' seconds');
|
||||
if ($this->_status === self::STATUS_CLOSING) {
|
||||
$this->destroy();
|
||||
}
|
||||
if ($this->_status === self::STATUS_CLOSED) {
|
||||
$this->onConnect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
203
vendor/workerman/workerman/Connection/AsyncUdpConnection.php
vendored
Normal file
203
vendor/workerman/workerman/Connection/AsyncUdpConnection.php
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
<?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 Workerman\Connection;
|
||||
|
||||
use Workerman\Events\EventInterface;
|
||||
use Workerman\Worker;
|
||||
use \Exception;
|
||||
|
||||
/**
|
||||
* AsyncUdpConnection.
|
||||
*/
|
||||
class AsyncUdpConnection extends UdpConnection
|
||||
{
|
||||
/**
|
||||
* Emitted when socket connection is successfully established.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $onConnect = null;
|
||||
|
||||
/**
|
||||
* Emitted when socket connection closed.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $onClose = null;
|
||||
|
||||
/**
|
||||
* Connected or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $connected = false;
|
||||
|
||||
/**
|
||||
* Context option.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_contextOption = null;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $remote_address
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($remote_address, $context_option = null)
|
||||
{
|
||||
// Get the application layer communication protocol and listening address.
|
||||
list($scheme, $address) = \explode(':', $remote_address, 2);
|
||||
// Check application layer protocol class.
|
||||
if ($scheme !== 'udp') {
|
||||
$scheme = \ucfirst($scheme);
|
||||
$this->protocol = '\\Protocols\\' . $scheme;
|
||||
if (!\class_exists($this->protocol)) {
|
||||
$this->protocol = "\\Workerman\\Protocols\\$scheme";
|
||||
if (!\class_exists($this->protocol)) {
|
||||
throw new Exception("class \\Protocols\\$scheme not exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_remoteAddress = \substr($address, 2);
|
||||
$this->_contextOption = $context_option;
|
||||
}
|
||||
|
||||
/**
|
||||
* For udp package.
|
||||
*
|
||||
* @param resource $socket
|
||||
* @return bool
|
||||
*/
|
||||
public function baseRead($socket)
|
||||
{
|
||||
$recv_buffer = \stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address);
|
||||
if (false === $recv_buffer || empty($remote_address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->onMessage) {
|
||||
if ($this->protocol) {
|
||||
$parser = $this->protocol;
|
||||
$recv_buffer = $parser::decode($recv_buffer, $this);
|
||||
}
|
||||
++ConnectionInterface::$statistics['total_request'];
|
||||
try {
|
||||
\call_user_func($this->onMessage, $this, $recv_buffer);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data on the connection.
|
||||
*
|
||||
* @param string $send_buffer
|
||||
* @param bool $raw
|
||||
* @return void|boolean
|
||||
*/
|
||||
public function send($send_buffer, $raw = false)
|
||||
{
|
||||
if (false === $raw && $this->protocol) {
|
||||
$parser = $this->protocol;
|
||||
$send_buffer = $parser::encode($send_buffer, $this);
|
||||
if ($send_buffer === '') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($this->connected === false) {
|
||||
$this->connect();
|
||||
}
|
||||
return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close connection.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param bool $raw
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function close($data = null, $raw = false)
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->send($data, $raw);
|
||||
}
|
||||
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
|
||||
\fclose($this->_socket);
|
||||
$this->connected = false;
|
||||
// Try to emit onClose callback.
|
||||
if ($this->onClose) {
|
||||
try {
|
||||
\call_user_func($this->onClose, $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
$this->onConnect = $this->onMessage = $this->onClose = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
if ($this->connected === true) {
|
||||
return;
|
||||
}
|
||||
if ($this->_contextOption) {
|
||||
$context = \stream_context_create($this->_contextOption);
|
||||
$this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg,
|
||||
30, \STREAM_CLIENT_CONNECT, $context);
|
||||
} else {
|
||||
$this->_socket = \stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg);
|
||||
}
|
||||
|
||||
if (!$this->_socket) {
|
||||
Worker::safeEcho(new \Exception($errmsg));
|
||||
return;
|
||||
}
|
||||
|
||||
\stream_set_blocking($this->_socket, false);
|
||||
|
||||
if ($this->onMessage) {
|
||||
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
|
||||
}
|
||||
$this->connected = true;
|
||||
// Try to emit onConnect callback.
|
||||
if ($this->onConnect) {
|
||||
try {
|
||||
\call_user_func($this->onConnect, $this);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
126
vendor/workerman/workerman/Connection/ConnectionInterface.php
vendored
Normal file
126
vendor/workerman/workerman/Connection/ConnectionInterface.php
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
<?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 Workerman\Connection;
|
||||
|
||||
/**
|
||||
* ConnectionInterface.
|
||||
*/
|
||||
#[\AllowDynamicProperties]
|
||||
abstract class ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Statistics for status command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $statistics = array(
|
||||
'connection_count' => 0,
|
||||
'total_request' => 0,
|
||||
'throw_exception' => 0,
|
||||
'send_fail' => 0,
|
||||
);
|
||||
|
||||
/**
|
||||
* Emitted when data is received.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $onMessage = null;
|
||||
|
||||
/**
|
||||
* Emitted when the other end of the socket sends a FIN packet.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $onClose = null;
|
||||
|
||||
/**
|
||||
* Emitted when an error occurs with connection.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
public $onError = null;
|
||||
|
||||
/**
|
||||
* Sends data on the connection.
|
||||
*
|
||||
* @param mixed $send_buffer
|
||||
* @return void|boolean
|
||||
*/
|
||||
abstract public function send($send_buffer);
|
||||
|
||||
/**
|
||||
* Get remote IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getRemoteIp();
|
||||
|
||||
/**
|
||||
* Get remote port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getRemotePort();
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Get local IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getLocalIp();
|
||||
|
||||
/**
|
||||
* Get local port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function getLocalPort();
|
||||
|
||||
/**
|
||||
* Get local address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getLocalAddress();
|
||||
|
||||
/**
|
||||
* Is ipv4.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isIPv4();
|
||||
|
||||
/**
|
||||
* Is ipv6.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isIPv6();
|
||||
|
||||
/**
|
||||
* Close connection.
|
||||
*
|
||||
* @param string|null $data
|
||||
* @return void
|
||||
*/
|
||||
abstract public function close($data = null);
|
||||
}
|
||||
1004
vendor/workerman/workerman/Connection/TcpConnection.php
vendored
Normal file
1004
vendor/workerman/workerman/Connection/TcpConnection.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
208
vendor/workerman/workerman/Connection/UdpConnection.php
vendored
Normal file
208
vendor/workerman/workerman/Connection/UdpConnection.php
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
<?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 Workerman\Connection;
|
||||
|
||||
/**
|
||||
* UdpConnection.
|
||||
*/
|
||||
class UdpConnection extends ConnectionInterface
|
||||
{
|
||||
/**
|
||||
* Application layer protocol.
|
||||
* The format is like this Workerman\\Protocols\\Http.
|
||||
*
|
||||
* @var \Workerman\Protocols\ProtocolInterface
|
||||
*/
|
||||
public $protocol = null;
|
||||
|
||||
/**
|
||||
* Transport layer protocol.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $transport = 'udp';
|
||||
|
||||
/**
|
||||
* Udp socket.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $_socket = null;
|
||||
|
||||
/**
|
||||
* Remote address.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_remoteAddress = '';
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param resource $socket
|
||||
* @param string $remote_address
|
||||
*/
|
||||
public function __construct($socket, $remote_address)
|
||||
{
|
||||
$this->_socket = $socket;
|
||||
$this->_remoteAddress = $remote_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data on the connection.
|
||||
*
|
||||
* @param string $send_buffer
|
||||
* @param bool $raw
|
||||
* @return void|boolean
|
||||
*/
|
||||
public function send($send_buffer, $raw = false)
|
||||
{
|
||||
if (false === $raw && $this->protocol) {
|
||||
$parser = $this->protocol;
|
||||
$send_buffer = $parser::encode($send_buffer, $this);
|
||||
if ($send_buffer === '') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return \strlen($send_buffer) === \stream_socket_sendto($this->_socket, $send_buffer, 0, $this->isIpV6() ? '[' . $this->getRemoteIp() . ']:' . $this->getRemotePort() : $this->_remoteAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteIp()
|
||||
{
|
||||
$pos = \strrpos($this->_remoteAddress, ':');
|
||||
if ($pos) {
|
||||
return \trim(\substr($this->_remoteAddress, 0, $pos), '[]');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRemotePort()
|
||||
{
|
||||
if ($this->_remoteAddress) {
|
||||
return (int)\substr(\strrchr($this->_remoteAddress, ':'), 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteAddress()
|
||||
{
|
||||
return $this->_remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local IP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalIp()
|
||||
{
|
||||
$address = $this->getLocalAddress();
|
||||
$pos = \strrpos($address, ':');
|
||||
if (!$pos) {
|
||||
return '';
|
||||
}
|
||||
return \substr($address, 0, $pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local port.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLocalPort()
|
||||
{
|
||||
$address = $this->getLocalAddress();
|
||||
$pos = \strrpos($address, ':');
|
||||
if (!$pos) {
|
||||
return 0;
|
||||
}
|
||||
return (int)\substr(\strrchr($address, ':'), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalAddress()
|
||||
{
|
||||
return (string)@\stream_socket_get_name($this->_socket, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is ipv4.
|
||||
*
|
||||
* @return bool.
|
||||
*/
|
||||
public function isIpV4()
|
||||
{
|
||||
if ($this->transport === 'unix') {
|
||||
return false;
|
||||
}
|
||||
return \strpos($this->getRemoteIp(), ':') === false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is ipv6.
|
||||
*
|
||||
* @return bool.
|
||||
*/
|
||||
public function isIpV6()
|
||||
{
|
||||
if ($this->transport === 'unix') {
|
||||
return false;
|
||||
}
|
||||
return \strpos($this->getRemoteIp(), ':') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connection.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param bool $raw
|
||||
* @return bool
|
||||
*/
|
||||
public function close($data = null, $raw = false)
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->send($data, $raw);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the real socket.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getSocket()
|
||||
{
|
||||
return $this->_socket;
|
||||
}
|
||||
}
|
||||
189
vendor/workerman/workerman/Events/Ev.php
vendored
Normal file
189
vendor/workerman/workerman/Events/Ev.php
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
<?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 有个鬼<42765633@qq.com>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
use \EvWatcher;
|
||||
|
||||
/**
|
||||
* ev eventloop
|
||||
*/
|
||||
class Ev implements EventInterface
|
||||
{
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $_timerId = 1;
|
||||
|
||||
/**
|
||||
* Add a timer.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = null)
|
||||
{
|
||||
$callback = function ($event, $socket) use ($fd, $func) {
|
||||
try {
|
||||
\call_user_func($func, $fd);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
};
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$event = new \EvSignal($fd, $callback);
|
||||
$this->_eventSignal[$fd] = $event;
|
||||
return true;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$repeat = $flag === self::EV_TIMER_ONCE ? 0 : $fd;
|
||||
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
|
||||
$event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param);
|
||||
$this->_eventTimer[self::$_timerId] = $event;
|
||||
return self::$_timerId++;
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE;
|
||||
$event = new \EvIo($fd, $real_flag, $callback);
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a timer.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
$this->_allEvents[$fd_key][$flag]->stop();
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
$this->_eventSignal[$fd_key]->stop();
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
$this->_eventTimer[$fd]->stop();
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
*
|
||||
* @param EvWatcher $event
|
||||
*/
|
||||
public function timerCallback(EvWatcher $event)
|
||||
{
|
||||
$param = $event->data;
|
||||
$timer_id = $param[4];
|
||||
if ($param[2] === self::EV_TIMER_ONCE) {
|
||||
$this->_eventTimer[$timer_id]->stop();
|
||||
unset($this->_eventTimer[$timer_id]);
|
||||
}
|
||||
try {
|
||||
\call_user_func_array($param[0], $param[1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $event) {
|
||||
$event->stop();
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @see EventInterface::loop()
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
\Ev::run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
\Ev::stop(\Ev::BREAK_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
215
vendor/workerman/workerman/Events/Event.php
vendored
Normal file
215
vendor/workerman/workerman/Events/Event.php
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
<?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 有个鬼<42765633@qq.com>
|
||||
* @copyright 有个鬼<42765633@qq.com>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* libevent eventloop
|
||||
*/
|
||||
class Event implements EventInterface
|
||||
{
|
||||
/**
|
||||
* Event base.
|
||||
* @var object
|
||||
*/
|
||||
protected $_eventBase = null;
|
||||
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
* @var int
|
||||
*/
|
||||
protected static $_timerId = 1;
|
||||
|
||||
/**
|
||||
* construct
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (\class_exists('\\\\EventBase', false)) {
|
||||
$class_name = '\\\\EventBase';
|
||||
} else {
|
||||
$class_name = '\EventBase';
|
||||
}
|
||||
$this->_eventBase = new $class_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EventInterface::add()
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args=array())
|
||||
{
|
||||
if (\class_exists('\\\\Event', false)) {
|
||||
$class_name = '\\\\Event';
|
||||
} else {
|
||||
$class_name = '\Event';
|
||||
}
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
|
||||
$fd_key = (int)$fd;
|
||||
$event = $class_name::signal($this->_eventBase, $fd, $func);
|
||||
if (!$event||!$event->add()) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventSignal[$fd_key] = $event;
|
||||
return true;
|
||||
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
|
||||
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
|
||||
$event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param);
|
||||
if (!$event||!$event->addTimer($fd)) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventTimer[self::$_timerId] = $event;
|
||||
return self::$_timerId++;
|
||||
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST;
|
||||
$event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd);
|
||||
if (!$event||!$event->add()) {
|
||||
return false;
|
||||
}
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Events\EventInterface::del()
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
$this->_allEvents[$fd_key][$flag]->del();
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
$this->_eventSignal[$fd_key]->del();
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
$this->_eventTimer[$fd]->del();
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
* @param int|null $fd
|
||||
* @param int $what
|
||||
* @param int $timer_id
|
||||
*/
|
||||
public function timerCallback($fd, $what, $param)
|
||||
{
|
||||
$timer_id = $param[4];
|
||||
|
||||
if ($param[2] === self::EV_TIMER_ONCE) {
|
||||
$this->_eventTimer[$timer_id]->del();
|
||||
unset($this->_eventTimer[$timer_id]);
|
||||
}
|
||||
|
||||
try {
|
||||
\call_user_func_array($param[0], $param[1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Events\EventInterface::clearAllTimer()
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $event) {
|
||||
$event->del();
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see EventInterface::loop()
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
$this->_eventBase->loop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
$this->_eventBase->exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
107
vendor/workerman/workerman/Events/EventInterface.php
vendored
Normal file
107
vendor/workerman/workerman/Events/EventInterface.php
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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 Workerman\Events;
|
||||
|
||||
interface EventInterface
|
||||
{
|
||||
/**
|
||||
* Read event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_READ = 1;
|
||||
|
||||
/**
|
||||
* Write event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_WRITE = 2;
|
||||
|
||||
/**
|
||||
* Except event
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_EXCEPT = 3;
|
||||
|
||||
/**
|
||||
* Signal event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_SIGNAL = 4;
|
||||
|
||||
/**
|
||||
* Timer event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_TIMER = 8;
|
||||
|
||||
/**
|
||||
* Timer once event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_TIMER_ONCE = 16;
|
||||
|
||||
/**
|
||||
* Add event listener to event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @param callable $func
|
||||
* @param array $args
|
||||
* @return bool
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array());
|
||||
|
||||
/**
|
||||
* Remove event listener from event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @return bool
|
||||
*/
|
||||
public function del($fd, $flag);
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer();
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop();
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function destroy();
|
||||
|
||||
/**
|
||||
* Get Timer count.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimerCount();
|
||||
}
|
||||
225
vendor/workerman/workerman/Events/Libevent.php
vendored
Normal file
225
vendor/workerman/workerman/Events/Libevent.php
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
<?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 Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* libevent eventloop
|
||||
*/
|
||||
class Libevent implements EventInterface
|
||||
{
|
||||
/**
|
||||
* Event base.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $_eventBase = null;
|
||||
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* construct
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventBase = \event_base_new();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = \EV_SIGNAL | \EV_PERSIST;
|
||||
$this->_eventSignal[$fd_key] = \event_new();
|
||||
if (!\event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) {
|
||||
return false;
|
||||
}
|
||||
if (!\event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
if (!\event_add($this->_eventSignal[$fd_key])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$event = \event_new();
|
||||
$timer_id = (int)$event;
|
||||
if (!\event_set($event, 0, \EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\event_base_set($event, $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time_interval = $fd * 1000000;
|
||||
if (!\event_add($event, $time_interval)) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval);
|
||||
return $timer_id;
|
||||
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? \EV_READ | \EV_PERSIST : \EV_WRITE | \EV_PERSIST;
|
||||
|
||||
$event = \event_new();
|
||||
|
||||
if (!\event_set($event, $fd, $real_flag, $func, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\event_base_set($event, $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\event_add($event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
\event_del($this->_allEvents[$fd_key][$flag]);
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
\event_del($this->_eventSignal[$fd_key]);
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
// 这里 fd 为timerid
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
\event_del($this->_eventTimer[$fd][2]);
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
*
|
||||
* @param mixed $_null1
|
||||
* @param int $_null2
|
||||
* @param mixed $timer_id
|
||||
*/
|
||||
protected function timerCallback($_null1, $_null2, $timer_id)
|
||||
{
|
||||
if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) {
|
||||
\event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]);
|
||||
}
|
||||
try {
|
||||
\call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
|
||||
$this->del($timer_id, self::EV_TIMER_ONCE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $task_data) {
|
||||
\event_del($task_data[2]);
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
\event_base_loop($this->_eventBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
foreach ($this->_eventSignal as $event) {
|
||||
\event_del($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
|
||||
264
vendor/workerman/workerman/Events/React/Base.php
vendored
Normal file
264
vendor/workerman/workerman/Events/React/Base.php
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
<?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 Workerman\Events\React;
|
||||
|
||||
use Workerman\Events\EventInterface;
|
||||
use React\EventLoop\TimerInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* Class StreamSelectLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class Base implements LoopInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_timerIdMap = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $_timerIdIndex = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_signalHandlerMap = array();
|
||||
|
||||
/**
|
||||
* @var LoopInterface
|
||||
*/
|
||||
protected $_eventLoop = null;
|
||||
|
||||
/**
|
||||
* Base constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listener to event loop.
|
||||
*
|
||||
* @param int $fd
|
||||
* @param int $flag
|
||||
* @param callable $func
|
||||
* @param array $args
|
||||
* @return bool
|
||||
*/
|
||||
public function add($fd, $flag, $func, ?array $args = array())
|
||||
{
|
||||
$args = (array)$args;
|
||||
switch ($flag) {
|
||||
case EventInterface::EV_READ:
|
||||
return $this->addReadStream($fd, $func);
|
||||
case EventInterface::EV_WRITE:
|
||||
return $this->addWriteStream($fd, $func);
|
||||
case EventInterface::EV_SIGNAL:
|
||||
if (isset($this->_signalHandlerMap[$fd])) {
|
||||
$this->removeSignal($fd, $this->_signalHandlerMap[$fd]);
|
||||
}
|
||||
$this->_signalHandlerMap[$fd] = $func;
|
||||
return $this->addSignal($fd, $func);
|
||||
case EventInterface::EV_TIMER:
|
||||
$timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) {
|
||||
\call_user_func_array($func, $args);
|
||||
});
|
||||
$this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj;
|
||||
return $this->_timerIdIndex;
|
||||
case EventInterface::EV_TIMER_ONCE:
|
||||
$index = ++$this->_timerIdIndex;
|
||||
$timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) {
|
||||
$this->del($index,EventInterface::EV_TIMER_ONCE);
|
||||
\call_user_func_array($func, $args);
|
||||
});
|
||||
$this->_timerIdMap[$index] = $timer_obj;
|
||||
return $this->_timerIdIndex;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listener from event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @return bool
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case EventInterface::EV_READ:
|
||||
return $this->removeReadStream($fd);
|
||||
case EventInterface::EV_WRITE:
|
||||
return $this->removeWriteStream($fd);
|
||||
case EventInterface::EV_SIGNAL:
|
||||
if (!isset($this->_eventLoop[$fd])) {
|
||||
return false;
|
||||
}
|
||||
$func = $this->_eventLoop[$fd];
|
||||
unset($this->_eventLoop[$fd]);
|
||||
return $this->removeSignal($fd, $func);
|
||||
|
||||
case EventInterface::EV_TIMER:
|
||||
case EventInterface::EV_TIMER_ONCE:
|
||||
if (isset($this->_timerIdMap[$fd])){
|
||||
$timer_obj = $this->_timerIdMap[$fd];
|
||||
unset($this->_timerIdMap[$fd]);
|
||||
$this->cancelTimer($timer_obj);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
$this->run();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_timerIdMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addReadStream($stream, $listener)
|
||||
{
|
||||
return $this->_eventLoop->addReadStream($stream, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addWriteStream($stream, $listener)
|
||||
{
|
||||
return $this->_eventLoop->addWriteStream($stream, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
public function removeReadStream($stream)
|
||||
{
|
||||
return $this->_eventLoop->removeReadStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $stream
|
||||
*/
|
||||
public function removeWriteStream($stream)
|
||||
{
|
||||
return $this->_eventLoop->removeWriteStream($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|int $interval
|
||||
* @param callable $callback
|
||||
* @return \React\EventLoop\Timer\Timer|TimerInterface
|
||||
*/
|
||||
public function addTimer($interval, $callback)
|
||||
{
|
||||
return $this->_eventLoop->addTimer($interval, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|int $interval
|
||||
* @param callable $callback
|
||||
* @return \React\EventLoop\Timer\Timer|TimerInterface
|
||||
*/
|
||||
public function addPeriodicTimer($interval, $callback)
|
||||
{
|
||||
return $this->_eventLoop->addPeriodicTimer($interval, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TimerInterface $timer
|
||||
*/
|
||||
public function cancelTimer(TimerInterface $timer)
|
||||
{
|
||||
return $this->_eventLoop->cancelTimer($timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function futureTick($listener)
|
||||
{
|
||||
return $this->_eventLoop->futureTick($listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function addSignal($signal, $listener)
|
||||
{
|
||||
return $this->_eventLoop->addSignal($signal, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $signal
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function removeSignal($signal, $listener)
|
||||
{
|
||||
return $this->_eventLoop->removeSignal($signal, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
return $this->_eventLoop->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop.
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
return $this->_eventLoop->stop();
|
||||
}
|
||||
}
|
||||
27
vendor/workerman/workerman/Events/React/ExtEventLoop.php
vendored
Normal file
27
vendor/workerman/workerman/Events/React/ExtEventLoop.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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 Workerman\Events\React;
|
||||
|
||||
/**
|
||||
* Class ExtEventLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class ExtEventLoop extends Base
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\ExtEventLoop();
|
||||
}
|
||||
}
|
||||
27
vendor/workerman/workerman/Events/React/ExtLibEventLoop.php
vendored
Normal file
27
vendor/workerman/workerman/Events/React/ExtLibEventLoop.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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 Workerman\Events\React;
|
||||
use Workerman\Events\EventInterface;
|
||||
|
||||
/**
|
||||
* Class ExtLibEventLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class ExtLibEventLoop extends Base
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\ExtLibeventLoop();
|
||||
}
|
||||
}
|
||||
26
vendor/workerman/workerman/Events/React/StreamSelectLoop.php
vendored
Normal file
26
vendor/workerman/workerman/Events/React/StreamSelectLoop.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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 Workerman\Events\React;
|
||||
|
||||
/**
|
||||
* Class StreamSelectLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class StreamSelectLoop extends Base
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
|
||||
}
|
||||
}
|
||||
357
vendor/workerman/workerman/Events/Select.php
vendored
Normal file
357
vendor/workerman/workerman/Events/Select.php
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
<?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 Workerman\Events;
|
||||
|
||||
use Throwable;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* select eventloop
|
||||
*/
|
||||
class Select implements EventInterface
|
||||
{
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $_signalEvents = array();
|
||||
|
||||
/**
|
||||
* Fds waiting for read event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_readFds = array();
|
||||
|
||||
/**
|
||||
* Fds waiting for write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_writeFds = array();
|
||||
|
||||
/**
|
||||
* Fds waiting for except event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_exceptFds = array();
|
||||
|
||||
/**
|
||||
* Timer scheduler.
|
||||
* {['data':timer_id, 'priority':run_timestamp], ..}
|
||||
*
|
||||
* @var \SplPriorityQueue
|
||||
*/
|
||||
protected $_scheduler = null;
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [[func, args, flag, timer_interval], ..]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_timerId = 1;
|
||||
|
||||
/**
|
||||
* Select timeout.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_selectTimeout = 100000000;
|
||||
|
||||
/**
|
||||
* Paired socket channels
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $channel = array();
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Init SplPriorityQueue.
|
||||
$this->_scheduler = new \SplPriorityQueue();
|
||||
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$count = $flag === self::EV_READ ? \count($this->_readFds) : \count($this->_writeFds);
|
||||
if ($count >= 1024) {
|
||||
echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n";
|
||||
} else if (\DIRECTORY_SEPARATOR !== '/' && $count >= 256) {
|
||||
echo "Warning: system call select exceeded the maximum number of connections 256.\n";
|
||||
}
|
||||
$fd_key = (int)$fd;
|
||||
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
|
||||
if ($flag === self::EV_READ) {
|
||||
$this->_readFds[$fd_key] = $fd;
|
||||
} else {
|
||||
$this->_writeFds[$fd_key] = $fd;
|
||||
}
|
||||
break;
|
||||
case self::EV_EXCEPT:
|
||||
$fd_key = (int)$fd;
|
||||
$this->_allEvents[$fd_key][$flag] = array($func, $fd);
|
||||
$this->_exceptFds[$fd_key] = $fd;
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
// Windows not support signal.
|
||||
if(\DIRECTORY_SEPARATOR !== '/') {
|
||||
return false;
|
||||
}
|
||||
$fd_key = (int)$fd;
|
||||
$this->_signalEvents[$fd_key][$flag] = array($func, $fd);
|
||||
\pcntl_signal($fd, array($this, 'signalHandler'));
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$timer_id = $this->_timerId++;
|
||||
$run_time = \microtime(true) + $fd;
|
||||
$this->_scheduler->insert($timer_id, -$run_time);
|
||||
$this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd);
|
||||
$select_timeout = ($run_time - \microtime(true)) * 1000000;
|
||||
$select_timeout = $select_timeout <= 0 ? 1 : $select_timeout;
|
||||
if( $this->_selectTimeout > $select_timeout ){
|
||||
$this->_selectTimeout = (int) $select_timeout;
|
||||
}
|
||||
return $timer_id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal handler.
|
||||
*
|
||||
* @param int $signal
|
||||
*/
|
||||
public function signalHandler($signal)
|
||||
{
|
||||
\call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
$fd_key = (int)$fd;
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]);
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
return true;
|
||||
case self::EV_WRITE:
|
||||
unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]);
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
return true;
|
||||
case self::EV_EXCEPT:
|
||||
unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]);
|
||||
if(empty($this->_allEvents[$fd_key]))
|
||||
{
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
return true;
|
||||
case self::EV_SIGNAL:
|
||||
if(\DIRECTORY_SEPARATOR !== '/') {
|
||||
return false;
|
||||
}
|
||||
unset($this->_signalEvents[$fd_key]);
|
||||
\pcntl_signal($fd, SIG_IGN);
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE;
|
||||
unset($this->_eventTimer[$fd_key]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick for timer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function tick()
|
||||
{
|
||||
$tasks_to_insert = [];
|
||||
while (!$this->_scheduler->isEmpty()) {
|
||||
$scheduler_data = $this->_scheduler->top();
|
||||
$timer_id = $scheduler_data['data'];
|
||||
$next_run_time = -$scheduler_data['priority'];
|
||||
$time_now = \microtime(true);
|
||||
$this->_selectTimeout = (int) (($next_run_time - $time_now) * 1000000);
|
||||
if ($this->_selectTimeout <= 0) {
|
||||
$this->_scheduler->extract();
|
||||
|
||||
if (!isset($this->_eventTimer[$timer_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// [func, args, flag, timer_interval]
|
||||
$task_data = $this->_eventTimer[$timer_id];
|
||||
if ($task_data[2] === self::EV_TIMER) {
|
||||
$next_run_time = $time_now + $task_data[3];
|
||||
$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);
|
||||
}
|
||||
if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) {
|
||||
$this->del($timer_id, self::EV_TIMER_ONCE);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
$this->_scheduler = new \SplPriorityQueue();
|
||||
$this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH);
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
while (1) {
|
||||
if(\DIRECTORY_SEPARATOR === '/') {
|
||||
// Calls signal handlers for pending signals
|
||||
\pcntl_signal_dispatch();
|
||||
}
|
||||
|
||||
$read = $this->_readFds;
|
||||
$write = $this->_writeFds;
|
||||
$except = $this->_exceptFds;
|
||||
$ret = false;
|
||||
|
||||
if ($read || $write || $except) {
|
||||
// Waiting read/write/signal/timeout events.
|
||||
try {
|
||||
$ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout);
|
||||
} catch (\Exception $e) {} catch (\Error $e) {}
|
||||
|
||||
} else {
|
||||
$this->_selectTimeout >= 1 && usleep($this->_selectTimeout);
|
||||
}
|
||||
|
||||
if (!$this->_scheduler->isEmpty()) {
|
||||
$this->tick();
|
||||
}
|
||||
|
||||
if (!$ret) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($read) {
|
||||
foreach ($read as $fd) {
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][self::EV_READ])) {
|
||||
\call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0],
|
||||
array($this->_allEvents[$fd_key][self::EV_READ][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($write) {
|
||||
foreach ($write as $fd) {
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) {
|
||||
\call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0],
|
||||
array($this->_allEvents[$fd_key][self::EV_WRITE][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($except) {
|
||||
foreach($except as $fd) {
|
||||
$fd_key = (int) $fd;
|
||||
if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) {
|
||||
\call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0],
|
||||
array($this->_allEvents[$fd_key][self::EV_EXCEPT][1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
285
vendor/workerman/workerman/Events/Swoole.php
vendored
Normal file
285
vendor/workerman/workerman/Events/Swoole.php
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
<?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 Ares<aresrr#qq.com>
|
||||
* @link http://www.workerman.net/
|
||||
* @link https://github.com/ares333/Workerman
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
use Swoole\Event;
|
||||
use Swoole\Timer;
|
||||
use Swoole\Coroutine;
|
||||
|
||||
class Swoole implements EventInterface
|
||||
{
|
||||
|
||||
protected $_timer = array();
|
||||
|
||||
protected $_timerOnceMap = array();
|
||||
|
||||
protected $mapId = 0;
|
||||
|
||||
protected $_fd = array();
|
||||
|
||||
// milisecond
|
||||
public static $signalDispatchInterval = 500;
|
||||
|
||||
protected $_hasSignal = false;
|
||||
|
||||
protected $_readEvents = array();
|
||||
|
||||
protected $_writeEvents = array();
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::add()
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$res = \pcntl_signal($fd, $func, false);
|
||||
if (! $this->_hasSignal && $res) {
|
||||
Timer::tick(static::$signalDispatchInterval,
|
||||
function () {
|
||||
\pcntl_signal_dispatch();
|
||||
});
|
||||
$this->_hasSignal = true;
|
||||
}
|
||||
return $res;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$method = self::EV_TIMER === $flag ? 'tick' : 'after';
|
||||
if ($this->mapId > \PHP_INT_MAX) {
|
||||
$this->mapId = 0;
|
||||
}
|
||||
$mapId = $this->mapId++;
|
||||
$t = (int)($fd * 1000);
|
||||
if ($t < 1) {
|
||||
$t = 1;
|
||||
}
|
||||
$timer_id = Timer::$method($t,
|
||||
function ($timer_id = null) use ($func, $args, $mapId) {
|
||||
try {
|
||||
\call_user_func_array($func, (array)$args);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
// EV_TIMER_ONCE
|
||||
if (! isset($timer_id)) {
|
||||
// may be deleted in $func
|
||||
if (\array_key_exists($mapId, $this->_timerOnceMap)) {
|
||||
$timer_id = $this->_timerOnceMap[$mapId];
|
||||
unset($this->_timer[$timer_id],
|
||||
$this->_timerOnceMap[$mapId]);
|
||||
}
|
||||
}
|
||||
});
|
||||
if ($flag === self::EV_TIMER_ONCE) {
|
||||
$this->_timerOnceMap[$mapId] = $timer_id;
|
||||
$this->_timer[$timer_id] = $mapId;
|
||||
} else {
|
||||
$this->_timer[$timer_id] = null;
|
||||
}
|
||||
return $timer_id;
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int) $fd;
|
||||
if ($flag === self::EV_READ) {
|
||||
$this->_readEvents[$fd_key] = $func;
|
||||
} else {
|
||||
$this->_writeEvents[$fd_key] = $func;
|
||||
}
|
||||
if (!isset($this->_fd[$fd_key])) {
|
||||
if ($flag === self::EV_READ) {
|
||||
$res = Event::add($fd, [$this, 'callRead'], null, SWOOLE_EVENT_READ);
|
||||
$fd_type = SWOOLE_EVENT_READ;
|
||||
} else {
|
||||
$res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE);
|
||||
$fd_type = SWOOLE_EVENT_WRITE;
|
||||
}
|
||||
if ($res) {
|
||||
$this->_fd[$fd_key] = $fd_type;
|
||||
}
|
||||
} else {
|
||||
$fd_val = $this->_fd[$fd_key];
|
||||
$res = true;
|
||||
if ($flag === self::EV_READ) {
|
||||
if (($fd_val & SWOOLE_EVENT_READ) !== SWOOLE_EVENT_READ) {
|
||||
$res = Event::set($fd, $func, null,
|
||||
SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE);
|
||||
$this->_fd[$fd_key] |= SWOOLE_EVENT_READ;
|
||||
}
|
||||
} else {
|
||||
if (($fd_val & SWOOLE_EVENT_WRITE) !== SWOOLE_EVENT_WRITE) {
|
||||
$res = Event::set($fd, null, $func,
|
||||
SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE);
|
||||
$this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fd
|
||||
* @return void
|
||||
*/
|
||||
protected function callRead($stream)
|
||||
{
|
||||
$fd = (int) $stream;
|
||||
if (isset($this->_readEvents[$fd])) {
|
||||
try {
|
||||
\call_user_func($this->_readEvents[$fd], $stream);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fd
|
||||
* @return void
|
||||
*/
|
||||
protected function callWrite($stream)
|
||||
{
|
||||
$fd = (int) $stream;
|
||||
if (isset($this->_writeEvents[$fd])) {
|
||||
try {
|
||||
\call_user_func($this->_writeEvents[$fd], $stream);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::del()
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
return \pcntl_signal($fd, SIG_IGN, false);
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
// already remove in EV_TIMER_ONCE callback.
|
||||
if (! \array_key_exists($fd, $this->_timer)) {
|
||||
return true;
|
||||
}
|
||||
$res = Timer::clear($fd);
|
||||
if ($res) {
|
||||
$mapId = $this->_timer[$fd];
|
||||
if (isset($mapId)) {
|
||||
unset($this->_timerOnceMap[$mapId]);
|
||||
}
|
||||
unset($this->_timer[$fd]);
|
||||
}
|
||||
return $res;
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int) $fd;
|
||||
if ($flag === self::EV_READ) {
|
||||
unset($this->_readEvents[$fd_key]);
|
||||
} elseif ($flag === self::EV_WRITE) {
|
||||
unset($this->_writeEvents[$fd_key]);
|
||||
}
|
||||
if (isset($this->_fd[$fd_key])) {
|
||||
$fd_val = $this->_fd[$fd_key];
|
||||
if ($flag === self::EV_READ) {
|
||||
$flag_remove = ~ SWOOLE_EVENT_READ;
|
||||
} else {
|
||||
$flag_remove = ~ SWOOLE_EVENT_WRITE;
|
||||
}
|
||||
$fd_val &= $flag_remove;
|
||||
if (0 === $fd_val) {
|
||||
$res = Event::del($fd);
|
||||
if ($res) {
|
||||
unset($this->_fd[$fd_key]);
|
||||
}
|
||||
} else {
|
||||
$res = Event::set($fd, null, null, $fd_val);
|
||||
if ($res) {
|
||||
$this->_fd[$fd_key] = $fd_val;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$res = true;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::clearAllTimer()
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach (array_keys($this->_timer) as $v) {
|
||||
Timer::clear($v);
|
||||
}
|
||||
$this->_timer = array();
|
||||
$this->_timerOnceMap = array();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::loop()
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
Event::wait();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::destroy()
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
foreach (Coroutine::listCoroutines() as $coroutine) {
|
||||
Coroutine::cancel($coroutine);
|
||||
}
|
||||
// Wait for coroutines to exit
|
||||
usleep(100000);
|
||||
Event::exit();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Workerman\Events\EventInterface::getTimerCount()
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_timer);
|
||||
}
|
||||
}
|
||||
260
vendor/workerman/workerman/Events/Uv.php
vendored
Normal file
260
vendor/workerman/workerman/Events/Uv.php
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
<?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\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* libuv eventloop
|
||||
*/
|
||||
class Uv implements EventInterface
|
||||
{
|
||||
/**
|
||||
* Event Loop.
|
||||
* @var object
|
||||
*/
|
||||
protected $_eventLoop = null;
|
||||
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* Timer id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $_timerId = 1;
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*
|
||||
* @param object $loop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(\UVLoop $loop = null)
|
||||
{
|
||||
if(!extension_loaded('uv'))
|
||||
{
|
||||
throw new \Exception(__CLASS__ . ' requires the UV extension, but detected it has NOT been installed yet.');
|
||||
}
|
||||
|
||||
if(empty($loop) || !$loop instanceof \UVLoop)
|
||||
{
|
||||
$this->_eventLoop = \uv_default_loop();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_eventLoop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add a timer
|
||||
*
|
||||
* @param resource $fd
|
||||
* @param int $flag
|
||||
* @param callback $func
|
||||
* @param mixed $args
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = null)
|
||||
{
|
||||
switch ($flag)
|
||||
{
|
||||
case self::EV_SIGNAL:
|
||||
$signalCallback = function($watcher, $socket)use($func, $fd){
|
||||
try {
|
||||
\call_user_func($func, $fd);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
};
|
||||
$signalWatcher = \uv_signal_init();
|
||||
\uv_signal_start($signalWatcher, $signalCallback, $fd);
|
||||
$this->_eventSignal[$fd] = $signalWatcher;
|
||||
return true;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$repeat = $flag === self::EV_TIMER_ONCE ? 0 : (int)($fd * 1000);
|
||||
$param = array($func, (array)$args, $flag, $fd, self::$_timerId);
|
||||
$timerWatcher = \uv_timer_init();
|
||||
\uv_timer_start($timerWatcher, ($flag === self::EV_TIMER_ONCE ? (int)($fd * 1000) :1), $repeat, function($watcher)use($param){
|
||||
call_user_func_array([$this, 'timerCallback'], [$param]);
|
||||
});
|
||||
$this->_eventTimer[self::$_timerId] = $timerWatcher;
|
||||
return self::$_timerId++;
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
$ioCallback = function($watcher, $status, $events, $fd)use($func){
|
||||
try {
|
||||
\call_user_func($func, $fd);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
};
|
||||
$ioWatcher = \uv_poll_init($this->_eventLoop, $fd);
|
||||
$real_flag = $flag === self::EV_READ ? \Uv::READABLE : \Uv::WRITABLE;
|
||||
\uv_poll_start($ioWatcher, $real_flag, $ioCallback);
|
||||
$this->_allEvents[$fd_key][$flag] = $ioWatcher;
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a timer
|
||||
*
|
||||
* @param resource $fd
|
||||
* @param int $flag
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag)
|
||||
{
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
$watcher = $this->_allEvents[$fd_key][$flag];
|
||||
\uv_is_active($watcher) && \uv_poll_stop($watcher);
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
$watcher = $this->_eventSignal[$fd_key];
|
||||
\uv_is_active($watcher) && \uv_signal_stop($watcher);
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
$watcher = $this->_eventTimer[$fd];
|
||||
\uv_is_active($watcher) && \uv_timer_stop($watcher);
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Timer callback
|
||||
*
|
||||
* @param array $input
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function timerCallback($input)
|
||||
{
|
||||
if(!is_array($input)) return;
|
||||
|
||||
$timer_id = $input[4];
|
||||
|
||||
if ($input[2] === self::EV_TIMER_ONCE)
|
||||
{
|
||||
$watcher = $this->_eventTimer[$timer_id];
|
||||
\uv_is_active($watcher) && \uv_timer_stop($watcher);
|
||||
unset($this->_eventTimer[$timer_id]);
|
||||
}
|
||||
|
||||
try {
|
||||
\call_user_func_array($input[0], $input[1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove all timers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
if(!is_array($this->_eventTimer)) return;
|
||||
|
||||
foreach($this->_eventTimer as $watcher)
|
||||
{
|
||||
\uv_is_active($watcher) && \uv_timer_stop($watcher);
|
||||
}
|
||||
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start loop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
\Uv_run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy loop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
!empty($this->_eventLoop) && \uv_loop_delete($this->_eventLoop);
|
||||
$this->_allEvents = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get timer count
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
44
vendor/workerman/workerman/Lib/Constants.php
vendored
Normal file
44
vendor/workerman/workerman/Lib/Constants.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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>
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*
|
||||
* @link http://www.workerman.net/
|
||||
*/
|
||||
|
||||
// Pcre.jit is not stable, temporarily disabled.
|
||||
ini_set('pcre.jit', 0);
|
||||
|
||||
// For onError callback.
|
||||
const WORKERMAN_CONNECT_FAIL = 1;
|
||||
// For onError callback.
|
||||
const WORKERMAN_SEND_FAIL = 2;
|
||||
|
||||
// Define OS Type
|
||||
const OS_TYPE_LINUX = 'linux';
|
||||
const OS_TYPE_WINDOWS = 'windows';
|
||||
|
||||
// Compatible with php7
|
||||
if (!class_exists('Error')) {
|
||||
class Error extends Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!interface_exists('SessionHandlerInterface')) {
|
||||
interface SessionHandlerInterface {
|
||||
public function close();
|
||||
public function destroy($session_id);
|
||||
public function gc($maxlifetime);
|
||||
public function open($save_path ,$session_name);
|
||||
public function read($session_id);
|
||||
public function write($session_id , $session_data);
|
||||
}
|
||||
}
|
||||
22
vendor/workerman/workerman/Lib/Timer.php
vendored
Normal file
22
vendor/workerman/workerman/Lib/Timer.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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 Workerman\Lib;
|
||||
|
||||
/**
|
||||
* Do not use Workerman\Lib\Timer.
|
||||
* Please use Workerman\Timer.
|
||||
* This class is only used for compatibility with workerman 3.*
|
||||
* @package Workerman\Lib
|
||||
*/
|
||||
class Timer extends \Workerman\Timer {}
|
||||
21
vendor/workerman/workerman/MIT-LICENSE.txt
vendored
Normal file
21
vendor/workerman/workerman/MIT-LICENSE.txt
vendored
Normal 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.
|
||||
61
vendor/workerman/workerman/Protocols/Frame.php
vendored
Normal file
61
vendor/workerman/workerman/Protocols/Frame.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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 Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
/**
|
||||
* Frame Protocol.
|
||||
*/
|
||||
class Frame
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, TcpConnection $connection)
|
||||
{
|
||||
if (\strlen($buffer) < 4) {
|
||||
return 0;
|
||||
}
|
||||
$unpack_data = \unpack('Ntotal_length', $buffer);
|
||||
return $unpack_data['total_length'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer)
|
||||
{
|
||||
return \substr($buffer, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer)
|
||||
{
|
||||
$total_length = 4 + \strlen($buffer);
|
||||
return \pack('N', $total_length) . $buffer;
|
||||
}
|
||||
}
|
||||
323
vendor/workerman/workerman/Protocols/Http.php
vendored
Normal file
323
vendor/workerman/workerman/Protocols/Http.php
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
<?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 Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\Http\Request;
|
||||
use Workerman\Protocols\Http\Response;
|
||||
use Workerman\Protocols\Http\Session;
|
||||
use Workerman\Protocols\Websocket;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Http.
|
||||
* @package Workerman\Protocols
|
||||
*/
|
||||
class Http
|
||||
{
|
||||
/**
|
||||
* Request class name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_requestClass = 'Workerman\Protocols\Http\Request';
|
||||
|
||||
/**
|
||||
* Upload tmp dir.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_uploadTmpDir = '';
|
||||
|
||||
/**
|
||||
* Open cache.
|
||||
*
|
||||
* @var bool.
|
||||
*/
|
||||
protected static $_enableCache = true;
|
||||
|
||||
/**
|
||||
* Get or set session name.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return string
|
||||
*/
|
||||
public static function sessionName($name = null)
|
||||
{
|
||||
if ($name !== null && $name !== '') {
|
||||
Session::$name = (string)$name;
|
||||
}
|
||||
return Session::$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the request class name.
|
||||
*
|
||||
* @param string|null $class_name
|
||||
* @return string
|
||||
*/
|
||||
public static function requestClass($class_name = null)
|
||||
{
|
||||
if ($class_name) {
|
||||
static::$_requestClass = $class_name;
|
||||
}
|
||||
return static::$_requestClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable Cache.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function enableCache($value)
|
||||
{
|
||||
static::$_enableCache = (bool)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($recv_buffer, TcpConnection $connection)
|
||||
{
|
||||
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 (\strlen($recv_buffer) >= 16384) {
|
||||
$connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
$length = $crlf_pos + 4;
|
||||
$method = \strstr($recv_buffer, ' ', true);
|
||||
|
||||
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);
|
||||
if ($pos = \strpos($header, "\r\nContent-Length: ")) {
|
||||
$length = $length + (int)\substr($header, $pos + 18, 10);
|
||||
$has_content_length = true;
|
||||
} else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) {
|
||||
$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 ($has_content_length) {
|
||||
if ($length > $connection->maxPackageSize) {
|
||||
$connection->close("HTTP/1.1 413 Request Entity Too Large\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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http decode.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return \Workerman\Protocols\Http\Request
|
||||
*/
|
||||
public static function decode($recv_buffer, TcpConnection $connection)
|
||||
{
|
||||
static $requests = array();
|
||||
$cacheable = static::$_enableCache && !isset($recv_buffer[512]);
|
||||
if (true === $cacheable && isset($requests[$recv_buffer])) {
|
||||
$request = $requests[$recv_buffer];
|
||||
$request->connection = $connection;
|
||||
$connection->__request = $request;
|
||||
$request->properties = array();
|
||||
return $request;
|
||||
}
|
||||
$request = new static::$_requestClass($recv_buffer);
|
||||
$request->connection = $connection;
|
||||
$connection->__request = $request;
|
||||
if (true === $cacheable) {
|
||||
$requests[$recv_buffer] = $request;
|
||||
if (\count($requests) > 512) {
|
||||
unset($requests[key($requests)]);
|
||||
}
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Http encode.
|
||||
*
|
||||
* @param string|Response $response
|
||||
* @param TcpConnection $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($response, TcpConnection $connection)
|
||||
{
|
||||
if (isset($connection->__request)) {
|
||||
$connection->__request->session = null;
|
||||
$connection->__request->connection = null;
|
||||
$connection->__request = null;
|
||||
}
|
||||
if (!\is_object($response)) {
|
||||
$ext_header = '';
|
||||
if (isset($connection->__header)) {
|
||||
foreach ($connection->__header as $name => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
$ext_header = "$name: $item\r\n";
|
||||
}
|
||||
} else {
|
||||
$ext_header = "$name: $value\r\n";
|
||||
}
|
||||
}
|
||||
unset($connection->__header);
|
||||
}
|
||||
$body_len = \strlen((string)$response);
|
||||
return "HTTP/1.1 200 OK\r\nServer: workerman\r\n{$ext_header}Connection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\n\r\n$response";
|
||||
}
|
||||
|
||||
if (isset($connection->__header)) {
|
||||
$response->withHeaders($connection->__header);
|
||||
unset($connection->__header);
|
||||
}
|
||||
|
||||
if (isset($response->file)) {
|
||||
$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(
|
||||
'Content-Length' => $body_len,
|
||||
'Accept-Ranges' => 'bytes',
|
||||
));
|
||||
if ($offset || $length) {
|
||||
$offset_end = $offset + $body_len - 1;
|
||||
$response->header('Content-Range', "bytes $offset-$offset_end/$file_size");
|
||||
}
|
||||
if ($body_len < 2 * 1024 * 1024) {
|
||||
$connection->send((string)$response . file_get_contents($file, false, null, $offset, $body_len), true);
|
||||
return '';
|
||||
}
|
||||
$handler = \fopen($file, 'r');
|
||||
if (false === $handler) {
|
||||
$connection->close(new Response(403, null, '403 Forbidden'));
|
||||
return '';
|
||||
}
|
||||
$connection->send((string)$response, true);
|
||||
static::sendStream($connection, $handler, $offset, $length);
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string)$response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send remainder of a stream to client.
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
* @param resource $handler
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
*/
|
||||
protected static function sendStream(TcpConnection $connection, $handler, $offset = 0, $length = 0)
|
||||
{
|
||||
$connection->bufferFull = false;
|
||||
if ($offset !== 0) {
|
||||
\fseek($handler, $offset);
|
||||
}
|
||||
$offset_end = $offset + $length;
|
||||
// Read file content from disk piece by piece and send to client.
|
||||
$do_write = function () use ($connection, $handler, $length, $offset_end) {
|
||||
// Send buffer not full.
|
||||
while ($connection->bufferFull === false) {
|
||||
// Read from disk.
|
||||
$size = 1024 * 1024;
|
||||
if ($length !== 0) {
|
||||
$tell = \ftell($handler);
|
||||
$remain_size = $offset_end - $tell;
|
||||
if ($remain_size <= 0) {
|
||||
fclose($handler);
|
||||
$connection->onBufferDrain = null;
|
||||
return;
|
||||
}
|
||||
$size = $remain_size > $size ? $size : $remain_size;
|
||||
}
|
||||
|
||||
$buffer = \fread($handler, $size);
|
||||
// Read eof.
|
||||
if ($buffer === '' || $buffer === false) {
|
||||
fclose($handler);
|
||||
$connection->onBufferDrain = null;
|
||||
return;
|
||||
}
|
||||
$connection->send($buffer, true);
|
||||
}
|
||||
};
|
||||
// Send buffer full.
|
||||
$connection->onBufferFull = function ($connection) {
|
||||
$connection->bufferFull = true;
|
||||
};
|
||||
// Send buffer drain.
|
||||
$connection->onBufferDrain = function ($connection) use ($do_write) {
|
||||
$connection->bufferFull = false;
|
||||
$do_write();
|
||||
};
|
||||
$do_write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or get uploadTmpDir.
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function uploadTmpDir($dir = null)
|
||||
{
|
||||
if (null !== $dir) {
|
||||
static::$_uploadTmpDir = $dir;
|
||||
}
|
||||
if (static::$_uploadTmpDir === '') {
|
||||
if ($upload_tmp_dir = \ini_get('upload_tmp_dir')) {
|
||||
static::$_uploadTmpDir = $upload_tmp_dir;
|
||||
} else if ($upload_tmp_dir = \sys_get_temp_dir()) {
|
||||
static::$_uploadTmpDir = $upload_tmp_dir;
|
||||
}
|
||||
}
|
||||
return static::$_uploadTmpDir;
|
||||
}
|
||||
}
|
||||
48
vendor/workerman/workerman/Protocols/Http/Chunk.php
vendored
Normal file
48
vendor/workerman/workerman/Protocols/Http/Chunk.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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 Workerman\Protocols\Http;
|
||||
|
||||
|
||||
/**
|
||||
* Class Chunk
|
||||
* @package Workerman\Protocols\Http
|
||||
*/
|
||||
class Chunk
|
||||
{
|
||||
/**
|
||||
* Chunk buffer.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_buffer = null;
|
||||
|
||||
/**
|
||||
* Chunk constructor.
|
||||
* @param string $buffer
|
||||
*/
|
||||
public function __construct($buffer)
|
||||
{
|
||||
$this->_buffer = $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* __toString
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return \dechex(\strlen($this->_buffer))."\r\n$this->_buffer\r\n";
|
||||
}
|
||||
}
|
||||
694
vendor/workerman/workerman/Protocols/Http/Request.php
vendored
Normal file
694
vendor/workerman/workerman/Protocols/Http/Request.php
vendored
Normal file
@@ -0,0 +1,694 @@
|
||||
<?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 Workerman\Protocols\Http;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\Http\Session;
|
||||
use Workerman\Protocols\Http;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Request
|
||||
* @package Workerman\Protocols\Http
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* Connection.
|
||||
*
|
||||
* @var TcpConnection
|
||||
*/
|
||||
public $connection = null;
|
||||
|
||||
/**
|
||||
* Session instance.
|
||||
*
|
||||
* @var Session
|
||||
*/
|
||||
public $session = null;
|
||||
|
||||
/**
|
||||
* Properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $properties = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public static $maxFileUploads = 1024;
|
||||
|
||||
/**
|
||||
* Http buffer.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_buffer = null;
|
||||
|
||||
/**
|
||||
* Request data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_data = null;
|
||||
|
||||
/**
|
||||
* Enable cache.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $_enableCache = true;
|
||||
|
||||
/**
|
||||
* Is safe.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_isSafe = true;
|
||||
|
||||
|
||||
/**
|
||||
* Request constructor.
|
||||
*
|
||||
* @param string $buffer
|
||||
*/
|
||||
public function __construct($buffer)
|
||||
{
|
||||
$this->_buffer = $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* $_GET.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get($name = null, $default = null)
|
||||
{
|
||||
if (!isset($this->_data['get'])) {
|
||||
$this->parseGet();
|
||||
}
|
||||
if (null === $name) {
|
||||
return $this->_data['get'];
|
||||
}
|
||||
return isset($this->_data['get'][$name]) ? $this->_data['get'][$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* $_POST.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function post($name = null, $default = null)
|
||||
{
|
||||
if (!isset($this->_data['post'])) {
|
||||
$this->parsePost();
|
||||
}
|
||||
if (null === $name) {
|
||||
return $this->_data['post'];
|
||||
}
|
||||
return isset($this->_data['post'][$name]) ? $this->_data['post'][$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header item by name.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param mixed|null $default
|
||||
* @return array|string|null
|
||||
*/
|
||||
public function header($name = null, $default = null)
|
||||
{
|
||||
if (!isset($this->_data['headers'])) {
|
||||
$this->parseHeaders();
|
||||
}
|
||||
if (null === $name) {
|
||||
return $this->_data['headers'];
|
||||
}
|
||||
$name = \strtolower($name);
|
||||
return isset($this->_data['headers'][$name]) ? $this->_data['headers'][$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie item by name.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param mixed|null $default
|
||||
* @return array|string|null
|
||||
*/
|
||||
public function cookie($name = null, $default = null)
|
||||
{
|
||||
if (!isset($this->_data['cookie'])) {
|
||||
$this->_data['cookie'] = array();
|
||||
\parse_str(\preg_replace('/; ?/', '&', $this->header('cookie', '')), $this->_data['cookie']);
|
||||
}
|
||||
if ($name === null) {
|
||||
return $this->_data['cookie'];
|
||||
}
|
||||
return isset($this->_data['cookie'][$name]) ? $this->_data['cookie'][$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload files.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function file(?string $name = null): mixed
|
||||
{
|
||||
if (!isset($this->_data['files'])) {
|
||||
$this->parsePost();
|
||||
}
|
||||
if (null === $name) {
|
||||
return $this->_data['files'];
|
||||
}
|
||||
return isset($this->_data['files'][$name]) ? $this->_data['files'][$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function method()
|
||||
{
|
||||
if (!isset($this->_data['method'])) {
|
||||
$this->parseHeadFirstLine();
|
||||
}
|
||||
return $this->_data['method'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get http protocol version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function protocolVersion()
|
||||
{
|
||||
if (!isset($this->_data['protocolVersion'])) {
|
||||
$this->parseProtocolVersion();
|
||||
}
|
||||
return $this->_data['protocolVersion'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get host.
|
||||
*
|
||||
* @param bool $without_port
|
||||
* @return string
|
||||
*/
|
||||
public function host($without_port = false)
|
||||
{
|
||||
$host = $this->header('host');
|
||||
if ($host && $without_port) {
|
||||
return preg_replace('/:\d{1,5}$/', '', $host);
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uri.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function uri()
|
||||
{
|
||||
if (!isset($this->_data['uri'])) {
|
||||
$this->parseHeadFirstLine();
|
||||
}
|
||||
return $this->_data['uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function path()
|
||||
{
|
||||
if (!isset($this->_data['path'])) {
|
||||
$this->_data['path'] = (string)\parse_url($this->uri(), PHP_URL_PATH);
|
||||
}
|
||||
return $this->_data['path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query string.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function queryString()
|
||||
{
|
||||
if (!isset($this->_data['query_string'])) {
|
||||
$this->_data['query_string'] = (string)\parse_url($this->uri(), PHP_URL_QUERY);
|
||||
}
|
||||
return $this->_data['query_string'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session.
|
||||
*
|
||||
* @return bool|\Workerman\Protocols\Http\Session
|
||||
*/
|
||||
public function session()
|
||||
{
|
||||
if ($this->session === null) {
|
||||
$session_id = $this->sessionId();
|
||||
if ($session_id === false) {
|
||||
return false;
|
||||
}
|
||||
$this->session = new Session($session_id);
|
||||
}
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/Set session id.
|
||||
*
|
||||
* @param $session_id
|
||||
* @return string
|
||||
*/
|
||||
public function sessionId($session_id = null)
|
||||
{
|
||||
if ($session_id) {
|
||||
unset($this->sid);
|
||||
}
|
||||
if (!isset($this->sid)) {
|
||||
$session_name = Session::$name;
|
||||
$sid = $session_id ? '' : $this->cookie($session_name);
|
||||
if ($sid === '' || $sid === null) {
|
||||
if ($this->connection === null) {
|
||||
Worker::safeEcho('Request->session() fail, header already send');
|
||||
return false;
|
||||
}
|
||||
$sid = $session_id ? $session_id : static::createSessionId();
|
||||
$cookie_params = Session::getCookieParams();
|
||||
$this->connection->__header['Set-Cookie'] = array($session_name . '=' . $sid
|
||||
. (empty($cookie_params['domain']) ? '' : '; Domain=' . $cookie_params['domain'])
|
||||
. (empty($cookie_params['lifetime']) ? '' : '; Max-Age=' . $cookie_params['lifetime'])
|
||||
. (empty($cookie_params['path']) ? '' : '; Path=' . $cookie_params['path'])
|
||||
. (empty($cookie_params['samesite']) ? '' : '; SameSite=' . $cookie_params['samesite'])
|
||||
. (!$cookie_params['secure'] ? '' : '; Secure')
|
||||
. (!$cookie_params['httponly'] ? '' : '; HttpOnly'));
|
||||
}
|
||||
$this->sid = $sid;
|
||||
}
|
||||
return $this->sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get http raw head.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function rawHead()
|
||||
{
|
||||
if (!isset($this->_data['head'])) {
|
||||
$this->_data['head'] = \strstr($this->_buffer, "\r\n\r\n", true);
|
||||
}
|
||||
return $this->_data['head'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get http raw body.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function rawBody()
|
||||
{
|
||||
return \substr($this->_buffer, \strpos($this->_buffer, "\r\n\r\n") + 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw buffer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function rawBuffer()
|
||||
{
|
||||
return $this->_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable cache.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function enableCache($value)
|
||||
{
|
||||
static::$_enableCache = (bool)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse first line of http header buffer.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseHeadFirstLine()
|
||||
{
|
||||
$first_line = \strstr($this->_buffer, "\r\n", true);
|
||||
$tmp = \explode(' ', $first_line, 3);
|
||||
$this->_data['method'] = $tmp[0];
|
||||
$this->_data['uri'] = isset($tmp[1]) ? $tmp[1] : '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse protocol version.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseProtocolVersion()
|
||||
{
|
||||
$first_line = \strstr($this->_buffer, "\r\n", true);
|
||||
$protoco_version = substr(\strstr($first_line, 'HTTP/'), 5);
|
||||
$this->_data['protocolVersion'] = $protoco_version ? $protoco_version : '1.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse headers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseHeaders()
|
||||
{
|
||||
static $cache = [];
|
||||
$this->_data['headers'] = array();
|
||||
$raw_head = $this->rawHead();
|
||||
$end_line_position = \strpos($raw_head, "\r\n");
|
||||
if ($end_line_position === false) {
|
||||
return;
|
||||
}
|
||||
$head_buffer = \substr($raw_head, $end_line_position + 2);
|
||||
$cacheable = static::$_enableCache && !isset($head_buffer[2048]);
|
||||
if ($cacheable && isset($cache[$head_buffer])) {
|
||||
$this->_data['headers'] = $cache[$head_buffer];
|
||||
return;
|
||||
}
|
||||
$head_data = \explode("\r\n", $head_buffer);
|
||||
foreach ($head_data as $content) {
|
||||
if (false !== \strpos($content, ':')) {
|
||||
list($key, $value) = \explode(':', $content, 2);
|
||||
$key = \strtolower($key);
|
||||
$value = \ltrim($value);
|
||||
} else {
|
||||
$key = \strtolower($content);
|
||||
$value = '';
|
||||
}
|
||||
if (isset($this->_data['headers'][$key])) {
|
||||
$this->_data['headers'][$key] = "{$this->_data['headers'][$key]},$value";
|
||||
} else {
|
||||
$this->_data['headers'][$key] = $value;
|
||||
}
|
||||
}
|
||||
if ($cacheable) {
|
||||
$cache[$head_buffer] = $this->_data['headers'];
|
||||
if (\count($cache) > 128) {
|
||||
unset($cache[key($cache)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse head.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parseGet()
|
||||
{
|
||||
static $cache = [];
|
||||
$query_string = $this->queryString();
|
||||
$this->_data['get'] = array();
|
||||
if ($query_string === '') {
|
||||
return;
|
||||
}
|
||||
$cacheable = static::$_enableCache && !isset($query_string[1024]);
|
||||
if ($cacheable && isset($cache[$query_string])) {
|
||||
$this->_data['get'] = $cache[$query_string];
|
||||
return;
|
||||
}
|
||||
\parse_str($query_string, $this->_data['get']);
|
||||
if ($cacheable) {
|
||||
$cache[$query_string] = $this->_data['get'];
|
||||
if (\count($cache) > 256) {
|
||||
unset($cache[key($cache)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse post.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function parsePost()
|
||||
{
|
||||
static $cache = [];
|
||||
$this->_data['post'] = $this->_data['files'] = array();
|
||||
$content_type = $this->header('content-type', '');
|
||||
if (\preg_match('/boundary="?(\S+)"?/', $content_type, $match)) {
|
||||
$http_post_boundary = '--' . $match[1];
|
||||
$this->parseUploadFiles($http_post_boundary);
|
||||
return;
|
||||
}
|
||||
$body_buffer = $this->rawBody();
|
||||
if ($body_buffer === '') {
|
||||
return;
|
||||
}
|
||||
$cacheable = static::$_enableCache && !isset($body_buffer[1024]);
|
||||
if ($cacheable && isset($cache[$body_buffer])) {
|
||||
$this->_data['post'] = $cache[$body_buffer];
|
||||
return;
|
||||
}
|
||||
if (\preg_match('/\bjson\b/i', $content_type)) {
|
||||
$this->_data['post'] = (array) json_decode($body_buffer, true);
|
||||
} else {
|
||||
\parse_str($body_buffer, $this->_data['post']);
|
||||
}
|
||||
if ($cacheable) {
|
||||
$cache[$body_buffer] = $this->_data['post'];
|
||||
if (\count($cache) > 256) {
|
||||
unset($cache[key($cache)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse upload files.
|
||||
*
|
||||
* @param string $http_post_boundary
|
||||
* @return void
|
||||
*/
|
||||
protected function parseUploadFiles($http_post_boundary)
|
||||
{
|
||||
$http_post_boundary = \trim($http_post_boundary, '"');
|
||||
$buffer = $this->_buffer;
|
||||
$post_encode_string = '';
|
||||
$files_encode_string = '';
|
||||
$files = [];
|
||||
$boday_position = strpos($buffer, "\r\n\r\n") + 4;
|
||||
$offset = $boday_position + strlen($http_post_boundary) + 2;
|
||||
$max_count = static::$maxFileUploads;
|
||||
while ($max_count-- > 0 && $offset) {
|
||||
$offset = $this->parseUploadFile($http_post_boundary, $offset, $post_encode_string, $files_encode_string, $files);
|
||||
}
|
||||
if ($post_encode_string) {
|
||||
parse_str($post_encode_string, $this->_data['post']);
|
||||
}
|
||||
|
||||
if ($files_encode_string) {
|
||||
parse_str($files_encode_string, $this->_data['files']);
|
||||
\array_walk_recursive($this->_data['files'], function (&$value) use ($files) {
|
||||
$value = $files[$value];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $boundary
|
||||
* @param $section_start_offset
|
||||
* @return int
|
||||
*/
|
||||
protected function parseUploadFile($boundary, $section_start_offset, &$post_encode_string, &$files_encode_str, &$files)
|
||||
{
|
||||
$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;
|
||||
}
|
||||
$content_lines_end_offset = \strpos($this->_buffer, "\r\n\r\n", $section_start_offset);
|
||||
if (!$content_lines_end_offset || $content_lines_end_offset + 4 > $section_end_offset) {
|
||||
return 0;
|
||||
}
|
||||
$content_lines_str = \substr($this->_buffer, $section_start_offset, $content_lines_end_offset - $section_start_offset);
|
||||
$content_lines = \explode("\r\n", trim($content_lines_str . "\r\n"));
|
||||
$boundary_value = \substr($this->_buffer, $content_lines_end_offset + 4, $section_end_offset - $content_lines_end_offset - 4);
|
||||
$upload_key = false;
|
||||
foreach ($content_lines as $content_line) {
|
||||
if (!\strpos($content_line, ': ')) {
|
||||
return 0;
|
||||
}
|
||||
list($key, $value) = \explode(': ', $content_line);
|
||||
switch (strtolower($key)) {
|
||||
case "content-disposition":
|
||||
// Is file data.
|
||||
if (\preg_match('/name="(.*?)"; filename="(.*?)"/i', $value, $match)) {
|
||||
$error = 0;
|
||||
$tmp_file = '';
|
||||
$file_name = $match[2];
|
||||
$size = \strlen($boundary_value);
|
||||
$tmp_upload_dir = HTTP::uploadTmpDir();
|
||||
if (!$tmp_upload_dir) {
|
||||
$error = UPLOAD_ERR_NO_TMP_DIR;
|
||||
} else if ($boundary_value === '' && $file_name === '') {
|
||||
$error = UPLOAD_ERR_NO_FILE;
|
||||
} else {
|
||||
$tmp_file = \tempnam($tmp_upload_dir, 'workerman.upload.');
|
||||
if ($tmp_file === false || false === \file_put_contents($tmp_file, $boundary_value)) {
|
||||
$error = UPLOAD_ERR_CANT_WRITE;
|
||||
}
|
||||
}
|
||||
$upload_key = $match[1];
|
||||
// Parse upload files.
|
||||
$file = [
|
||||
'name' => $file_name,
|
||||
'tmp_name' => $tmp_file,
|
||||
'size' => $size,
|
||||
'error' => $error,
|
||||
'type' => '',
|
||||
];
|
||||
break;
|
||||
} // Is post field.
|
||||
else {
|
||||
// Parse $_POST.
|
||||
if (\preg_match('/name="(.*?)"$/', $value, $match)) {
|
||||
$k = $match[1];
|
||||
$post_encode_string .= \urlencode($k) . "=" . \urlencode($boundary_value) . '&';
|
||||
}
|
||||
return $section_end_offset + \strlen($boundary) + 2;
|
||||
}
|
||||
break;
|
||||
case "content-type":
|
||||
$file['type'] = \trim($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($upload_key === false) {
|
||||
return 0;
|
||||
}
|
||||
$files_encode_str .= \urlencode($upload_key) . '=' . \count($files) . '&';
|
||||
$files[] = $file;
|
||||
|
||||
return $section_end_offset + \strlen($boundary) + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create session id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function createSessionId()
|
||||
{
|
||||
return \bin2hex(\pack('d', \microtime(true)) . random_bytes(8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return isset($this->properties[$name]) ? $this->properties[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Isset.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->properties[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($this->properties[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* __toString.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* __wakeup.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->_isSafe = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* __destruct.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->_data['files']) && $this->_isSafe) {
|
||||
\clearstatcache();
|
||||
\array_walk_recursive($this->_data['files'], function($value, $key){
|
||||
if ($key === 'tmp_name') {
|
||||
if (\is_file($value)) {
|
||||
\unlink($value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
458
vendor/workerman/workerman/Protocols/Http/Response.php
vendored
Normal file
458
vendor/workerman/workerman/Protocols/Http/Response.php
vendored
Normal file
@@ -0,0 +1,458 @@
|
||||
<?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 Workerman\Protocols\Http;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
* @package Workerman\Protocols\Http
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Header data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_header = null;
|
||||
|
||||
/**
|
||||
* Http status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_status = null;
|
||||
|
||||
/**
|
||||
* Http reason.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_reason = null;
|
||||
|
||||
/**
|
||||
* Http version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_version = '1.1';
|
||||
|
||||
/**
|
||||
* Http body.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_body = null;
|
||||
|
||||
/**
|
||||
* Send file info
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $file = null;
|
||||
|
||||
/**
|
||||
* Mine type map.
|
||||
* @var array
|
||||
*/
|
||||
protected static $_mimeTypeMap = null;
|
||||
|
||||
/**
|
||||
* Phrases.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_phrases = array(
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-status',
|
||||
208 => 'Already Reported',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
306 => 'Switch Proxy',
|
||||
307 => 'Temporary Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Time-out',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Request Entity Too Large',
|
||||
414 => 'Request-URI Too Large',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Requested range not satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
418 => 'I\'m a teapot',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
425 => 'Unordered Collection',
|
||||
426 => 'Upgrade Required',
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
431 => 'Request Header Fields Too Large',
|
||||
451 => 'Unavailable For Legal Reasons',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Time-out',
|
||||
505 => 'HTTP Version not supported',
|
||||
506 => 'Variant Also Negotiates',
|
||||
507 => 'Insufficient Storage',
|
||||
508 => 'Loop Detected',
|
||||
511 => 'Network Authentication Required',
|
||||
);
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
static::initMimeTypeMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Response constructor.
|
||||
*
|
||||
* @param int $status
|
||||
* @param array $headers
|
||||
* @param string $body
|
||||
*/
|
||||
public function __construct(
|
||||
$status = 200,
|
||||
$headers = array(),
|
||||
$body = ''
|
||||
) {
|
||||
$this->_status = $status;
|
||||
$this->_header = $headers;
|
||||
$this->_body = (string)$body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function header($name, $value) {
|
||||
$this->_header[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @return Response
|
||||
*/
|
||||
public function withHeader($name, $value) {
|
||||
return $this->header($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set headers.
|
||||
*
|
||||
* @param array $headers
|
||||
* @return $this
|
||||
*/
|
||||
public function withHeaders($headers) {
|
||||
$this->_header = \array_merge_recursive($this->_header, $headers);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove header.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function withoutHeader($name) {
|
||||
unset($this->_header[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header.
|
||||
*
|
||||
* @param string $name
|
||||
* @return null|array|string
|
||||
*/
|
||||
public function getHeader($name) {
|
||||
if (!isset($this->_header[$name])) {
|
||||
return null;
|
||||
}
|
||||
return $this->_header[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders() {
|
||||
return $this->_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status.
|
||||
*
|
||||
* @param int $code
|
||||
* @param string|null $reason_phrase
|
||||
* @return $this
|
||||
*/
|
||||
public function withStatus($code, $reason_phrase = null) {
|
||||
$this->_status = $code;
|
||||
$this->_reason = $reason_phrase;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status code.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatusCode() {
|
||||
return $this->_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reason phrase.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReasonPhrase() {
|
||||
return $this->_reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set protocol version.
|
||||
*
|
||||
* @param int $version
|
||||
* @return $this
|
||||
*/
|
||||
public function withProtocolVersion($version) {
|
||||
$this->_version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set http body.
|
||||
*
|
||||
* @param string $body
|
||||
* @return $this
|
||||
*/
|
||||
public function withBody($body) {
|
||||
$this->_body = $body;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get http raw body.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function rawBody() {
|
||||
return $this->_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send file.
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @return $this
|
||||
*/
|
||||
public function withFile($file, $offset = 0, $length = 0) {
|
||||
if (!\is_file($file)) {
|
||||
return $this->withStatus(404)->withBody('<h3>404 Not Found</h3>');
|
||||
}
|
||||
$this->file = array('file' => $file, 'offset' => $offset, 'length' => $length);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cookie.
|
||||
*
|
||||
* @param $name
|
||||
* @param string $value
|
||||
* @param int $max_age
|
||||
* @param string $path
|
||||
* @param string $domain
|
||||
* @param bool $secure
|
||||
* @param bool $http_only
|
||||
* @param string $same_site
|
||||
* @return $this
|
||||
*/
|
||||
public function cookie($name, $value = '', $max_age = null, $path = '', $domain = '', $secure = false, $http_only = false, $same_site = '')
|
||||
{
|
||||
$this->_header['Set-Cookie'][] = $name . '=' . \rawurlencode($value)
|
||||
. (empty($domain) ? '' : '; Domain=' . $domain)
|
||||
. ($max_age === null ? '' : '; Max-Age=' . $max_age)
|
||||
. (empty($path) ? '' : '; Path=' . $path)
|
||||
. (!$secure ? '' : '; Secure')
|
||||
. (!$http_only ? '' : '; HttpOnly')
|
||||
. (empty($same_site) ? '' : '; SameSite=' . $same_site);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create header for file.
|
||||
*
|
||||
* @param array $file_info
|
||||
* @return string
|
||||
*/
|
||||
protected function createHeadForFile($file_info)
|
||||
{
|
||||
$file = $file_info['file'];
|
||||
$reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status];
|
||||
$head = "HTTP/{$this->_version} {$this->_status} $reason\r\n";
|
||||
$headers = $this->_header;
|
||||
if (!isset($headers['Server'])) {
|
||||
$head .= "Server: workerman\r\n";
|
||||
}
|
||||
foreach ($headers as $name => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
$head .= "$name: $item\r\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$head .= "$name: $value\r\n";
|
||||
}
|
||||
|
||||
if (!isset($headers['Connection'])) {
|
||||
$head .= "Connection: keep-alive\r\n";
|
||||
}
|
||||
|
||||
$file_info = \pathinfo($file);
|
||||
$extension = isset($file_info['extension']) ? $file_info['extension'] : '';
|
||||
$base_name = isset($file_info['basename']) ? $file_info['basename'] : 'unknown';
|
||||
if (!isset($headers['Content-Type'])) {
|
||||
if (isset(self::$_mimeTypeMap[$extension])) {
|
||||
$head .= "Content-Type: " . self::$_mimeTypeMap[$extension] . "\r\n";
|
||||
} else {
|
||||
$head .= "Content-Type: application/octet-stream\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($headers['Content-Disposition']) && !isset(self::$_mimeTypeMap[$extension])) {
|
||||
$head .= "Content-Disposition: attachment; filename=\"$base_name\"\r\n";
|
||||
}
|
||||
|
||||
if (!isset($headers['Last-Modified'])) {
|
||||
if ($mtime = \filemtime($file)) {
|
||||
$head .= 'Last-Modified: '. \gmdate('D, d M Y H:i:s', $mtime) . ' GMT' . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
return "{$head}\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* __toString.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if (isset($this->file)) {
|
||||
return $this->createHeadForFile($this->file);
|
||||
}
|
||||
|
||||
$reason = $this->_reason ? $this->_reason : static::$_phrases[$this->_status];
|
||||
$body_len = \strlen($this->_body);
|
||||
if (empty($this->_header)) {
|
||||
return "HTTP/{$this->_version} {$this->_status} $reason\r\nServer: workerman\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\nConnection: keep-alive\r\n\r\n{$this->_body}";
|
||||
}
|
||||
|
||||
$head = "HTTP/{$this->_version} {$this->_status} $reason\r\n";
|
||||
$headers = $this->_header;
|
||||
if (!isset($headers['Server'])) {
|
||||
$head .= "Server: workerman\r\n";
|
||||
}
|
||||
foreach ($headers as $name => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
$head .= "$name: $item\r\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$head .= "$name: $value\r\n";
|
||||
}
|
||||
|
||||
if (!isset($headers['Connection'])) {
|
||||
$head .= "Connection: keep-alive\r\n";
|
||||
}
|
||||
|
||||
if (!isset($headers['Content-Type'])) {
|
||||
$head .= "Content-Type: text/html;charset=utf-8\r\n";
|
||||
} else if ($headers['Content-Type'] === 'text/event-stream') {
|
||||
return $head . $this->_body;
|
||||
}
|
||||
|
||||
if (!isset($headers['Transfer-Encoding'])) {
|
||||
$head .= "Content-Length: $body_len\r\n\r\n";
|
||||
} else {
|
||||
return $body_len ? "$head\r\n" . dechex($body_len) . "\r\n{$this->_body}\r\n" : "$head\r\n";
|
||||
}
|
||||
|
||||
// The whole http package
|
||||
return $head . $this->_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init mime map.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function initMimeTypeMap()
|
||||
{
|
||||
$mime_file = __DIR__ . '/mime.types';
|
||||
$items = \file($mime_file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($items as $content) {
|
||||
if (\preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
|
||||
$mime_type = $match[1];
|
||||
$extension_var = $match[2];
|
||||
$extension_array = \explode(' ', \substr($extension_var, 0, -1));
|
||||
foreach ($extension_array as $file_extension) {
|
||||
static::$_mimeTypeMap[$file_extension] = $mime_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Response::init();
|
||||
64
vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php
vendored
Normal file
64
vendor/workerman/workerman/Protocols/Http/ServerSentEvents.php
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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 Workerman\Protocols\Http;
|
||||
|
||||
/**
|
||||
* Class ServerSentEvents
|
||||
* @package Workerman\Protocols\Http
|
||||
*/
|
||||
class ServerSentEvents
|
||||
{
|
||||
/**
|
||||
* Data.
|
||||
* @var array
|
||||
*/
|
||||
protected $_data = null;
|
||||
|
||||
/**
|
||||
* ServerSentEvents constructor.
|
||||
* $data for example ['event'=>'ping', 'data' => 'some thing', 'id' => 1000, 'retry' => 5000]
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->_data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* __toString.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$buffer = '';
|
||||
$data = $this->_data;
|
||||
if (isset($data[''])) {
|
||||
$buffer = ": {$data['']}\n";
|
||||
}
|
||||
if (isset($data['event'])) {
|
||||
$buffer .= "event: {$data['event']}\n";
|
||||
}
|
||||
if (isset($data['id'])) {
|
||||
$buffer .= "id: {$data['id']}\n";
|
||||
}
|
||||
if (isset($data['retry'])) {
|
||||
$buffer .= "retry: {$data['retry']}\n";
|
||||
}
|
||||
if (isset($data['data'])) {
|
||||
$buffer .= 'data: ' . str_replace("\n", "\ndata: ", $data['data']) . "\n";
|
||||
}
|
||||
return $buffer . "\n";
|
||||
}
|
||||
}
|
||||
461
vendor/workerman/workerman/Protocols/Http/Session.php
vendored
Normal file
461
vendor/workerman/workerman/Protocols/Http/Session.php
vendored
Normal file
@@ -0,0 +1,461 @@
|
||||
<?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 Workerman\Protocols\Http;
|
||||
|
||||
use Workerman\Protocols\Http\Session\SessionHandlerInterface;
|
||||
|
||||
/**
|
||||
* Class Session
|
||||
* @package Workerman\Protocols\Http
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
/**
|
||||
* Session andler class which implements SessionHandlerInterface.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_handlerClass = 'Workerman\Protocols\Http\Session\FileSessionHandler';
|
||||
|
||||
/**
|
||||
* Parameters of __constructor for session handler class.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected static $_handlerConfig = null;
|
||||
|
||||
/**
|
||||
* Session name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $name = 'PHPSID';
|
||||
|
||||
/**
|
||||
* Auto update timestamp.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $autoUpdateTimestamp = false;
|
||||
|
||||
/**
|
||||
* Session lifetime.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $lifetime = 1440;
|
||||
|
||||
/**
|
||||
* Cookie lifetime.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $cookieLifetime = 1440;
|
||||
|
||||
/**
|
||||
* Session cookie path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $cookiePath = '/';
|
||||
|
||||
/**
|
||||
* Session cookie domain.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $domain = '';
|
||||
|
||||
/**
|
||||
* HTTPS only cookies.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $secure = false;
|
||||
|
||||
/**
|
||||
* HTTP access only.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $httpOnly = true;
|
||||
|
||||
/**
|
||||
* Same-site cookies.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $sameSite = '';
|
||||
|
||||
/**
|
||||
* Gc probability.
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
public static $gcProbability = [1, 1000];
|
||||
|
||||
/**
|
||||
* Session handler instance.
|
||||
*
|
||||
* @var SessionHandlerInterface
|
||||
*/
|
||||
protected static $_handler = null;
|
||||
|
||||
/**
|
||||
* Session data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_data = [];
|
||||
|
||||
/**
|
||||
* Session changed and need to save.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_needSave = false;
|
||||
|
||||
/**
|
||||
* Session id.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
protected $_sessionId = null;
|
||||
|
||||
/**
|
||||
* Is safe.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_isSafe = true;
|
||||
|
||||
/**
|
||||
* Session constructor.
|
||||
*
|
||||
* @param string $session_id
|
||||
*/
|
||||
public function __construct($session_id)
|
||||
{
|
||||
static::checkSessionId($session_id);
|
||||
if (static::$_handler === null) {
|
||||
static::initHandler();
|
||||
}
|
||||
$this->_sessionId = $session_id;
|
||||
if ($data = static::$_handler->read($session_id)) {
|
||||
$this->_data = \unserialize($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->_sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get($name, $default = null)
|
||||
{
|
||||
return isset($this->_data[$name]) ? $this->_data[$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data in the session.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set($name, $value)
|
||||
{
|
||||
$this->_data[$name] = $value;
|
||||
$this->_needSave = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an item from the session.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function delete($name)
|
||||
{
|
||||
unset($this->_data[$name]);
|
||||
$this->_needSave = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and delete an item from the session.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function pull($name, $default = null)
|
||||
{
|
||||
$value = $this->get($name, $default);
|
||||
$this->delete($name);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store data in the session.
|
||||
*
|
||||
* @param string|array $key
|
||||
* @param mixed|null $value
|
||||
*/
|
||||
public function put($key, $value = null)
|
||||
{
|
||||
if (!\is_array($key)) {
|
||||
$this->set($key, $value);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($key as $k => $v) {
|
||||
$this->_data[$k] = $v;
|
||||
}
|
||||
$this->_needSave = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a piece of data from the session.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function forget($name)
|
||||
{
|
||||
if (\is_scalar($name)) {
|
||||
$this->delete($name);
|
||||
return;
|
||||
}
|
||||
if (\is_array($name)) {
|
||||
foreach ($name as $key) {
|
||||
unset($this->_data[$key]);
|
||||
}
|
||||
}
|
||||
$this->_needSave = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the data in the session.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all data from the session.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$this->_needSave = true;
|
||||
$this->_data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determining If An Item Exists In The Session.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->_data[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* To determine if an item is present in the session, even if its value is null.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($name)
|
||||
{
|
||||
return \array_key_exists($name, $this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save session to store.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if ($this->_needSave) {
|
||||
if (empty($this->_data)) {
|
||||
static::$_handler->destroy($this->_sessionId);
|
||||
} else {
|
||||
static::$_handler->write($this->_sessionId, \serialize($this->_data));
|
||||
}
|
||||
} elseif (static::$autoUpdateTimestamp) {
|
||||
static::refresh();
|
||||
}
|
||||
$this->_needSave = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh session expire time.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function refresh()
|
||||
{
|
||||
static::$_handler->updateTimestamp($this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (($gc_probability = (int)\ini_get('session.gc_probability')) && ($gc_divisor = (int)\ini_get('session.gc_divisor'))) {
|
||||
static::$gcProbability = [$gc_probability, $gc_divisor];
|
||||
}
|
||||
|
||||
if ($gc_max_life_time = \ini_get('session.gc_maxlifetime')) {
|
||||
self::$lifetime = (int)$gc_max_life_time;
|
||||
}
|
||||
|
||||
$session_cookie_params = \session_get_cookie_params();
|
||||
static::$cookieLifetime = $session_cookie_params['lifetime'];
|
||||
static::$cookiePath = $session_cookie_params['path'];
|
||||
static::$domain = $session_cookie_params['domain'];
|
||||
static::$secure = $session_cookie_params['secure'];
|
||||
static::$httpOnly = $session_cookie_params['httponly'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session handler class.
|
||||
*
|
||||
* @param mixed|null $class_name
|
||||
* @param mixed|null $config
|
||||
* @return string
|
||||
*/
|
||||
public static function handlerClass($class_name = null, $config = null)
|
||||
{
|
||||
if ($class_name) {
|
||||
static::$_handlerClass = $class_name;
|
||||
}
|
||||
if ($config) {
|
||||
static::$_handlerConfig = $config;
|
||||
}
|
||||
return static::$_handlerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cookie params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCookieParams()
|
||||
{
|
||||
return [
|
||||
'lifetime' => static::$cookieLifetime,
|
||||
'path' => static::$cookiePath,
|
||||
'domain' => static::$domain,
|
||||
'secure' => static::$secure,
|
||||
'httponly' => static::$httpOnly,
|
||||
'samesite' => static::$sameSite,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Init handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function initHandler()
|
||||
{
|
||||
if (static::$_handlerConfig === null) {
|
||||
static::$_handler = new static::$_handlerClass();
|
||||
} else {
|
||||
static::$_handler = new static::$_handlerClass(static::$_handlerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GC sessions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function gc()
|
||||
{
|
||||
static::$_handler->gc(static::$lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* __wakeup.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->_isSafe = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* __destruct.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->_isSafe) {
|
||||
return;
|
||||
}
|
||||
$this->save();
|
||||
if (\random_int(1, static::$gcProbability[1]) <= static::$gcProbability[0]) {
|
||||
$this->gc();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check session id.
|
||||
*
|
||||
* @param string $session_id
|
||||
*/
|
||||
protected static function checkSessionId($session_id)
|
||||
{
|
||||
if (!\preg_match('/^[a-zA-Z0-9"]+$/', $session_id)) {
|
||||
throw new SessionException("session_id $session_id is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class SessionException
|
||||
* @package Workerman\Protocols\Http
|
||||
*/
|
||||
class SessionException extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Init session.
|
||||
Session::init();
|
||||
183
vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php
vendored
Normal file
183
vendor/workerman/workerman/Protocols/Http/Session/FileSessionHandler.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?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 Workerman\Protocols\Http\Session;
|
||||
|
||||
use Workerman\Protocols\Http\Session;
|
||||
|
||||
/**
|
||||
* Class FileSessionHandler
|
||||
* @package Workerman\Protocols\Http\Session
|
||||
*/
|
||||
class FileSessionHandler implements SessionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Session save path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_sessionSavePath = null;
|
||||
|
||||
/**
|
||||
* Session file prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_sessionFilePrefix = 'session_';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
$save_path = @\session_save_path();
|
||||
if (!$save_path || \strpos($save_path, 'tcp://') === 0) {
|
||||
$save_path = \sys_get_temp_dir();
|
||||
}
|
||||
static::sessionSavePath($save_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* FileSessionHandler constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct($config = array()) {
|
||||
if (isset($config['save_path'])) {
|
||||
static::sessionSavePath($config['save_path']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($save_path, $name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($session_id)
|
||||
{
|
||||
$session_file = static::sessionFile($session_id);
|
||||
\clearstatcache();
|
||||
if (\is_file($session_file)) {
|
||||
if (\time() - \filemtime($session_file) > Session::$lifetime) {
|
||||
\unlink($session_file);
|
||||
return '';
|
||||
}
|
||||
$data = \file_get_contents($session_file);
|
||||
return $data ? $data : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($session_id, $session_data)
|
||||
{
|
||||
$temp_file = static::$_sessionSavePath . uniqid(bin2hex(random_bytes(8)), true);
|
||||
if (!\file_put_contents($temp_file, $session_data)) {
|
||||
return false;
|
||||
}
|
||||
return \rename($temp_file, static::sessionFile($session_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sesstion modify time.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php
|
||||
* @see https://www.php.net/manual/zh/function.touch.php
|
||||
*
|
||||
* @param string $id Session id.
|
||||
* @param string $data Session Data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateTimestamp($id, $data = "")
|
||||
{
|
||||
$session_file = static::sessionFile($id);
|
||||
if (!file_exists($session_file)) {
|
||||
return false;
|
||||
}
|
||||
// set file modify time to current time
|
||||
$set_modify_time = \touch($session_file);
|
||||
// clear file stat cache
|
||||
\clearstatcache();
|
||||
return $set_modify_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($session_id)
|
||||
{
|
||||
$session_file = static::sessionFile($session_id);
|
||||
if (\is_file($session_file)) {
|
||||
\unlink($session_file);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gc($maxlifetime) {
|
||||
$time_now = \time();
|
||||
foreach (\glob(static::$_sessionSavePath . static::$_sessionFilePrefix . '*') as $file) {
|
||||
if(\is_file($file) && $time_now - \filemtime($file) > $maxlifetime) {
|
||||
\unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session file path.
|
||||
*
|
||||
* @param string $session_id
|
||||
* @return string
|
||||
*/
|
||||
protected static function sessionFile($session_id) {
|
||||
return static::$_sessionSavePath.static::$_sessionFilePrefix.$session_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set session file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function sessionSavePath($path) {
|
||||
if ($path) {
|
||||
if ($path[\strlen($path)-1] !== DIRECTORY_SEPARATOR) {
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
static::$_sessionSavePath = $path;
|
||||
if (!\is_dir($path)) {
|
||||
\mkdir($path, 0777, true);
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
FileSessionHandler::init();
|
||||
46
vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php
vendored
Normal file
46
vendor/workerman/workerman/Protocols/Http/Session/RedisClusterSessionHandler.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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 Workerman\Protocols\Http\Session;
|
||||
|
||||
use Workerman\Protocols\Http\Session;
|
||||
|
||||
class RedisClusterSessionHandler extends RedisSessionHandler
|
||||
{
|
||||
public function __construct($config)
|
||||
{
|
||||
$timeout = isset($config['timeout']) ? $config['timeout'] : 2;
|
||||
$read_timeout = isset($config['read_timeout']) ? $config['read_timeout'] : $timeout;
|
||||
$persistent = isset($config['persistent']) ? $config['persistent'] : false;
|
||||
$auth = isset($config['auth']) ? $config['auth'] : '';
|
||||
if ($auth) {
|
||||
$this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent, $auth);
|
||||
} else {
|
||||
$this->_redis = new \RedisCluster(null, $config['host'], $timeout, $read_timeout, $persistent);
|
||||
}
|
||||
if (empty($config['prefix'])) {
|
||||
$config['prefix'] = 'redis_session_';
|
||||
}
|
||||
$this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($session_id)
|
||||
{
|
||||
return $this->_redis->get($session_id);
|
||||
}
|
||||
|
||||
}
|
||||
154
vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php
vendored
Normal file
154
vendor/workerman/workerman/Protocols/Http/Session/RedisSessionHandler.php
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
<?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 Workerman\Protocols\Http\Session;
|
||||
|
||||
use Workerman\Protocols\Http\Session;
|
||||
use Workerman\Timer;
|
||||
use RedisException;
|
||||
|
||||
/**
|
||||
* Class RedisSessionHandler
|
||||
* @package Workerman\Protocols\Http\Session
|
||||
*/
|
||||
class RedisSessionHandler implements SessionHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \Redis
|
||||
*/
|
||||
protected $_redis;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* RedisSessionHandler constructor.
|
||||
* @param array $config = [
|
||||
* 'host' => '127.0.0.1',
|
||||
* 'port' => 6379,
|
||||
* 'timeout' => 2,
|
||||
* 'auth' => '******',
|
||||
* 'database' => 2,
|
||||
* 'prefix' => 'redis_session_',
|
||||
* 'ping' => 55,
|
||||
* ]
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
if (false === extension_loaded('redis')) {
|
||||
throw new \RuntimeException('Please install redis extension.');
|
||||
}
|
||||
|
||||
if (!isset($config['timeout'])) {
|
||||
$config['timeout'] = 2;
|
||||
}
|
||||
|
||||
$this->_config = $config;
|
||||
|
||||
$this->connect();
|
||||
|
||||
Timer::add(!empty($config['ping']) ? $config['ping'] : 55, function () {
|
||||
$this->_redis->get('ping');
|
||||
});
|
||||
}
|
||||
|
||||
public function connect()
|
||||
{
|
||||
$config = $this->_config;
|
||||
|
||||
$this->_redis = new \Redis();
|
||||
if (false === $this->_redis->connect($config['host'], $config['port'], $config['timeout'])) {
|
||||
throw new \RuntimeException("Redis connect {$config['host']}:{$config['port']} fail.");
|
||||
}
|
||||
if (!empty($config['auth'])) {
|
||||
$this->_redis->auth($config['auth']);
|
||||
}
|
||||
if (!empty($config['database'])) {
|
||||
$this->_redis->select($config['database']);
|
||||
}
|
||||
if (empty($config['prefix'])) {
|
||||
$config['prefix'] = 'redis_session_';
|
||||
}
|
||||
$this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($save_path, $name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($session_id)
|
||||
{
|
||||
try {
|
||||
return $this->_redis->get($session_id);
|
||||
} catch (RedisException $e) {
|
||||
$msg = strtolower($e->getMessage());
|
||||
if ($msg === 'connection lost' || strpos($msg, 'went away')) {
|
||||
$this->connect();
|
||||
return $this->_redis->get($session_id);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($session_id, $session_data)
|
||||
{
|
||||
return true === $this->_redis->setex($session_id, Session::$lifetime, $session_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTimestamp($id, $data = "")
|
||||
{
|
||||
return true === $this->_redis->expire($id, Session::$lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($session_id)
|
||||
{
|
||||
$this->_redis->del($session_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gc($maxlifetime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
114
vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php
vendored
Normal file
114
vendor/workerman/workerman/Protocols/Http/Session/SessionHandlerInterface.php
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
<?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 Workerman\Protocols\Http\Session;
|
||||
|
||||
interface SessionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Close the session
|
||||
* @link http://php.net/manual/en/sessionhandlerinterface.close.php
|
||||
* @return bool <p>
|
||||
* The return value (usually TRUE on success, FALSE on failure).
|
||||
* Note this value is returned internally to PHP for processing.
|
||||
* </p>
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* Destroy a session
|
||||
* @link http://php.net/manual/en/sessionhandlerinterface.destroy.php
|
||||
* @param string $session_id The session ID being destroyed.
|
||||
* @return bool <p>
|
||||
* The return value (usually TRUE on success, FALSE on failure).
|
||||
* Note this value is returned internally to PHP for processing.
|
||||
* </p>
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function destroy($session_id);
|
||||
|
||||
/**
|
||||
* Cleanup old sessions
|
||||
* @link http://php.net/manual/en/sessionhandlerinterface.gc.php
|
||||
* @param int $maxlifetime <p>
|
||||
* Sessions that have not updated for
|
||||
* the last maxlifetime seconds will be removed.
|
||||
* </p>
|
||||
* @return bool <p>
|
||||
* The return value (usually TRUE on success, FALSE on failure).
|
||||
* Note this value is returned internally to PHP for processing.
|
||||
* </p>
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function gc($maxlifetime);
|
||||
|
||||
/**
|
||||
* Initialize session
|
||||
* @link http://php.net/manual/en/sessionhandlerinterface.open.php
|
||||
* @param string $save_path The path where to store/retrieve the session.
|
||||
* @param string $name The session name.
|
||||
* @return bool <p>
|
||||
* The return value (usually TRUE on success, FALSE on failure).
|
||||
* Note this value is returned internally to PHP for processing.
|
||||
* </p>
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function open($save_path, $name);
|
||||
|
||||
|
||||
/**
|
||||
* Read session data
|
||||
* @link http://php.net/manual/en/sessionhandlerinterface.read.php
|
||||
* @param string $session_id The session id to read data for.
|
||||
* @return string <p>
|
||||
* Returns an encoded string of the read data.
|
||||
* If nothing was read, it must return an empty string.
|
||||
* Note this value is returned internally to PHP for processing.
|
||||
* </p>
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function read($session_id);
|
||||
|
||||
/**
|
||||
* Write session data
|
||||
* @link http://php.net/manual/en/sessionhandlerinterface.write.php
|
||||
* @param string $session_id The session id.
|
||||
* @param string $session_data <p>
|
||||
* The encoded session data. This data is the
|
||||
* result of the PHP internally encoding
|
||||
* the $_SESSION superglobal to a serialized
|
||||
* string and passing it as this parameter.
|
||||
* Please note sessions use an alternative serialization method.
|
||||
* </p>
|
||||
* @return bool <p>
|
||||
* The return value (usually TRUE on success, FALSE on failure).
|
||||
* Note this value is returned internally to PHP for processing.
|
||||
* </p>
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function write($session_id, $session_data);
|
||||
|
||||
/**
|
||||
* Update sesstion modify time.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php
|
||||
*
|
||||
* @param string $id Session id.
|
||||
* @param string $data Session Data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateTimestamp($id, $data = "");
|
||||
|
||||
}
|
||||
90
vendor/workerman/workerman/Protocols/Http/mime.types
vendored
Normal file
90
vendor/workerman/workerman/Protocols/Http/mime.types
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
image/svg+xml svg svgz;
|
||||
image/webp webp;
|
||||
|
||||
application/font-woff woff;
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
font/ttf ttf;
|
||||
}
|
||||
52
vendor/workerman/workerman/Protocols/ProtocolInterface.php
vendored
Normal file
52
vendor/workerman/workerman/Protocols/ProtocolInterface.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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 Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Protocol interface
|
||||
*/
|
||||
interface ProtocolInterface
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
* Please return the length of package.
|
||||
* If length is unknow please return 0 that mean wating more data.
|
||||
* If the package has something wrong please return false the connection will be closed.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int|false
|
||||
*/
|
||||
public static function input($recv_buffer, ConnectionInterface $connection);
|
||||
|
||||
/**
|
||||
* Decode package and emit onMessage($message) callback, $message is the result that decode returned.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decode($recv_buffer, ConnectionInterface $connection);
|
||||
|
||||
/**
|
||||
* Encode package brefore sending to client.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($data, ConnectionInterface $connection);
|
||||
}
|
||||
70
vendor/workerman/workerman/Protocols/Text.php
vendored
Normal file
70
vendor/workerman/workerman/Protocols/Text.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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 Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Text Protocol.
|
||||
*/
|
||||
class Text
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
// Judge whether the package length exceeds the limit.
|
||||
if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) {
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
// Find the position of "\n".
|
||||
$pos = \strpos($buffer, "\n");
|
||||
// No "\n", packet length is unknown, continue to wait for the data so return 0.
|
||||
if ($pos === false) {
|
||||
return 0;
|
||||
}
|
||||
// Return the current package length.
|
||||
return $pos + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer)
|
||||
{
|
||||
// Add "\n"
|
||||
return $buffer . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer)
|
||||
{
|
||||
// Remove "\n"
|
||||
return \rtrim($buffer, "\r\n");
|
||||
}
|
||||
}
|
||||
562
vendor/workerman/workerman/Protocols/Websocket.php
vendored
Normal file
562
vendor/workerman/workerman/Protocols/Websocket.php
vendored
Normal file
@@ -0,0 +1,562 @@
|
||||
<?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 Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\Http\Request;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* WebSocket protocol.
|
||||
*/
|
||||
class Websocket implements \Workerman\Protocols\ProtocolInterface
|
||||
{
|
||||
/**
|
||||
* Websocket blob type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_BLOB = "\x81";
|
||||
|
||||
/**
|
||||
* Websocket blob type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_BLOB_DEFLATE = "\xc1";
|
||||
|
||||
/**
|
||||
* Websocket arraybuffer type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_ARRAYBUFFER = "\x82";
|
||||
|
||||
/**
|
||||
* Websocket arraybuffer type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_ARRAYBUFFER_DEFLATE = "\xc2";
|
||||
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
// Receive length.
|
||||
$recv_len = \strlen($buffer);
|
||||
// We need more data.
|
||||
if ($recv_len < 6) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Has not yet completed the handshake.
|
||||
if (empty($connection->context->websocketHandshake)) {
|
||||
return static::dealHandshake($buffer, $connection);
|
||||
}
|
||||
|
||||
// Buffer websocket frame data.
|
||||
if ($connection->context->websocketCurrentFrameLength) {
|
||||
// We need more frame data.
|
||||
if ($connection->context->websocketCurrentFrameLength > $recv_len) {
|
||||
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$first_byte = \ord($buffer[0]);
|
||||
$second_byte = \ord($buffer[1]);
|
||||
$data_len = $second_byte & 127;
|
||||
$is_fin_frame = $first_byte >> 7;
|
||||
$masked = $second_byte >> 7;
|
||||
|
||||
if (!$masked) {
|
||||
Worker::safeEcho("frame not masked so close the connection\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
$opcode = $first_byte & 0xf;
|
||||
switch ($opcode) {
|
||||
case 0x0:
|
||||
break;
|
||||
// Blob type.
|
||||
case 0x1:
|
||||
break;
|
||||
// Arraybuffer type.
|
||||
case 0x2:
|
||||
break;
|
||||
// Close package.
|
||||
case 0x8:
|
||||
// Try to emit onWebSocketClose callback.
|
||||
$close_cb = $connection->onWebSocketClose ?? $connection->worker->onWebSocketClose ?? false;
|
||||
if ($close_cb) {
|
||||
try {
|
||||
$close_cb($connection);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
} // Close connection.
|
||||
else {
|
||||
$connection->close("\x88\x02\x03\xe8", true);
|
||||
}
|
||||
return 0;
|
||||
// Ping package.
|
||||
case 0x9:
|
||||
break;
|
||||
// Pong package.
|
||||
case 0xa:
|
||||
break;
|
||||
// Wrong opcode.
|
||||
default :
|
||||
Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate packet length.
|
||||
$head_len = 6;
|
||||
if ($data_len === 126) {
|
||||
$head_len = 8;
|
||||
if ($head_len > $recv_len) {
|
||||
return 0;
|
||||
}
|
||||
$pack = \unpack('nn/ntotal_len', $buffer);
|
||||
$data_len = $pack['total_len'];
|
||||
} else {
|
||||
if ($data_len === 127) {
|
||||
$head_len = 14;
|
||||
if ($head_len > $recv_len) {
|
||||
return 0;
|
||||
}
|
||||
$arr = \unpack('n/N2c', $buffer);
|
||||
$data_len = $arr['c1'] * 4294967296 + $arr['c2'];
|
||||
}
|
||||
}
|
||||
$current_frame_length = $head_len + $data_len;
|
||||
|
||||
$total_package_size = \strlen($connection->context->websocketDataBuffer) + $current_frame_length;
|
||||
if ($total_package_size > $connection->maxPackageSize) {
|
||||
Worker::safeEcho("error package. package_length=$total_package_size\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($is_fin_frame) {
|
||||
if ($opcode === 0x9) {
|
||||
if ($recv_len >= $current_frame_length) {
|
||||
$ping_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection);
|
||||
$connection->consumeRecvBuffer($current_frame_length);
|
||||
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
$ping_cb = $connection->onWebSocketPing ?? $connection->worker->onWebSocketPing ?? false;
|
||||
if ($ping_cb) {
|
||||
try {
|
||||
$ping_cb($connection, $ping_data);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
} else {
|
||||
$connection->send($ping_data);
|
||||
}
|
||||
$connection->websocketType = $tmp_connection_type;
|
||||
if ($recv_len > $current_frame_length) {
|
||||
return static::input(\substr($buffer, $current_frame_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} else if ($opcode === 0xa) {
|
||||
if ($recv_len >= $current_frame_length) {
|
||||
$pong_data = static::decode(\substr($buffer, 0, $current_frame_length), $connection);
|
||||
$connection->consumeRecvBuffer($current_frame_length);
|
||||
$tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
// Try to emit onWebSocketPong callback.
|
||||
$pong_cb = $connection->onWebSocketPong ?? $connection->worker->onWebSocketPong ?? false;
|
||||
if ($pong_cb) {
|
||||
try {
|
||||
$pong_cb($connection, $pong_data);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
$connection->websocketType = $tmp_connection_type;
|
||||
if ($recv_len > $current_frame_length) {
|
||||
return static::input(\substr($buffer, $current_frame_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return $current_frame_length;
|
||||
} else {
|
||||
$connection->context->websocketCurrentFrameLength = $current_frame_length;
|
||||
}
|
||||
}
|
||||
|
||||
// Received just a frame length data.
|
||||
if ($connection->context->websocketCurrentFrameLength === $recv_len) {
|
||||
static::decode($buffer, $connection);
|
||||
$connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength);
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
return 0;
|
||||
} // The length of the received data is greater than the length of a frame.
|
||||
elseif ($connection->context->websocketCurrentFrameLength < $recv_len) {
|
||||
static::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength);
|
||||
$current_frame_length = $connection->context->websocketCurrentFrameLength;
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
// Continue to read next frame.
|
||||
return static::input(\substr($buffer, $current_frame_length), $connection);
|
||||
} // The length of the received data is less than the length of a frame.
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
if (!is_scalar($buffer)) {
|
||||
throw new \Exception("You can't send(" . \gettype($buffer) . ") to client, you need to convert it to a string. ");
|
||||
}
|
||||
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = static::BINARY_TYPE_BLOB;
|
||||
}
|
||||
|
||||
// permessage-deflate
|
||||
if (\ord($connection->websocketType) & 64) {
|
||||
$buffer = static::deflate($connection, $buffer);
|
||||
}
|
||||
|
||||
$first_byte = $connection->websocketType;
|
||||
$len = \strlen($buffer);
|
||||
|
||||
if ($len <= 125) {
|
||||
$encode_buffer = $first_byte . \chr($len) . $buffer;
|
||||
} else {
|
||||
if ($len <= 65535) {
|
||||
$encode_buffer = $first_byte . \chr(126) . \pack("n", $len) . $buffer;
|
||||
} else {
|
||||
$encode_buffer = $first_byte . \chr(127) . \pack("xxxxN", $len) . $buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// Handshake not completed so temporary buffer websocket data waiting for send.
|
||||
if (empty($connection->context->websocketHandshake)) {
|
||||
if (empty($connection->context->tmpWebsocketData)) {
|
||||
$connection->context->tmpWebsocketData = '';
|
||||
}
|
||||
// If buffer has already full then discard the current package.
|
||||
if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) {
|
||||
if ($connection->onError) {
|
||||
try {
|
||||
($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
$connection->context->tmpWebsocketData .= $encode_buffer;
|
||||
// Check buffer is full.
|
||||
if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) {
|
||||
if ($connection->onBufferFull) {
|
||||
try {
|
||||
($connection->onBufferFull)($connection);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Return empty string.
|
||||
return '';
|
||||
}
|
||||
|
||||
return $encode_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
$first_byte = \ord($buffer[0]);
|
||||
$second_byte = \ord($buffer[1]);
|
||||
$len = $second_byte & 127;
|
||||
$is_fin_frame = $first_byte >> 7;
|
||||
$rsv1 = 64 === ($first_byte & 64);
|
||||
|
||||
if ($len === 126) {
|
||||
$masks = \substr($buffer, 4, 4);
|
||||
$data = \substr($buffer, 8);
|
||||
} else {
|
||||
if ($len === 127) {
|
||||
$masks = \substr($buffer, 10, 4);
|
||||
$data = \substr($buffer, 14);
|
||||
} else {
|
||||
$masks = \substr($buffer, 2, 4);
|
||||
$data = \substr($buffer, 6);
|
||||
}
|
||||
}
|
||||
$dataLength = \strlen($data);
|
||||
$masks = \str_repeat($masks, \floor($dataLength / 4)) . \substr($masks, 0, $dataLength % 4);
|
||||
$decoded = $data ^ $masks;
|
||||
if ($connection->context->websocketCurrentFrameLength) {
|
||||
$connection->context->websocketDataBuffer .= $decoded;
|
||||
if ($rsv1) {
|
||||
return static::inflate($connection, $connection->context->websocketDataBuffer, $is_fin_frame);
|
||||
}
|
||||
return $connection->context->websocketDataBuffer;
|
||||
} else {
|
||||
if ($connection->context->websocketDataBuffer !== '') {
|
||||
$decoded = $connection->context->websocketDataBuffer . $decoded;
|
||||
$connection->context->websocketDataBuffer = '';
|
||||
}
|
||||
if ($rsv1) {
|
||||
return static::inflate($connection, $decoded, $is_fin_frame);
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate.
|
||||
*
|
||||
* @param $connection
|
||||
* @param $buffer
|
||||
* @param $is_fin_frame
|
||||
* @return false|string
|
||||
*/
|
||||
protected static function inflate($connection, $buffer, $is_fin_frame)
|
||||
{
|
||||
if (!isset($connection->context->inflator)) {
|
||||
$connection->context->inflator = \inflate_init(
|
||||
\ZLIB_ENCODING_RAW,
|
||||
[
|
||||
'level' => -1,
|
||||
'memory' => 8,
|
||||
'window' => 15,
|
||||
'strategy' => \ZLIB_DEFAULT_STRATEGY
|
||||
]
|
||||
);
|
||||
}
|
||||
if ($is_fin_frame) {
|
||||
$buffer .= "\x00\x00\xff\xff";
|
||||
}
|
||||
return \inflate_add($connection->context->inflator, $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deflate.
|
||||
*
|
||||
* @param $connection
|
||||
* @param $buffer
|
||||
* @return false|string
|
||||
*/
|
||||
protected static function deflate($connection, $buffer)
|
||||
{
|
||||
if (!isset($connection->context->deflator)) {
|
||||
$connection->context->deflator = \deflate_init(
|
||||
\ZLIB_ENCODING_RAW,
|
||||
[
|
||||
'level' => -1,
|
||||
'memory' => 8,
|
||||
'window' => 15,
|
||||
'strategy' => \ZLIB_DEFAULT_STRATEGY
|
||||
]
|
||||
);
|
||||
}
|
||||
return \substr(\deflate_add($connection->context->deflator, $buffer), 0, -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket handshake.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function dealHandshake($buffer, $connection)
|
||||
{
|
||||
// HTTP protocol.
|
||||
if (0 === \strpos($buffer, 'GET')) {
|
||||
// Find \r\n\r\n.
|
||||
$header_end_pos = \strpos($buffer, "\r\n\r\n");
|
||||
if (!$header_end_pos) {
|
||||
return 0;
|
||||
}
|
||||
$header_length = $header_end_pos + 4;
|
||||
|
||||
// Get Sec-WebSocket-Key.
|
||||
$Sec_WebSocket_Key = '';
|
||||
if (\preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) {
|
||||
$Sec_WebSocket_Key = $match[1];
|
||||
} else {
|
||||
$connection->close("HTTP/1.0 400 Bad Request\r\nServer: workerman\r\n\r\n<div style=\"text-align:center\"><h1>WebSocket</h1><hr>workerman</div>", true);
|
||||
return 0;
|
||||
}
|
||||
// Calculation websocket key.
|
||||
$new_key = \base64_encode(\sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
|
||||
// Handshake response data.
|
||||
$handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
. "Upgrade: websocket\r\n"
|
||||
. "Sec-WebSocket-Version: 13\r\n"
|
||||
. "Connection: Upgrade\r\n"
|
||||
. "Sec-WebSocket-Accept: " . $new_key . "\r\n";
|
||||
|
||||
// Websocket data buffer.
|
||||
$connection->context->websocketDataBuffer = '';
|
||||
// Current websocket frame length.
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
// Current websocket frame data.
|
||||
$connection->context->websocketCurrentFrameBuffer = '';
|
||||
// Consume handshake data.
|
||||
$connection->consumeRecvBuffer($header_length);
|
||||
|
||||
// Try to emit onWebSocketConnect callback.
|
||||
$on_websocket_connect = $connection->onWebSocketConnect ?? $connection->worker->onWebSocketConnect ?? false;
|
||||
if ($on_websocket_connect) {
|
||||
static::parseHttpHeader($buffer);
|
||||
try {
|
||||
\call_user_func($on_websocket_connect, $connection, $buffer);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
if (!empty($_SESSION) && \class_exists('\GatewayWorker\Lib\Context')) {
|
||||
$connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION);
|
||||
}
|
||||
$_GET = $_SERVER = $_SESSION = $_COOKIE = array();
|
||||
}
|
||||
|
||||
// blob or arraybuffer
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = static::BINARY_TYPE_BLOB;
|
||||
}
|
||||
|
||||
$has_server_header = false;
|
||||
|
||||
if (isset($connection->headers)) {
|
||||
if (\is_array($connection->headers)) {
|
||||
foreach ($connection->headers as $header) {
|
||||
if (\stripos($header, 'Server:') === 0) {
|
||||
$has_server_header = true;
|
||||
}
|
||||
$handshake_message .= "$header\r\n";
|
||||
}
|
||||
} else {
|
||||
if (\stripos($connection->headers, 'Server:') !== false) {
|
||||
$has_server_header = true;
|
||||
}
|
||||
$handshake_message .= "$connection->headers\r\n";
|
||||
}
|
||||
}
|
||||
if (!$has_server_header) {
|
||||
$handshake_message .= "Server: workerman/" . Worker::VERSION . "\r\n";
|
||||
}
|
||||
$handshake_message .= "\r\n";
|
||||
// Send handshake response.
|
||||
$connection->send($handshake_message, true);
|
||||
// Mark handshake complete..
|
||||
$connection->context->websocketHandshake = true;
|
||||
|
||||
// There are data waiting to be sent.
|
||||
if (!empty($connection->context->tmpWebsocketData)) {
|
||||
$connection->send($connection->context->tmpWebsocketData, true);
|
||||
$connection->context->tmpWebsocketData = '';
|
||||
}
|
||||
if (\strlen($buffer) > $header_length) {
|
||||
return static::input(\substr($buffer, $header_length), $connection);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// Bad websocket handshake request.
|
||||
$connection->close("HTTP/1.0 400 Bad Request\r\nServer: workerman\r\n\r\n<div style=\"text-align:center\"><h1>400 Bad Request</h1><hr>workerman</div>", true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse http header.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return void
|
||||
*/
|
||||
protected static function parseHttpHeader($buffer)
|
||||
{
|
||||
// Parse headers.
|
||||
list($http_header, ) = \explode("\r\n\r\n", $buffer, 2);
|
||||
$header_data = \explode("\r\n", $http_header);
|
||||
|
||||
if ($_SERVER) {
|
||||
$_SERVER = array();
|
||||
}
|
||||
|
||||
list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = \explode(' ',
|
||||
$header_data[0]);
|
||||
|
||||
unset($header_data[0]);
|
||||
foreach ($header_data as $content) {
|
||||
// \r\n\r\n
|
||||
if (empty($content)) {
|
||||
continue;
|
||||
}
|
||||
list($key, $value) = \explode(':', $content, 2);
|
||||
$key = \str_replace('-', '_', \strtoupper($key));
|
||||
$value = \trim($value);
|
||||
$_SERVER['HTTP_' . $key] = $value;
|
||||
switch ($key) {
|
||||
// HTTP_HOST
|
||||
case 'HOST':
|
||||
$tmp = \explode(':', $value);
|
||||
$_SERVER['SERVER_NAME'] = $tmp[0];
|
||||
if (isset($tmp[1])) {
|
||||
$_SERVER['SERVER_PORT'] = $tmp[1];
|
||||
}
|
||||
break;
|
||||
// cookie
|
||||
case 'COOKIE':
|
||||
\parse_str(\str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// QUERY_STRING
|
||||
$_SERVER['QUERY_STRING'] = \parse_url($_SERVER['REQUEST_URI'], \PHP_URL_QUERY);
|
||||
if ($_SERVER['QUERY_STRING']) {
|
||||
// $GET
|
||||
\parse_str($_SERVER['QUERY_STRING'], $_GET);
|
||||
} else {
|
||||
$_SERVER['QUERY_STRING'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
432
vendor/workerman/workerman/Protocols/Ws.php
vendored
Normal file
432
vendor/workerman/workerman/Protocols/Ws.php
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
<?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 Workerman\Protocols;
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Websocket protocol for client.
|
||||
*/
|
||||
class Ws
|
||||
{
|
||||
/**
|
||||
* Websocket blob type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_BLOB = "\x81";
|
||||
|
||||
/**
|
||||
* Websocket arraybuffer type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BINARY_TYPE_ARRAYBUFFER = "\x82";
|
||||
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
if (empty($connection->context->handshakeStep)) {
|
||||
Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n");
|
||||
return false;
|
||||
}
|
||||
// Recv handshake response
|
||||
if ($connection->context->handshakeStep === 1) {
|
||||
return self::dealHandshake($buffer, $connection);
|
||||
}
|
||||
$recvLen = \strlen($buffer);
|
||||
if ($recvLen < 2) {
|
||||
return 0;
|
||||
}
|
||||
// Buffer websocket frame data.
|
||||
if ($connection->context->websocketCurrentFrameLength) {
|
||||
// We need more frame data.
|
||||
if ($connection->context->websocketCurrentFrameLength > $recvLen) {
|
||||
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
|
||||
$firstbyte = \ord($buffer[0]);
|
||||
$secondbyte = \ord($buffer[1]);
|
||||
$dataLen = $secondbyte & 127;
|
||||
$isFinFrame = $firstbyte >> 7;
|
||||
$masked = $secondbyte >> 7;
|
||||
|
||||
if ($masked) {
|
||||
Worker::safeEcho("frame masked so close the connection\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
$opcode = $firstbyte & 0xf;
|
||||
|
||||
switch ($opcode) {
|
||||
case 0x0:
|
||||
// Blob type.
|
||||
case 0x1:
|
||||
// Arraybuffer type.
|
||||
case 0x2:
|
||||
// Ping package.
|
||||
case 0x9:
|
||||
// Pong package.
|
||||
case 0xa:
|
||||
break;
|
||||
// Close package.
|
||||
case 0x8:
|
||||
// Try to emit onWebSocketClose callback.
|
||||
if (isset($connection->onWebSocketClose)) {
|
||||
try {
|
||||
($connection->onWebSocketClose)($connection);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
} // Close connection.
|
||||
else {
|
||||
$connection->close();
|
||||
}
|
||||
return 0;
|
||||
// Wrong opcode.
|
||||
default :
|
||||
Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
// Calculate packet length.
|
||||
if ($dataLen === 126) {
|
||||
if (\strlen($buffer) < 4) {
|
||||
return 0;
|
||||
}
|
||||
$pack = \unpack('nn/ntotal_len', $buffer);
|
||||
$currentFrameLength = $pack['total_len'] + 4;
|
||||
} else if ($dataLen === 127) {
|
||||
if (\strlen($buffer) < 10) {
|
||||
return 0;
|
||||
}
|
||||
$arr = \unpack('n/N2c', $buffer);
|
||||
$currentFrameLength = $arr['c1'] * 4294967296 + $arr['c2'] + 10;
|
||||
} else {
|
||||
$currentFrameLength = $dataLen + 2;
|
||||
}
|
||||
|
||||
$totalPackageSize = \strlen($connection->context->websocketDataBuffer) + $currentFrameLength;
|
||||
if ($totalPackageSize > $connection->maxPackageSize) {
|
||||
Worker::safeEcho("error package. package_length=$totalPackageSize\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($isFinFrame) {
|
||||
if ($opcode === 0x9) {
|
||||
if ($recvLen >= $currentFrameLength) {
|
||||
$pingData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($currentFrameLength);
|
||||
$tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
if (isset($connection->onWebSocketPing)) {
|
||||
try {
|
||||
($connection->onWebSocketPing)($connection, $pingData);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
} else {
|
||||
$connection->send($pingData);
|
||||
}
|
||||
$connection->websocketType = $tmpConnectionType;
|
||||
if ($recvLen > $currentFrameLength) {
|
||||
return static::input(\substr($buffer, $currentFrameLength), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
} else if ($opcode === 0xa) {
|
||||
if ($recvLen >= $currentFrameLength) {
|
||||
$pongData = static::decode(\substr($buffer, 0, $currentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($currentFrameLength);
|
||||
$tmpConnectionType = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB;
|
||||
$connection->websocketType = "\x8a";
|
||||
// Try to emit onWebSocketPong callback.
|
||||
if (isset($connection->onWebSocketPong)) {
|
||||
try {
|
||||
($connection->onWebSocketPong)($connection, $pongData);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
$connection->websocketType = $tmpConnectionType;
|
||||
if ($recvLen > $currentFrameLength) {
|
||||
return static::input(\substr($buffer, $currentFrameLength), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return $currentFrameLength;
|
||||
} else {
|
||||
$connection->context->websocketCurrentFrameLength = $currentFrameLength;
|
||||
}
|
||||
}
|
||||
// Received just a frame length data.
|
||||
if ($connection->context->websocketCurrentFrameLength === $recvLen) {
|
||||
self::decode($buffer, $connection);
|
||||
$connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength);
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
return 0;
|
||||
} // The length of the received data is greater than the length of a frame.
|
||||
elseif ($connection->context->websocketCurrentFrameLength < $recvLen) {
|
||||
self::decode(\substr($buffer, 0, $connection->context->websocketCurrentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($connection->context->websocketCurrentFrameLength);
|
||||
$currentFrameLength = $connection->context->websocketCurrentFrameLength;
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
// Continue to read next frame.
|
||||
return self::input(\substr($buffer, $currentFrameLength), $connection);
|
||||
} // The length of the received data is less than the length of a frame.
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($payload, ConnectionInterface $connection)
|
||||
{
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = self::BINARY_TYPE_BLOB;
|
||||
}
|
||||
$payload = (string)$payload;
|
||||
if (empty($connection->context->handshakeStep)) {
|
||||
static::sendHandshake($connection);
|
||||
}
|
||||
|
||||
$maskKey = "\x00\x00\x00\x00";
|
||||
$length = \strlen($payload);
|
||||
|
||||
if (strlen($payload) < 126) {
|
||||
$head = chr(0x80 | $length);
|
||||
} elseif ($length < 0xFFFF) {
|
||||
$head = chr(0x80 | 126) . pack("n", $length);
|
||||
} else {
|
||||
$head = chr(0x80 | 127) . pack("N", 0) . pack("N", $length);
|
||||
}
|
||||
|
||||
$frame = $connection->websocketType . $head . $maskKey;
|
||||
// append payload to frame:
|
||||
$maskKey = \str_repeat($maskKey, \floor($length / 4)) . \substr($maskKey, 0, $length % 4);
|
||||
$frame .= $payload ^ $maskKey;
|
||||
if ($connection->context->handshakeStep === 1) {
|
||||
// If buffer has already full then discard the current package.
|
||||
if (\strlen($connection->context->tmpWebsocketData) > $connection->maxSendBufferSize) {
|
||||
if ($connection->onError) {
|
||||
try {
|
||||
($connection->onError)($connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
$connection->context->tmpWebsocketData = $connection->context->tmpWebsocketData . $frame;
|
||||
// Check buffer is full.
|
||||
if ($connection->maxSendBufferSize <= \strlen($connection->context->tmpWebsocketData)) {
|
||||
if ($connection->onBufferFull) {
|
||||
try {
|
||||
($connection->onBufferFull)($connection);
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($bytes, ConnectionInterface $connection)
|
||||
{
|
||||
$dataLength = \ord($bytes[1]);
|
||||
|
||||
if ($dataLength === 126) {
|
||||
$decodedData = \substr($bytes, 4);
|
||||
} else if ($dataLength === 127) {
|
||||
$decodedData = \substr($bytes, 10);
|
||||
} else {
|
||||
$decodedData = \substr($bytes, 2);
|
||||
}
|
||||
if ($connection->context->websocketCurrentFrameLength) {
|
||||
$connection->context->websocketDataBuffer .= $decodedData;
|
||||
return $connection->context->websocketDataBuffer;
|
||||
} else {
|
||||
if ($connection->context->websocketDataBuffer !== '') {
|
||||
$decodedData = $connection->context->websocketDataBuffer . $decodedData;
|
||||
$connection->context->websocketDataBuffer = '';
|
||||
}
|
||||
return $decodedData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send websocket handshake data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function onConnect($connection)
|
||||
{
|
||||
static::sendHandshake($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
*/
|
||||
public static function onClose($connection)
|
||||
{
|
||||
$connection->context->handshakeStep = null;
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
$connection->context->tmpWebsocketData = '';
|
||||
$connection->context->websocketDataBuffer = '';
|
||||
if (!empty($connection->context->websocketPingTimer)) {
|
||||
Timer::del($connection->context->websocketPingTimer);
|
||||
$connection->context->websocketPingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send websocket handshake.
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
* @return void
|
||||
*/
|
||||
public static function sendHandshake(ConnectionInterface $connection)
|
||||
{
|
||||
if (!empty($connection->context->handshakeStep)) {
|
||||
return;
|
||||
}
|
||||
// Get Host.
|
||||
$port = $connection->getRemotePort();
|
||||
$host = $port === 80 || $port === 443 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port;
|
||||
// Handshake header.
|
||||
$connection->context->websocketSecKey = \base64_encode(random_bytes(16));
|
||||
$userHeader = $connection->headers ?? null;
|
||||
$userHeaderStr = '';
|
||||
if (!empty($userHeader)) {
|
||||
if (\is_array($userHeader)) {
|
||||
foreach ($userHeader as $k => $v) {
|
||||
$userHeaderStr .= "$k: $v\r\n";
|
||||
}
|
||||
} else {
|
||||
$userHeaderStr .= $userHeader;
|
||||
}
|
||||
$userHeaderStr = "\r\n" . \trim($userHeaderStr);
|
||||
}
|
||||
$header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n" .
|
||||
(!\preg_match("/\nHost:/i", $userHeaderStr) ? "Host: $host\r\n" : '') .
|
||||
"Connection: Upgrade\r\n" .
|
||||
"Upgrade: websocket\r\n" .
|
||||
(isset($connection->websocketOrigin) ? "Origin: " . $connection->websocketOrigin . "\r\n" : '') .
|
||||
(isset($connection->websocketClientProtocol) ? "Sec-WebSocket-Protocol: " . $connection->websocketClientProtocol . "\r\n" : '') .
|
||||
"Sec-WebSocket-Version: 13\r\n" .
|
||||
"Sec-WebSocket-Key: " . $connection->context->websocketSecKey . $userHeaderStr . "\r\n\r\n";
|
||||
$connection->send($header, true);
|
||||
$connection->context->handshakeStep = 1;
|
||||
$connection->context->websocketCurrentFrameLength = 0;
|
||||
$connection->context->websocketDataBuffer = '';
|
||||
$connection->context->tmpWebsocketData = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket handshake.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function dealHandshake($buffer, ConnectionInterface $connection)
|
||||
{
|
||||
$pos = \strpos($buffer, "\r\n\r\n");
|
||||
if ($pos) {
|
||||
//checking Sec-WebSocket-Accept
|
||||
if (\preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) {
|
||||
if ($match[1] !== \base64_encode(\sha1($connection->context->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) {
|
||||
Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . \substr($buffer, 0, $pos) . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . \substr($buffer, 0, $pos) . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// handshake complete
|
||||
|
||||
// Get WebSocket subprotocol (if specified by server)
|
||||
if (\preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) {
|
||||
$connection->websocketServerProtocol = \trim($match[1]);
|
||||
}
|
||||
|
||||
$connection->context->handshakeStep = 2;
|
||||
$handshakeResponseLength = $pos + 4;
|
||||
// Try to emit onWebSocketConnect callback.
|
||||
if (isset($connection->onWebSocketConnect)) {
|
||||
try {
|
||||
($connection->onWebSocketConnect)($connection, \substr($buffer, 0, $handshakeResponseLength));
|
||||
} catch (\Throwable $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
// Headbeat.
|
||||
if (!empty($connection->websocketPingInterval)) {
|
||||
$connection->context->websocketPingTimer = Timer::add($connection->websocketPingInterval, function () use ($connection) {
|
||||
if (false === $connection->send(\pack('H*', '898000000000'), true)) {
|
||||
Timer::del($connection->context->websocketPingTimer);
|
||||
$connection->context->websocketPingTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$connection->consumeRecvBuffer($handshakeResponseLength);
|
||||
if (!empty($connection->context->tmpWebsocketData)) {
|
||||
$connection->send($connection->context->tmpWebsocketData, true);
|
||||
$connection->context->tmpWebsocketData = '';
|
||||
}
|
||||
if (\strlen($buffer) > $handshakeResponseLength) {
|
||||
return self::input(\substr($buffer, $handshakeResponseLength), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
342
vendor/workerman/workerman/README.md
vendored
Normal file
342
vendor/workerman/workerman/README.md
vendored
Normal file
@@ -0,0 +1,342 @@
|
||||
# Workerman
|
||||
[](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
|
||||
## What is it
|
||||
Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications.
|
||||
Workerman supports HTTP, Websocket, SSL and other custom protocols.
|
||||
Workerman supports event extension.
|
||||
|
||||
## Requires
|
||||
PHP 7.0 or Higher
|
||||
A POSIX compatible operating system (Linux, OSX, BSD)
|
||||
POSIX and PCNTL extensions required
|
||||
Event extension recommended for better performance
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
composer require workerman/workerman
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### A websocket server
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Create a Websocket server
|
||||
$ws_worker = new Worker('websocket://0.0.0.0:2346');
|
||||
|
||||
// Emitted when new connection come
|
||||
$ws_worker->onConnect = function ($connection) {
|
||||
echo "New connection\n";
|
||||
};
|
||||
|
||||
// Emitted when data received
|
||||
$ws_worker->onMessage = function ($connection, $data) {
|
||||
// Send hello $data
|
||||
$connection->send('Hello ' . $data);
|
||||
};
|
||||
|
||||
// Emitted when connection closed
|
||||
$ws_worker->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
// Run worker
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### An http server
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// #### http worker ####
|
||||
$http_worker = new Worker('http://0.0.0.0:2345');
|
||||
|
||||
// 4 processes
|
||||
$http_worker->count = 4;
|
||||
|
||||
// Emitted when data received
|
||||
$http_worker->onMessage = function ($connection, $request) {
|
||||
//$request->get();
|
||||
//$request->post();
|
||||
//$request->header();
|
||||
//$request->cookie();
|
||||
//$request->session();
|
||||
//$request->uri();
|
||||
//$request->path();
|
||||
//$request->method();
|
||||
|
||||
// Send data to client
|
||||
$connection->send("Hello World");
|
||||
};
|
||||
|
||||
// Run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### A tcp server
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// #### create socket and listen 1234 port ####
|
||||
$tcp_worker = new Worker('tcp://0.0.0.0:1234');
|
||||
|
||||
// 4 processes
|
||||
$tcp_worker->count = 4;
|
||||
|
||||
// Emitted when new connection come
|
||||
$tcp_worker->onConnect = function ($connection) {
|
||||
echo "New Connection\n";
|
||||
};
|
||||
|
||||
// Emitted when data received
|
||||
$tcp_worker->onMessage = function ($connection, $data) {
|
||||
// Send data to client
|
||||
$connection->send("Hello $data \n");
|
||||
};
|
||||
|
||||
// Emitted when connection is closed
|
||||
$tcp_worker->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### A udp server
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$worker = new Worker('udp://0.0.0.0:1234');
|
||||
|
||||
// 4 processes
|
||||
$tcp_worker->count = 4;
|
||||
|
||||
// Emitted when data received
|
||||
$worker->onMessage = function($connection, $data)
|
||||
{
|
||||
$connection->send($data);
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Enable SSL
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// SSL context.
|
||||
$context = array(
|
||||
'ssl' => array(
|
||||
'local_cert' => '/your/path/of/server.pem',
|
||||
'local_pk' => '/your/path/of/server.key',
|
||||
'verify_peer' => false,
|
||||
)
|
||||
);
|
||||
|
||||
// Create a Websocket server with ssl context.
|
||||
$ws_worker = new Worker('websocket://0.0.0.0:2346', $context);
|
||||
|
||||
// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://).
|
||||
// The similar approaches for Https etc.
|
||||
$ws_worker->transport = 'ssl';
|
||||
|
||||
$ws_worker->onMessage = function ($connection, $data) {
|
||||
// Send hello $data
|
||||
$connection->send('Hello ' . $data);
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Custom protocol
|
||||
Protocols/MyTextProtocol.php
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Protocols;
|
||||
|
||||
/**
|
||||
* User defined protocol
|
||||
* Format Text+"\n"
|
||||
*/
|
||||
class MyTextProtocol
|
||||
{
|
||||
public static function input($recv_buffer)
|
||||
{
|
||||
// Find the position of the first occurrence of "\n"
|
||||
$pos = strpos($recv_buffer, "\n");
|
||||
|
||||
// Not a complete package. Return 0 because the length of package can not be calculated
|
||||
if ($pos === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return length of the package
|
||||
return $pos+1;
|
||||
}
|
||||
|
||||
public static function decode($recv_buffer)
|
||||
{
|
||||
return trim($recv_buffer);
|
||||
}
|
||||
|
||||
public static function encode($data)
|
||||
{
|
||||
return $data . "\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// #### MyTextProtocol worker ####
|
||||
$text_worker = new Worker('MyTextProtocol://0.0.0.0:5678');
|
||||
|
||||
$text_worker->onConnect = function ($connection) {
|
||||
echo "New connection\n";
|
||||
};
|
||||
|
||||
$text_worker->onMessage = function ($connection, $data) {
|
||||
// Send data to client
|
||||
$connection->send("Hello world\n");
|
||||
};
|
||||
|
||||
$text_worker->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
// Run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Timer
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Timer;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$task = new Worker();
|
||||
$task->onWorkerStart = function ($task) {
|
||||
// 2.5 seconds
|
||||
$time_interval = 2.5;
|
||||
$timer_id = Timer::add($time_interval, function () {
|
||||
echo "Timer run\n";
|
||||
});
|
||||
};
|
||||
|
||||
// Run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### AsyncTcpConnection (tcp/ws/text/frame etc...)
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Connection\AsyncTcpConnection;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->onWorkerStart = function () {
|
||||
// Websocket protocol for client.
|
||||
$ws_connection = new AsyncTcpConnection('ws://echo.websocket.org:80');
|
||||
$ws_connection->onConnect = function ($connection) {
|
||||
$connection->send('Hello');
|
||||
};
|
||||
$ws_connection->onMessage = function ($connection, $data) {
|
||||
echo "Recv: $data\n";
|
||||
};
|
||||
$ws_connection->onError = function ($connection, $code, $msg) {
|
||||
echo "Error: $msg\n";
|
||||
};
|
||||
$ws_connection->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
$ws_connection->connect();
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Available commands
|
||||
```php start.php start ```
|
||||
```php start.php start -d ```
|
||||

|
||||
```php start.php status ```
|
||||

|
||||
```php start.php connections```
|
||||
```php start.php stop ```
|
||||
```php start.php restart ```
|
||||
```php start.php reload ```
|
||||
|
||||
## Documentation
|
||||
|
||||
中文主页:[http://www.workerman.net](https://www.workerman.net)
|
||||
|
||||
中文文档: [https://www.workerman.net/doc/workerman](https://www.workerman.net/doc/workerman)
|
||||
|
||||
Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/SUMMARY.md)
|
||||
|
||||
# Benchmarks
|
||||
https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=db&l=yyku7z-e7&a=2
|
||||

|
||||
|
||||
## Sponsors
|
||||
[opencollective.com/walkor](https://opencollective.com/walkor)
|
||||
|
||||
[patreon.com/walkor](https://patreon.com/walkor)
|
||||
|
||||
## Donate
|
||||
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UQGGS9UB35WWG"><img src="http://donate.workerman.net/img/donate.png"></a>
|
||||
|
||||
## Other links with workerman
|
||||
|
||||
[webman](https://github.com/walkor/webman)
|
||||
[PHPSocket.IO](https://github.com/walkor/phpsocket.io)
|
||||
[php-socks5](https://github.com/walkor/php-socks5)
|
||||
[php-http-proxy](https://github.com/walkor/php-http-proxy)
|
||||
|
||||
## LICENSE
|
||||
|
||||
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).
|
||||
220
vendor/workerman/workerman/Timer.php
vendored
Normal file
220
vendor/workerman/workerman/Timer.php
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
<?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 Workerman;
|
||||
|
||||
use Workerman\Events\EventInterface;
|
||||
use Workerman\Worker;
|
||||
use \Exception;
|
||||
|
||||
/**
|
||||
* Timer.
|
||||
*
|
||||
* example:
|
||||
* Workerman\Timer::add($time_interval, callback, array($arg1, $arg2..));
|
||||
*/
|
||||
class Timer
|
||||
{
|
||||
/**
|
||||
* Tasks that based on ALARM signal.
|
||||
* [
|
||||
* run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
|
||||
* run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]],
|
||||
* ..
|
||||
* ]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_tasks = array();
|
||||
|
||||
/**
|
||||
* event
|
||||
*
|
||||
* @var EventInterface
|
||||
*/
|
||||
protected static $_event = null;
|
||||
|
||||
/**
|
||||
* timer id
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $_timerId = 0;
|
||||
|
||||
/**
|
||||
* timer status
|
||||
* [
|
||||
* timer_id1 => bool,
|
||||
* timer_id2 => bool,
|
||||
* ....................,
|
||||
* ]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_status = array();
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @param EventInterface $event
|
||||
* @return void
|
||||
*/
|
||||
public static function init($event = null)
|
||||
{
|
||||
if ($event) {
|
||||
self::$_event = $event;
|
||||
return;
|
||||
}
|
||||
if (\function_exists('pcntl_signal')) {
|
||||
\pcntl_signal(\SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ALARM signal handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function signalHandle()
|
||||
{
|
||||
if (!self::$_event) {
|
||||
\pcntl_alarm(1);
|
||||
self::tick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a timer.
|
||||
*
|
||||
* @param float $time_interval
|
||||
* @param callable $func
|
||||
* @param mixed $args
|
||||
* @param bool $persistent
|
||||
* @return int|bool
|
||||
*/
|
||||
public static function add($time_interval, $func, $args = array(), $persistent = true)
|
||||
{
|
||||
if ($time_interval <= 0) {
|
||||
Worker::safeEcho(new Exception("bad time_interval"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($args === null) {
|
||||
$args = array();
|
||||
}
|
||||
|
||||
if (self::$_event) {
|
||||
return self::$_event->add($time_interval,
|
||||
$persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args);
|
||||
}
|
||||
|
||||
// If not workerman runtime just return.
|
||||
if (!Worker::getAllWorkers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_callable($func)) {
|
||||
Worker::safeEcho(new Exception("not callable"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty(self::$_tasks)) {
|
||||
\pcntl_alarm(1);
|
||||
}
|
||||
|
||||
$run_time = \time() + $time_interval;
|
||||
if (!isset(self::$_tasks[$run_time])) {
|
||||
self::$_tasks[$run_time] = array();
|
||||
}
|
||||
|
||||
self::$_timerId = self::$_timerId == \PHP_INT_MAX ? 1 : ++self::$_timerId;
|
||||
self::$_status[self::$_timerId] = true;
|
||||
self::$_tasks[$run_time][self::$_timerId] = array($func, (array)$args, $persistent, $time_interval);
|
||||
|
||||
return self::$_timerId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tick.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tick()
|
||||
{
|
||||
if (empty(self::$_tasks)) {
|
||||
\pcntl_alarm(0);
|
||||
return;
|
||||
}
|
||||
$time_now = \time();
|
||||
foreach (self::$_tasks as $run_time => $task_data) {
|
||||
if ($time_now >= $run_time) {
|
||||
foreach ($task_data as $index => $one_task) {
|
||||
$task_func = $one_task[0];
|
||||
$task_args = $one_task[1];
|
||||
$persistent = $one_task[2];
|
||||
$time_interval = $one_task[3];
|
||||
try {
|
||||
\call_user_func_array($task_func, $task_args);
|
||||
} catch (\Exception $e) {
|
||||
Worker::safeEcho($e);
|
||||
}
|
||||
if($persistent && !empty(self::$_status[$index])) {
|
||||
$new_run_time = \time() + $time_interval;
|
||||
if(!isset(self::$_tasks[$new_run_time])) self::$_tasks[$new_run_time] = array();
|
||||
self::$_tasks[$new_run_time][$index] = array($task_func, (array)$task_args, $persistent, $time_interval);
|
||||
}
|
||||
}
|
||||
unset(self::$_tasks[$run_time]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a timer.
|
||||
*
|
||||
* @param mixed $timer_id
|
||||
* @return bool
|
||||
*/
|
||||
public static function del($timer_id)
|
||||
{
|
||||
if (self::$_event) {
|
||||
return self::$_event->del($timer_id, EventInterface::EV_TIMER);
|
||||
}
|
||||
|
||||
foreach(self::$_tasks as $run_time => $task_data)
|
||||
{
|
||||
if(array_key_exists($timer_id, $task_data)) unset(self::$_tasks[$run_time][$timer_id]);
|
||||
}
|
||||
|
||||
if(array_key_exists($timer_id, self::$_status)) unset(self::$_status[$timer_id]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delAll()
|
||||
{
|
||||
self::$_tasks = self::$_status = array();
|
||||
if (\function_exists('pcntl_alarm')) {
|
||||
\pcntl_alarm(0);
|
||||
}
|
||||
if (self::$_event) {
|
||||
self::$_event->clearAllTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
2756
vendor/workerman/workerman/Worker.php
vendored
Normal file
2756
vendor/workerman/workerman/Worker.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
38
vendor/workerman/workerman/composer.json
vendored
Normal file
38
vendor/workerman/workerman/composer.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "workerman/workerman",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"event-loop",
|
||||
"asynchronous"
|
||||
],
|
||||
"homepage": "http://www.workerman.net",
|
||||
"license": "MIT",
|
||||
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "walkor",
|
||||
"email": "walkor@workerman.net",
|
||||
"homepage": "http://www.workerman.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "walkor@workerman.net",
|
||||
"issues": "https://github.com/walkor/workerman/issues",
|
||||
"forum": "http://wenda.workerman.net/",
|
||||
"wiki": "http://doc.workerman.net/",
|
||||
"source": "https://github.com/walkor/workerman"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Workerman\\": "./"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
Reference in New Issue
Block a user