Compare commits

...

10 Commits

Author SHA1 Message Date
Echo Noch
d61c5656c5 yes 2024-12-15 11:21:10 +08:00
walkor
663e8e8fcf
Merge pull request #10 from linkec/master
Rebuild and Support UDP ASSOCIATE
2020-09-13 10:04:16 +08:00
Link
1572a141ff rebuild 2020-09-13 00:52:49 +08:00
walkor
dc1a3390b5
Update start.php 2020-05-07 10:27:35 +08:00
walkor
c3368cc272
Merge pull request #4 from marekrost/master
Added plaintext username/password auth
2018-06-20 13:07:48 +08:00
Marek Rost
0d8a43830c
Update README.md 2018-06-19 18:17:05 +02:00
Marek Rost
f9e7afba03
Update README.md 2018-06-19 18:16:40 +02:00
Marek Rost
2ba2822baf
Update README.md 2018-06-19 18:14:52 +02:00
Marek Rost
5aef507ca5
Update README.md 2018-06-19 18:13:10 +02:00
Marek Rost
53c70b72ce Config fix, updated README 2018-06-19 18:11:41 +02:00
62 changed files with 14109 additions and 106 deletions

View File

@ -1,19 +1,22 @@
# socks5-proxy # socks5-proxy
Socks5 proxy written in PHP based on [workerman](https://github.com/walkor/Workerman). Socks5 proxy written in PHP based on [workerman](https://github.com/walkor/Workerman). Now with username/password authentication according to RFC 1929.
## Install ## Install
1. ```git clone https://github.com/walkor/php-socks5``` 1. ```git clone https://github.com/walkor/php-socks5```
2. ```composer install``` 2. ```composer install```
## Config
Edit file ```config.php```
## Start ## Start
php start.php start -d ```php start.php start -d```
## Stop ## Stop
php start.php stop ```php start.php stop```
## Status ## Status
php start.php status ```php start.php status```
## Other links ## Other links
https://github.com/walkor/shadowsocks-php https://github.com/walkor/shadowsocks-php

82
composer.lock generated Normal file
View File

@ -0,0 +1,82 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7d5fe279e64858f72c1db8f82722e08b",
"packages": [
{
"name": "workerman/workerman",
"version": "v4.1.15",
"source": {
"type": "git",
"url": "https://github.com/walkor/workerman.git",
"reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/workerman/zipball/afc8242fc769ab7cf22eb4ac22b97cb59d465e4e",
"reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"type": "library",
"autoload": {
"psr-4": {
"Workerman\\": "./"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net",
"keywords": [
"asynchronous",
"event-loop"
],
"support": {
"email": "walkor@workerman.net",
"forum": "http://wenda.workerman.net/",
"issues": "https://github.com/walkor/workerman/issues",
"source": "https://github.com/walkor/workerman",
"wiki": "http://doc.workerman.net/"
},
"funding": [
{
"url": "https://opencollective.com/workerman",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/walkor",
"type": "patreon"
}
],
"time": "2024-02-19T02:10:39+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.6.0"
}

View File

@ -1,3 +1,14 @@
$AUTH_ENABLED = 0; <?php
$USERNAME = 'changeme';
$PASSWORD = '1234'; $config = [
"auth" => [
METHOD_NO_AUTH => true,
METHOD_USER_PASS => function ($request) {
return $request['user'] == 'user' && $request['pass'] == 'pass';
}
],
"log_level" => LOG_DEBUG,
"tcp_port" => 20501,
"udp_port" => 0,
"wanIP" => '10.88.0.4',
];

587
start.php
View File

@ -1,14 +1,27 @@
<?php <?php
/**
* This file is part of https://github.com/walkor/php-socks5.
*
* 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
*/
ini_set("memory_limit", "512M");
use \Workerman\Worker; use \Workerman\Worker;
use \Workerman\WebServer; use \Workerman\Timer;
use \Workerman\Connection\TcpConnection;
use \Workerman\Connection\AsyncTcpConnection; use \Workerman\Connection\AsyncTcpConnection;
use \Workerman\Connection\AsyncUdpConnection;
// 自动加载类 // 自动加载类
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/config.php';
define('STAGE_INIT', 0); define('STAGE_INIT', 0);
define('STAGE_AUTH', 1); define('STAGE_AUTH', 1);
define('STAGE_ADDR', 2); define('STAGE_ADDR', 2);
@ -22,6 +35,16 @@ define('CMD_CONNECT', 1);
define('CMD_BIND', 2); define('CMD_BIND', 2);
define('CMD_UDP_ASSOCIATE', 3); define('CMD_UDP_ASSOCIATE', 3);
define('ERR_GENERAL', 1);
define('ERR_NOT_ALLOW', 2);
define('ERR_NETWORK', 3);
define('ERR_HOST', 4);
define('ERR_REFUSE', 5);
define('ERR_TTL_EXPIRED', 6);
define('ERR_UNKNOW_COMMAND', 7);
define('ERR_UNKNOW_ADDR_TYPE', 8);
define('ERR_UNKNOW', 9);
define('ADDRTYPE_IPV4', 1); define('ADDRTYPE_IPV4', 1);
define('ADDRTYPE_IPV6', 4); define('ADDRTYPE_IPV6', 4);
define('ADDRTYPE_HOST', 3); define('ADDRTYPE_HOST', 3);
@ -30,131 +53,515 @@ define('METHOD_NO_AUTH', 0);
define('METHOD_GSSAPI', 1); define('METHOD_GSSAPI', 1);
define('METHOD_USER_PASS', 2); define('METHOD_USER_PASS', 2);
$worker = new Worker('tcp://0.0.0.0:1080'); require_once __DIR__ . '/config.php';
$worker->onConnect = function($connection)
{ if (count($config['auth']) == 0) {
$config['auth'] = [METHOD_NO_AUTH => true];
}
$worker = new Worker('tcp://0.0.0.0:' . $config['tcp_port']);
$worker->onConnect = function ($connection) {
$connection->stage = STAGE_INIT; $connection->stage = STAGE_INIT;
$connection->auth_type = NULL;
}; };
$worker->onMessage = function($connection, $buffer) $worker->onMessage = function ($connection, $buffer) {
{ global $config;
switch($connection->stage) logger(LOG_DEBUG, "recv:" . bin2hex($buffer));
{ switch ($connection->stage) {
// 初始化环节
case STAGE_INIT: case STAGE_INIT:
if ($AUTH_ENABLED) $request = [];
{ // 当前偏移量
$methodslen = ord($buffer[1]); $offset = 0;
$methods = array();
for ($i = 0; $i < strlen($buffer)-3; $i++) // 检测buffer长度
{ if (strlen($buffer) < 2) {
array_push($methods, ord($buffer[$i+3])); logger(LOG_ERR, "init socks5 failed. buffer too short.");
}
if (in_array(METHOD_USER_PASS, $methods))
{
$connection->send("\x05\x02");
$connection->stage = STAGE_AUTH;
return;
}
echo "client does not support user/pass auth\n";
$connection->send("\x05\xff"); $connection->send("\x05\xff");
$connection->stage = STAGE_DESTROYED; $connection->stage = STAGE_DESTROYED;
$connection->close(); $connection->close();
return; return;
} }
$connection->send("\x05\x00");
$connection->stage = STAGE_ADDR; // Socks5 版本
return; $request['ver'] = ord($buffer[$offset]);
case STAGE_AUTH: $offset += 1;
$userlen = ord($buffer[1]);
$user = substr($buffer, 2, $userlen); // 认证方法数量
$passlen = ord($buffer[2 + $userlen]); $request['method_count'] = ord($buffer[$offset]);
$pass = substr($buffer, 3 + $userlen, $passlen); $offset += 1;
if ($user == $USERNAME && $pass == $PASSWORD)
{ if (strlen($buffer) < 2 + $request['method_count']) {
$connection->send("\x05\x00"); logger(LOG_ERR, "init authentic failed. buffer too short.");
$connection->stage = STAGE_ADDR; $connection->send("\x05\xff");
return;
}
echo "auth failed\n";
$connection->send("\x05\x01");
$connection->stage = STAGE_DESTROYED; $connection->stage = STAGE_DESTROYED;
$connection->close(); $connection->close();
return; return;
}
// 客户端支持的认证方法
$request['methods'] = [];
for ($i = 1; $i <= $request['method_count']; $i++) {
$request['methods'][] = ord($buffer[$offset]);
$offset++;
}
foreach ($config['auth'] as $k => $v) {
if (in_array($k, $request['methods'])) {
logger(LOG_INFO, "auth client via method $k");
logger(LOG_DEBUG, "send:" . bin2hex("\x05" . chr($k)));
$connection->send("\x05" . chr($k));
if ($k == 0) {
$connection->stage = STAGE_ADDR;
} else {
$connection->stage = STAGE_AUTH;
}
$connection->auth_type = $k;
return;
}
}
if ($connection->stage != STAGE_AUTH) {
logger(LOG_ERR, "client has no matched auth methods");
logger(LOG_DEBUG, "send:" . bin2hex("\x05\xff"));
$connection->send("\x05\xff");
$connection->stage = STAGE_DESTROYED;
$connection->close();
}
return;
// 认证环节
case STAGE_AUTH:
$request = [];
// 当前偏移量
$offset = 0;
if (strlen($buffer) < 5) {
logger(LOG_ERR, "auth failed. buffer too short.");
$connection->send("\x01\x01");
$connection->stage = STAGE_DESTROYED;
$connection->close();
return;
}
// var_dump($connection->auth_type);
switch ($connection->auth_type) {
case METHOD_USER_PASS:
// 子协议 协商 版本
$request['sub_ver'] = ord($buffer[$offset]);
$offset += 1;
// 用户名
$request['user_len'] = ord($buffer[$offset]);
$offset += 1;
if (strlen($buffer) < 2 + $request['user_len'] + 2) {
logger(LOG_ERR, "auth username failed. buffer too short.");
$connection->send("\x01\x01");
$connection->stage = STAGE_DESTROYED;
$connection->close();
return;
}
$request['user'] = substr($buffer, $offset, $request['user_len']);
$offset += $request['user_len'];
// 密码
$request['pass_len'] = ord($buffer[$offset]);
$offset += 1;
if (strlen($buffer) < 2 + $request['user_len'] + 1 + $request['pass_len']) {
logger(LOG_ERR, "auth password failed. buffer too short.");
$connection->send("\x01\x01");
$connection->stage = STAGE_DESTROYED;
$connection->close();
return;
}
$request['pass'] = substr($buffer, $offset, $request['pass_len']);
$offset += $request['pass_len'];
if ($config["auth"][METHOD_USER_PASS]($request)) {
logger(LOG_INFO, "auth ok");
$connection->send("\x01\x00");
$connection->stage = STAGE_ADDR;
} else {
logger(LOG_INFO, "auth failed");
$connection->send("\x01\x01");
$connection->stage = STAGE_DESTROYED;
$connection->close();
}
break;
default:
logger(LOG_ERR, "unsupport auth type");
$connection->send("\x01\x01");
$connection->stage = STAGE_DESTROYED;
$connection->close();
break;
}
return;
case STAGE_ADDR: case STAGE_ADDR:
$cmd = ord($buffer[1]); $request = [];
if($cmd != CMD_CONNECT) // 当前偏移量
{ $offset = 0;
echo "bad cmd $cmd\n";
$connection->close(); if (strlen($buffer) < 4) {
logger(LOG_ERR, "connect init failed. buffer too short.");
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_GENERAL;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
return; return;
} }
$header_data = parse_socket5_header($buffer);
if(!$header_data) // Socks 版本
{ $request['ver'] = ord($buffer[$offset]);
$connection->close(); $offset += 1;
// 命令
$request['command'] = ord($buffer[$offset]);
$offset += 1;
// RSV
$request['rsv'] = ord($buffer[$offset]);
$offset += 1;
// AddressType
$request['addr_type'] = ord($buffer[$offset]);
$offset += 1;
// DestAddr
switch ($request['addr_type']) {
case ADDRTYPE_IPV4:
if (strlen($buffer) < 4 + 4) {
logger(LOG_ERR, "connect init failed.[ADDRTYPE_IPV4] buffer too short.");
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_GENERAL;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
return; return;
} }
$tmp = substr($buffer, $offset, 4);
$ip = 0;
for ($i = 0; $i < 4; $i++) {
// var_dump(ord($tmp[$i]));
$ip += ord($tmp[$i]) * pow(256, 3 - $i);
}
$request['dest_addr'] = long2ip($ip);
$offset += 4;
break;
case ADDRTYPE_HOST:
$request['host_len'] = ord($buffer[$offset]);
$offset += 1;
if (strlen($buffer) < 4 + 1 + $request['host_len']) {
logger(LOG_ERR, "connect init failed.[ADDRTYPE_HOST] buffer too short.");
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_GENERAL;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
return;
}
$request['dest_addr'] = substr($buffer, $offset, $request['host_len']);
$offset += $request['host_len'];
break;
case ADDRTYPE_IPV6:
default:
logger(LOG_ERR, "unsupport ipv6. [ADDRTYPE_IPV6].");
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_UNKNOW_ADDR_TYPE;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
return;
break;
}
// DestPort
if (strlen($buffer) < $offset + 2) {
logger(LOG_ERR, "connect init failed.[port] buffer too short.");
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_GENERAL;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
return;
}
$portData = unpack("n", substr($buffer, $offset, 2));
$request['dest_port'] = $portData[1];
$offset += 2;
// var_dump($request);
switch ($request['command']) {
case CMD_CONNECT:
logger(LOG_DEBUG, 'tcp://' . $request['dest_addr'] . ':' . $request['dest_port']);
if ($request['addr_type'] == ADDRTYPE_HOST) {
if (!filter_var($request['dest_addr'], FILTER_VALIDATE_IP)) {
logger(LOG_DEBUG, 'resolve DNS ' . $request['dest_addr']);
$connection->stage = STAGE_DNS;
$addr = dns_get_record($request['dest_addr'], DNS_A);
$addr = $addr ? array_pop($addr) : null;
logger(LOG_DEBUG, 'DNS resolved ' . $request['dest_addr'] . ' => ' . $addr['ip']);
} else {
$addr['ip'] = $request['dest_addr'];
}
} else {
$addr['ip'] = $request['dest_addr'];
}
if ($addr) {
$connection->stage = STAGE_CONNECTING; $connection->stage = STAGE_CONNECTING;
$remote_connection = new AsyncTcpConnection('tcp://'.$header_data[1].':'.$header_data[2]); $remote_connection = new AsyncTcpConnection('tcp://' . $addr['ip'] . ':' . $request['dest_port']);
$remote_connection->onConnect = function($remote_connection)use($connection) $remote_connection->onConnect = function ($remote_connection) use ($connection, $request) {
{
$connection->state = STAGE_STREAM; $connection->state = STAGE_STREAM;
$connection->send("\x05\x00\x00\x01\x00\x00\x00\x00\x10\x10"); $response = [];
$response['ver'] = 5;
$response['rep'] = 0;
$response['rsv'] = 0;
$response['addr_type'] = $request['addr_type'];
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 18512;
$connection->send(packResponse($response));
$connection->pipe($remote_connection); $connection->pipe($remote_connection);
$remote_connection->pipe($connection); $remote_connection->pipe($connection);
logger(LOG_DEBUG, 'tcp://' . $request['dest_addr'] . ':' . $request['dest_port'] . ' [OK]');
}; };
$remote_connection->connect(); $remote_connection->connect();
} else {
logger(LOG_DEBUG, 'DNS resolve failed.');
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_HOST;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
}
break;
case CMD_UDP_ASSOCIATE:
$connection->stage = STAGE_UDP_ASSOC;
var_dump("CMD_UDP_ASSOCIATE " . $config['udp_port']);
if ($config['udp_port'] == 0) {
$connection->udpWorker = new Worker('udp://0.0.0.0:0');
$connection->udpWorker->incId = 0;
$connection->udpWorker->onMessage = function ($udp_connection, $data) use ($connection) {
udpWorkerOnMessage($udp_connection, $data, $connection->udpWorker);
};
$connection->udpWorker->listen();
$listenInfo = stream_socket_get_name($connection->udpWorker->getMainSocket(), false);
list($bind_addr, $bind_port) = explode(":", $listenInfo);
} else {
$bind_port = $config['udp_port'];
}
$bind_addr = $config['wanIP'];
$response['ver'] = 5;
$response['rep'] = 0;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = $bind_addr;
$response['bind_port'] = $bind_port;
logger(LOG_DEBUG, 'send:' . bin2hex(packResponse($response)));
$connection->send(packResponse($response));
break;
default:
logger(LOG_ERR, "connect init failed. unknow command.");
$connection->stage = STAGE_DESTROYED;
$response = [];
$response['ver'] = 5;
$response['rep'] = ERR_UNKNOW_COMMAND;
$response['rsv'] = 0;
$response['addr_type'] = ADDRTYPE_IPV4;
$response['bind_addr'] = '0.0.0.0';
$response['bind_port'] = 0;
$connection->close(packResponse($response));
return;
break;
}
} }
}; };
$worker->onClose = function ($connection) {
logger(LOG_INFO, "client closed.");
};
function udpWorkerOnMessage($udp_connection, $data, &$worker)
function parse_socket5_header($buffer)
{ {
$addr_type = ord($buffer[3]);
switch($addr_type) logger(LOG_DEBUG, 'send:' . bin2hex($data));
{ $request = [];
$offset = 0;
$request['rsv'] = substr($data, $offset, 2);
$offset += 2;
$request['frag'] = ord($data[$offset]);
$offset += 1;
$request['addr_type'] = ord($data[$offset]);
$offset += 1;
switch ($request['addr_type']) {
case ADDRTYPE_IPV4: case ADDRTYPE_IPV4:
if(strlen($buffer) < 10) $tmp = substr($data, $offset, 4);
{ $ip = 0;
echo bin2hex($buffer)."\n"; for ($i = 0; $i < 4; $i++) {
echo "buffer too short\n"; $ip += ord($tmp[$i]) * pow(256, 3 - $i);
return false;
} }
$dest_addr = ord($buffer[4]).'.'.ord($buffer[5]).'.'.ord($buffer[6]).'.'.ord($buffer[7]); $request['dest_addr'] = long2ip($ip);
$port_data = unpack('n', substr($buffer, -2)); $offset += 4;
$dest_port = $port_data[1];
$header_length = 10;
break; break;
case ADDRTYPE_HOST: case ADDRTYPE_HOST:
$addrlen = ord($buffer[4]); $request['host_len'] = ord($data[$offset]);
if(strlen($buffer) < $addrlen + 5) $offset += 1;
{
echo $buffer."\n"; $request['dest_addr'] = substr($data, $offset, $request['host_len']);
echo bin2hex($buffer)."\n"; $offset += $request['host_len'];
echo "buffer too short\n";
return false;
}
$dest_addr = substr($buffer, 5, $addrlen);
$port_data = unpack('n', substr($buffer, -2));
$dest_port = $port_data[1];
$header_length = $addrlen + 7;
break; break;
case ADDRTYPE_IPV6: case ADDRTYPE_IPV6:
if(strlen($buffer) < 22) if (strlen($data) < 22) {
{
echo "buffer too short\n"; echo "buffer too short\n";
return false; $error = true;
break;
} }
echo "todo ipv6\n"; echo "todo ipv6\n";
return false; $error = true;
default: default:
echo "unsupported addrtype $addr_type\n"; echo "unsupported addrtype {$request['addr_type']}\n";
return false; $error = true;
} }
return array($addr_type, $dest_addr, $dest_port, $header_length);
$portData = unpack("n", substr($data, $offset, 2));
$request['dest_port'] = $portData[1];
$offset += 2;
// var_dump($request['dest_addr']);
if ($request['addr_type'] == ADDRTYPE_HOST) {
logger(LOG_DEBUG, '解析DNS');
$addr = dns_get_record($request['dest_addr'], DNS_A);
$addr = $addr ? array_pop($addr) : null;
logger(LOG_DEBUG, 'DNS 解析完成' . $addr['ip']);
} else {
$addr['ip'] = $request['dest_addr'];
}
// var_dump($request);
// var_dump($udp_connection);
$remote_connection = new AsyncUdpConnection('udp://' . $addr['ip'] . ':' . $request['dest_port']);
$remote_connection->id = $worker->incId++;
$remote_connection->udp_connection = $udp_connection;
$remote_connection->onConnect = function ($remote_connection) use ($data, $offset) {
$remote_connection->send(substr($data, $offset));
};
$remote_connection->onMessage = function ($remote_connection, $recv) use ($data, $offset, $udp_connection, $worker) {
$udp_connection->close(substr($data, 0, $offset) . $recv);
$remote_connection->close();
unset($worker->udpConnections[$remote_connection->id]);
};
$remote_connection->deadTime = time() + 3;
$remote_connection->connect();
$worker->udpConnections[$remote_connection->id] = $remote_connection;
} }
// 如果不是在根目录启动则运行runAll方法 $udpWorker = new Worker('udp://0.0.0.0:1080');
if(!defined('GLOBAL_START')) $udpWorker->incId = 0;
$udpWorker->onWorkerStart = function ($worker) {
$worker->udpConnections = [];
Timer::add(1, function () use ($worker) {
foreach ($worker->udpConnections as $id => $remote_connection) {
if ($remote_connection->deadTime < time()) {
$remote_connection->close();
$remote_connection->udp_connection->close();
unset($worker->udpConnections[$id]);
}
}
});
};
$udpWorker->onMessage = 'udpWorkerOnMessage';
function packResponse($response)
{ {
$data = "";
$data .= chr($response['ver']);
$data .= chr($response['rep']);
$data .= chr($response['rsv']);
$data .= chr($response['addr_type']);
switch ($response['addr_type']) {
case ADDRTYPE_IPV4:
$tmp = explode('.', $response['bind_addr']);
foreach ($tmp as $block) {
$data .= chr($block);
}
break;
case ADDRTYPE_HOST:
$host_len = strlen($response['bind_addr']);
$data .= chr($host_len);
$data .= $response['bind_addr'];
break;
}
$data .= pack("n", $response['bind_port']);
return $data;
}
function logger($level, $str)
{
global $config;
if ($config['log_level'] >= $level) {
echo $str . "\n";
}
}
// 如果不是在根目录启动则运行runAll方法
if (!defined('GLOBAL_START')) {
Worker::runAll(); Worker::runAll();
} }

11
superbench.log Normal file
View File

@ -0,0 +1,11 @@
----------------------------------------------------------------------------------
Node Name Upload Speed Download Speed Latency Packet Loss
 Speedtest.net 774.65 Mbit/s 648.17 Mbit/s (*)94.45 ms Not available
 Zhenjiang 5G CT 644.54 Mbit/s 30.92 Mbit/s 46.07 ms Not available
 Nanjing 5G CT 24.85 Mbit/s 744.16 Mbit/s 41.06 ms 13.0%
 Hangzhou CT 521.82 Mbit/s 991.68 Mbit/s 38.51 ms Not available
 Shanghai 5G CU 941.08 Mbit/s 1159.90 Mbit/s 82.42 ms 0.0%
 TianJin 5G CU 1.46 Mbit/s 10.20 Mbit/s 199.76 ms Not available
 Beijing CM 918.89 Mbit/s 1135.99 Mbit/s 73.82 ms Not available
 Chengdu BN 896.37 Mbit/s 26.72 Mbit/s 75.30 ms Not available
----------------------------------------------------------------------------------

1095
superbench.sh Normal file

File diff suppressed because it is too large Load Diff

25
vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit7d5fe279e64858f72c1db8f82722e08b::getLoader();

579
vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

359
vendor/composer/InstalledVersions.php vendored Normal file
View File

@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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.

10
vendor/composer/autoload_classmap.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

10
vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Workerman\\' => array($vendorDir . '/workerman/workerman'),
);

38
vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,38 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit7d5fe279e64858f72c1db8f82722e08b
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit7d5fe279e64858f72c1db8f82722e08b', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit7d5fe279e64858f72c1db8f82722e08b', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::getInitializer($loader));
$loader->register(true);
return $loader;
}
}

36
vendor/composer/autoload_static.php vendored Normal file
View File

@ -0,0 +1,36 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b
{
public static $prefixLengthsPsr4 = array (
'W' =>
array (
'Workerman\\' => 10,
),
);
public static $prefixDirsPsr4 = array (
'Workerman\\' =>
array (
0 => __DIR__ . '/..' . '/workerman/workerman',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit7d5fe279e64858f72c1db8f82722e08b::$classMap;
}, null, ClassLoader::class);
}
}

72
vendor/composer/installed.json vendored Normal file
View File

@ -0,0 +1,72 @@
{
"packages": [
{
"name": "workerman/workerman",
"version": "v4.1.15",
"version_normalized": "4.1.15.0",
"source": {
"type": "git",
"url": "https://github.com/walkor/workerman.git",
"reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/walkor/workerman/zipball/afc8242fc769ab7cf22eb4ac22b97cb59d465e4e",
"reference": "afc8242fc769ab7cf22eb4ac22b97cb59d465e4e",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"time": "2024-02-19T02:10:39+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Workerman\\": "./"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net",
"homepage": "http://www.workerman.net",
"role": "Developer"
}
],
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
"homepage": "http://www.workerman.net",
"keywords": [
"asynchronous",
"event-loop"
],
"support": {
"email": "walkor@workerman.net",
"forum": "http://wenda.workerman.net/",
"issues": "https://github.com/walkor/workerman/issues",
"source": "https://github.com/walkor/workerman",
"wiki": "http://doc.workerman.net/"
},
"funding": [
{
"url": "https://opencollective.com/workerman",
"type": "open_collective"
},
{
"url": "https://www.patreon.com/walkor",
"type": "patreon"
}
],
"install-path": "../workerman/workerman"
}
],
"dev": true,
"dev-package-names": []
}

32
vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,32 @@
<?php return array(
'root' => array(
'name' => 'workerman/phptty',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '663e8e8fcf7891b61411bcbeb9515dde8cea924f',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'workerman/phptty' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '663e8e8fcf7891b61411bcbeb9515dde8cea924f',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'workerman/workerman' => array(
'pretty_version' => 'v4.1.15',
'version' => '4.1.15.0',
'reference' => 'afc8242fc769ab7cf22eb4ac22b97cb59d465e4e',
'type' => 'library',
'install_path' => __DIR__ . '/../workerman/workerman',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70000)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@ -0,0 +1 @@
6911

100
vendor/workerman/workerman.log vendored Normal file
View File

@ -0,0 +1,100 @@
2024-06-16 06:02:02 pid:6880 Workerman[start.php] start in DEBUG mode
2024-06-16 06:02:03 pid:6880 Workerman[start.php] stopping ...
2024-06-16 06:02:03 pid:6880 Workerman[start.php] has been stopped
2024-06-16 06:02:06 pid:6909 Workerman[start.php] start in DAEMON mode
2024-07-26 02:40:35 pid:6913 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(560): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#8 {main}
2024-07-26 02:40:35 pid:6911 worker[none:6913] exit with status 64000
2024-08-22 04:41:53 pid:30460 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux()
#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers()
#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#10 {main}
2024-08-22 04:41:53 pid:6911 worker[none:30460] exit with status 64000
2024-10-16 09:13:08 pid:7425 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux()
#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers()
#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#10 {main}
2024-10-16 09:13:08 pid:6911 worker[none:7425] exit with status 64000
2024-11-01 23:30:00 pid:11414 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux()
#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers()
#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#10 {main}
2024-11-01 23:30:00 pid:6911 worker[none:11414] exit with status 64000
2024-11-03 03:46:28 pid:8250 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux()
#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers()
#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#10 {main}
2024-11-03 03:46:28 pid:6911 worker[none:8250] exit with status 64000
2024-11-03 13:04:45 pid:12375 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux()
#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers()
#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#10 {main}
2024-11-03 13:04:45 pid:6911 worker[none:12375] exit with status 64000
2024-11-25 02:25:35 pid:22517 ArgumentCountError: Too few arguments to function udpWorkerOnMessage(), 2 passed in /www/php-socks5/vendor/workerman/workerman/Worker.php on line 2630 and exactly 3 expected in /www/php-socks5/start.php:435
Stack trace:
#0 /www/php-socks5/vendor/workerman/workerman/Worker.php(2630): udpWorkerOnMessage()
#1 [internal function]: Workerman\Worker->acceptUdpConnection()
#2 /www/php-socks5/vendor/workerman/workerman/Events/Event.php(193): EventBase->loop()
#3 /www/php-socks5/vendor/workerman/workerman/Worker.php(1638): Workerman\Events\Event->loop()
#4 /www/php-socks5/vendor/workerman/workerman/Worker.php(1429): Workerman\Worker::forkOneWorkerForLinux()
#5 /www/php-socks5/vendor/workerman/workerman/Worker.php(1403): Workerman\Worker::forkWorkersForLinux()
#6 /www/php-socks5/vendor/workerman/workerman/Worker.php(1788): Workerman\Worker::forkWorkers()
#7 /www/php-socks5/vendor/workerman/workerman/Worker.php(1724): Workerman\Worker::monitorWorkersForLinux()
#8 /www/php-socks5/vendor/workerman/workerman/Worker.php(562): Workerman\Worker::monitorWorkers()
#9 /www/php-socks5/start.php(566): Workerman\Worker::runAll()
#10 {main}
2024-11-25 02:25:35 pid:6911 worker[none:22517] exit with status 64000

View File

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

6
vendor/workerman/workerman/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
logs
.buildpath
.project
.settings
.idea
.DS_Store

View 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');

View File

@ -0,0 +1,378 @@
<?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);
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);
}
// 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;
}
}
}
}

View 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);
}
}
}
}

View 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);
}

View File

@ -0,0 +1,982 @@
<?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;
/**
* TcpConnection.
*/
class TcpConnection extends ConnectionInterface
{
/**
* Read buffer size.
*
* @var int
*/
const READ_BUFFER_SIZE = 65535;
/**
* Status initial.
*
* @var int
*/
const STATUS_INITIAL = 0;
/**
* Status connecting.
*
* @var int
*/
const STATUS_CONNECTING = 1;
/**
* Status connection established.
*
* @var int
*/
const STATUS_ESTABLISHED = 2;
/**
* Status closing.
*
* @var int
*/
const STATUS_CLOSING = 4;
/**
* Status closed.
*
* @var int
*/
const STATUS_CLOSED = 8;
/**
* 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;
/**
* Emitted when the send buffer becomes full.
*
* @var callable
*/
public $onBufferFull = null;
/**
* Emitted when the send buffer becomes empty.
*
* @var callable
*/
public $onBufferDrain = null;
/**
* Application layer protocol.
* The format is like this Workerman\\Protocols\\Http.
*
* @var \Workerman\Protocols\ProtocolInterface
*/
public $protocol = null;
/**
* Transport (tcp/udp/unix/ssl).
*
* @var string
*/
public $transport = 'tcp';
/**
* Which worker belong to.
*
* @var Worker
*/
public $worker = null;
/**
* Bytes read.
*
* @var int
*/
public $bytesRead = 0;
/**
* Bytes written.
*
* @var int
*/
public $bytesWritten = 0;
/**
* Connection->id.
*
* @var int
*/
public $id = 0;
/**
* A copy of $worker->id which used to clean up the connection in worker->connections
*
* @var int
*/
protected $_id = 0;
/**
* Sets the maximum send buffer size for the current connection.
* OnBufferFull callback will be emited When the send buffer is full.
*
* @var int
*/
public $maxSendBufferSize = 1048576;
/**
* Context.
*
* @var object|null
*/
public $context = null;
/**
* Default send buffer size.
*
* @var int
*/
public static $defaultMaxSendBufferSize = 1048576;
/**
* Sets the maximum acceptable packet size for the current connection.
*
* @var int
*/
public $maxPackageSize = 1048576;
/**
* Default maximum acceptable packet size.
*
* @var int
*/
public static $defaultMaxPackageSize = 10485760;
/**
* Id recorder.
*
* @var int
*/
protected static $_idRecorder = 1;
/**
* Socket
*
* @var resource
*/
protected $_socket = null;
/**
* Send buffer.
*
* @var string
*/
protected $_sendBuffer = '';
/**
* Receive buffer.
*
* @var string
*/
protected $_recvBuffer = '';
/**
* Current package length.
*
* @var int
*/
protected $_currentPackageLength = 0;
/**
* Connection status.
*
* @var int
*/
protected $_status = self::STATUS_ESTABLISHED;
/**
* Remote address.
*
* @var string
*/
protected $_remoteAddress = '';
/**
* Is paused.
*
* @var bool
*/
protected $_isPaused = false;
/**
* SSL handshake completed or not.
*
* @var bool
*/
protected $_sslHandshakeCompleted = false;
/**
* All connection instances.
*
* @var array
*/
public static $connections = array();
/**
* Status to string.
*
* @var array
*/
public static $_statusToString = array(
self::STATUS_INITIAL => 'INITIAL',
self::STATUS_CONNECTING => 'CONNECTING',
self::STATUS_ESTABLISHED => 'ESTABLISHED',
self::STATUS_CLOSING => 'CLOSING',
self::STATUS_CLOSED => 'CLOSED',
);
/**
* Construct.
*
* @param resource $socket
* @param string $remote_address
*/
public function __construct($socket, $remote_address = '')
{
++self::$statistics['connection_count'];
$this->id = $this->_id = self::$_idRecorder++;
if(self::$_idRecorder === \PHP_INT_MAX){
self::$_idRecorder = 0;
}
$this->_socket = $socket;
\stream_set_blocking($this->_socket, 0);
// Compatible with hhvm
if (\function_exists('stream_set_read_buffer')) {
\stream_set_read_buffer($this->_socket, 0);
}
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
$this->maxSendBufferSize = self::$defaultMaxSendBufferSize;
$this->maxPackageSize = self::$defaultMaxPackageSize;
$this->_remoteAddress = $remote_address;
static::$connections[$this->id] = $this;
$this->context = new \stdClass;
}
/**
* Get status.
*
* @param bool $raw_output
*
* @return int|string
*/
public function getStatus($raw_output = true)
{
if ($raw_output) {
return $this->_status;
}
return self::$_statusToString[$this->_status];
}
/**
* Sends data on the connection.
*
* @param mixed $send_buffer
* @param bool $raw
* @return bool|null
*/
public function send($send_buffer, $raw = false)
{
if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) {
return false;
}
// Try to call protocol::encode($send_buffer) before sending.
if (false === $raw && $this->protocol !== null) {
$parser = $this->protocol;
$send_buffer = $parser::encode($send_buffer, $this);
if ($send_buffer === '') {
return;
}
}
if ($this->_status !== self::STATUS_ESTABLISHED ||
($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true)
) {
if ($this->_sendBuffer && $this->bufferIsFull()) {
++self::$statistics['send_fail'];
return false;
}
$this->_sendBuffer .= $send_buffer;
$this->checkBufferWillFull();
return;
}
// Attempt to send data directly.
if ($this->_sendBuffer === '') {
if ($this->transport === 'ssl') {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
$this->_sendBuffer = $send_buffer;
$this->checkBufferWillFull();
return;
}
$len = 0;
try {
$len = @\fwrite($this->_socket, $send_buffer);
} catch (\Exception $e) {
Worker::log($e);
} catch (\Error $e) {
Worker::log($e);
}
// send successful.
if ($len === \strlen($send_buffer)) {
$this->bytesWritten += $len;
return true;
}
// Send only part of the data.
if ($len > 0) {
$this->_sendBuffer = \substr($send_buffer, $len);
$this->bytesWritten += $len;
} else {
// Connection closed?
if (!\is_resource($this->_socket) || \feof($this->_socket)) {
++self::$statistics['send_fail'];
if ($this->onError) {
try {
\call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'client closed');
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
$this->destroy();
return false;
}
$this->_sendBuffer = $send_buffer;
}
Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
// Check if the send buffer will be full.
$this->checkBufferWillFull();
return;
}
if ($this->bufferIsFull()) {
++self::$statistics['send_fail'];
return false;
}
$this->_sendBuffer .= $send_buffer;
// Check if the send buffer is full.
$this->checkBufferWillFull();
}
/**
* Get remote IP.
*
* @return string
*/
public function getRemoteIp()
{
$pos = \strrpos($this->_remoteAddress, ':');
if ($pos) {
return (string) \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()
{
if (!\is_resource($this->_socket)) {
return '';
}
return (string)@\stream_socket_get_name($this->_socket, false);
}
/**
* Get send buffer queue size.
*
* @return integer
*/
public function getSendBufferQueueSize()
{
return \strlen($this->_sendBuffer);
}
/**
* Get recv buffer queue size.
*
* @return integer
*/
public function getRecvBufferQueueSize()
{
return \strlen($this->_recvBuffer);
}
/**
* 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;
}
/**
* Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload.
*
* @return void
*/
public function pauseRecv()
{
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
$this->_isPaused = true;
}
/**
* Resumes reading after a call to pauseRecv.
*
* @return void
*/
public function resumeRecv()
{
if ($this->_isPaused === true) {
Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead'));
$this->_isPaused = false;
$this->baseRead($this->_socket, false);
}
}
/**
* Base read handler.
*
* @param resource $socket
* @param bool $check_eof
* @return void
*/
public function baseRead($socket, $check_eof = true)
{
// SSL handshake.
if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) {
if ($this->doSslHandshake($socket)) {
$this->_sslHandshakeCompleted = true;
if ($this->_sendBuffer) {
Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite'));
}
} else {
return;
}
}
$buffer = '';
try {
$buffer = @\fread($socket, self::READ_BUFFER_SIZE);
} catch (\Exception $e) {} catch (\Error $e) {}
// Check connection closed.
if ($buffer === '' || $buffer === false) {
if ($check_eof && (\feof($socket) || !\is_resource($socket) || $buffer === false)) {
$this->destroy();
return;
}
} else {
$this->bytesRead += \strlen($buffer);
$this->_recvBuffer .= $buffer;
}
// If the application layer protocol has been set up.
if ($this->protocol !== null) {
$parser = $this->protocol;
while ($this->_recvBuffer !== '' && !$this->_isPaused) {
// The current packet length is known.
if ($this->_currentPackageLength) {
// Data is not enough for a package.
if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) {
break;
}
} else {
// Get current package length.
try {
$this->_currentPackageLength = $parser::input($this->_recvBuffer, $this);
} catch (\Exception $e) {} catch (\Error $e) {}
// The packet length is unknown.
if ($this->_currentPackageLength === 0) {
break;
} elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= $this->maxPackageSize) {
// Data is not enough for a package.
if ($this->_currentPackageLength > \strlen($this->_recvBuffer)) {
break;
}
} // Wrong package.
else {
Worker::safeEcho('Error package. package_length=' . \var_export($this->_currentPackageLength, true));
$this->destroy();
return;
}
}
// The data is enough for a packet.
++self::$statistics['total_request'];
// The current packet length is equal to the length of the buffer.
if (\strlen($this->_recvBuffer) === $this->_currentPackageLength) {
$one_request_buffer = $this->_recvBuffer;
$this->_recvBuffer = '';
} else {
// Get a full package from the buffer.
$one_request_buffer = \substr($this->_recvBuffer, 0, $this->_currentPackageLength);
// Remove the current package from the receive buffer.
$this->_recvBuffer = \substr($this->_recvBuffer, $this->_currentPackageLength);
}
// Reset the current packet length to 0.
$this->_currentPackageLength = 0;
if (!$this->onMessage) {
continue;
}
try {
// Decode request buffer before Emitting onMessage callback.
\call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this));
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
return;
}
if ($this->_recvBuffer === '' || $this->_isPaused) {
return;
}
// Applications protocol is not set.
++self::$statistics['total_request'];
if (!$this->onMessage) {
$this->_recvBuffer = '';
return;
}
try {
\call_user_func($this->onMessage, $this, $this->_recvBuffer);
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
// Clean receive buffer.
$this->_recvBuffer = '';
}
/**
* Base write handler.
*
* @return void|bool
*/
public function baseWrite()
{
\set_error_handler(function(){});
if ($this->transport === 'ssl') {
$len = @\fwrite($this->_socket, $this->_sendBuffer, 8192);
} else {
$len = @\fwrite($this->_socket, $this->_sendBuffer);
}
\restore_error_handler();
if ($len === \strlen($this->_sendBuffer)) {
$this->bytesWritten += $len;
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
$this->_sendBuffer = '';
// Try to emit onBufferDrain callback when the send buffer becomes empty.
if ($this->onBufferDrain) {
try {
\call_user_func($this->onBufferDrain, $this);
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
if ($this->_status === self::STATUS_CLOSING) {
$this->destroy();
}
return true;
}
if ($len > 0) {
$this->bytesWritten += $len;
$this->_sendBuffer = \substr($this->_sendBuffer, $len);
} else {
++self::$statistics['send_fail'];
$this->destroy();
}
}
/**
* SSL handshake.
*
* @param resource $socket
* @return bool
*/
public function doSslHandshake($socket){
if (\feof($socket)) {
$this->destroy();
return false;
}
$async = $this instanceof AsyncTcpConnection;
/**
* We disabled ssl3 because https://blog.qualys.com/ssllabs/2014/10/15/ssl-3-is-dead-killed-by-the-poodle-attack.
* You can enable ssl3 by the codes below.
*/
/*if($async){
$type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT | STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
}else{
$type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER;
}*/
if($async){
$type = \STREAM_CRYPTO_METHOD_SSLv2_CLIENT | \STREAM_CRYPTO_METHOD_SSLv23_CLIENT;
}else{
$type = \STREAM_CRYPTO_METHOD_SSLv2_SERVER | \STREAM_CRYPTO_METHOD_SSLv23_SERVER;
}
// Hidden error.
\set_error_handler(function($errno, $errstr, $file){
if (!Worker::$daemonize) {
Worker::safeEcho("SSL handshake error: $errstr \n");
}
});
$ret = \stream_socket_enable_crypto($socket, true, $type);
\restore_error_handler();
// Negotiation has failed.
if (false === $ret) {
$this->destroy();
return false;
} elseif (0 === $ret) {
// There isn't enough data and should try again.
return 0;
}
if (isset($this->onSslHandshake)) {
try {
\call_user_func($this->onSslHandshake, $this);
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
return true;
}
/**
* This method pulls all the data out of a readable stream, and writes it to the supplied destination.
*
* @param self $dest
* @return void
*/
public function pipe(self $dest)
{
$source = $this;
$this->onMessage = function ($source, $data) use ($dest) {
$dest->send($data);
};
$this->onClose = function ($source) use ($dest) {
$dest->close();
};
$dest->onBufferFull = function ($dest) use ($source) {
$source->pauseRecv();
};
$dest->onBufferDrain = function ($dest) use ($source) {
$source->resumeRecv();
};
}
/**
* Remove $length of data from receive buffer.
*
* @param int $length
* @return void
*/
public function consumeRecvBuffer($length)
{
$this->_recvBuffer = \substr($this->_recvBuffer, $length);
}
/**
* Close connection.
*
* @param mixed $data
* @param bool $raw
* @return void
*/
public function close($data = null, $raw = false)
{
if($this->_status === self::STATUS_CONNECTING){
$this->destroy();
return;
}
if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) {
return;
}
if ($data !== null) {
$this->send($data, $raw);
}
$this->_status = self::STATUS_CLOSING;
if ($this->_sendBuffer === '') {
$this->destroy();
} else {
$this->pauseRecv();
}
}
/**
* Get the real socket.
*
* @return resource
*/
public function getSocket()
{
return $this->_socket;
}
/**
* Check whether the send buffer will be full.
*
* @return void
*/
protected function checkBufferWillFull()
{
if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) {
if ($this->onBufferFull) {
try {
\call_user_func($this->onBufferFull, $this);
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
}
}
/**
* Whether send buffer is full.
*
* @return bool
*/
protected function bufferIsFull()
{
// Buffer has been marked as full but still has data to send then the packet is discarded.
if ($this->maxSendBufferSize <= \strlen($this->_sendBuffer)) {
if ($this->onError) {
try {
\call_user_func($this->onError, $this, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
return true;
}
return false;
}
/**
* Whether send buffer is Empty.
*
* @return bool
*/
public function bufferIsEmpty()
{
return empty($this->_sendBuffer);
}
/**
* Destroy connection.
*
* @return void
*/
public function destroy()
{
// Avoid repeated calls.
if ($this->_status === self::STATUS_CLOSED) {
return;
}
// Remove event listener.
Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ);
Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE);
// Close socket.
try {
@\fclose($this->_socket);
} catch (\Exception $e) {} catch (\Error $e) {}
$this->_status = self::STATUS_CLOSED;
// 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);
}
}
// Try to emit protocol::onClose
if ($this->protocol && \method_exists($this->protocol, 'onClose')) {
try {
\call_user_func(array($this->protocol, 'onClose'), $this);
} catch (\Exception $e) {
Worker::stopAll(250, $e);
} catch (\Error $e) {
Worker::stopAll(250, $e);
}
}
$this->_sendBuffer = $this->_recvBuffer = '';
$this->_currentPackageLength = 0;
$this->_isPaused = $this->_sslHandshakeCompleted = false;
if ($this->_status === self::STATUS_CLOSED) {
// Cleaning up the callback to avoid memory leaks.
$this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null;
// Remove from worker->connections.
if ($this->worker) {
unset($this->worker->connections[$this->_id]);
}
unset(static::$connections[$this->_id]);
}
}
/**
* Destruct.
*
* @return void
*/
public function __destruct()
{
static $mod;
self::$statistics['connection_count']--;
if (Worker::getGracefulStop()) {
if (!isset($mod)) {
$mod = \ceil((self::$statistics['connection_count'] + 1) / 3);
}
if (0 === self::$statistics['connection_count'] % $mod) {
Worker::log('worker[' . \posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)');
}
if(0 === self::$statistics['connection_count']) {
Worker::stopAll();
}
}
}
}

View 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
View 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);
}
}

View 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);
}
}

View 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();
}

View 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);
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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();
}
}

View 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);
}
}

View File

@ -0,0 +1,230 @@
<?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;
class Swoole implements EventInterface
{
protected $_timer = array();
protected $_timerOnceMap = array();
protected $mapId = 0;
protected $_fd = array();
// milisecond
public static $signalDispatchInterval = 500;
protected $_hasSignal = false;
/**
*
* {@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 (! isset($this->_fd[$fd_key])) {
if ($flag === self::EV_READ) {
$res = Event::add($fd, $func, 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;
}
}
/**
*
* {@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 (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()
{
Event::exit();
posix_kill(posix_getpid(), SIGINT);
}
/**
*
* {@inheritdoc}
*
* @see \Workerman\Events\EventInterface::getTimerCount()
*/
public function getTimerCount()
{
return \count($this->_timer);
}
}

260
vendor/workerman/workerman/Events/Uv.php vendored Normal file
View 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);
}
}

View 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);
}
}

View 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 {}

View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2009-2015 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/workerman/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,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;
}
}

View 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;
}
}

View 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";
}
}

View 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($name = null)
{
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);
}
}
});
}
}
}

View 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();

View 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";
}
}

View 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();

View 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();

View 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);
}
}

View 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;
}
}

View 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 = "");
}

View 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;
}

View 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);
}

View 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");
}
}

View File

@ -0,0 +1,564 @@
<?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' => 9,
'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' => 9,
'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.1 200 WebSocket\r\nServer: workerman/" . Worker::VERSION . "\r\n\r\n<div style=\"text-align:center\"><h1>WebSocket</h1><hr>workerman/" . Worker::VERSION . "</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.1 200 WebSocket\r\nServer: workerman/" . Worker::VERSION . "\r\n\r\n<div style=\"text-align:center\"><h1>WebSocket</h1><hr>workerman/" . Worker::VERSION . "</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'] = '';
}
}
}

View 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 ? $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
View File

@ -0,0 +1,342 @@
# Workerman
[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman)
[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman)
[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman)
[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman)
[![License](https://poser.pugx.org/workerman/workerman/license)](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 ```
![workerman start](http://www.workerman.net/img/workerman-start.png)
```php start.php status ```
![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123)
```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
![image](https://user-images.githubusercontent.com/6073368/146704320-1559fe97-aa67-4ee3-95d6-61e341b3c93b.png)
## 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
View 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();
}
}
}

2672
vendor/workerman/workerman/Worker.php vendored Normal file

File diff suppressed because it is too large Load Diff

View 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": ">=7.0"
},
"suggest": {
"ext-event": "For better performance. "
},
"autoload": {
"psr-4": {
"Workerman\\": "./"
}
},
"minimum-stability": "dev"
}