first commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
composer.lock
|
||||
vendor
|
||||
vendor/
|
||||
.idea
|
||||
.idea/
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# webman-framework
|
||||
Note: This repository is the core code of the webman framework. If you want to build an application using webman, visit the main [webman](https://github.com/walkor/webman) repository.
|
||||
|
||||
## LICENSE
|
||||
MIT
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "workerman/webman-framework",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"high performance",
|
||||
"http service"
|
||||
],
|
||||
"homepage": "https://www.workerman.net",
|
||||
"license": "MIT",
|
||||
"description": "High performance HTTP Service Framework.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "walkor",
|
||||
"email": "walkor@workerman.net",
|
||||
"homepage": "https://www.workerman.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "walkor@workerman.net",
|
||||
"issues": "https://github.com/walkor/webman/issues",
|
||||
"forum": "https://wenda.workerman.net/",
|
||||
"wiki": "https://doc.workerman.net/",
|
||||
"source": "https://github.com/walkor/webman-framework"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"workerman/workerman": "^4.0.4",
|
||||
"nikic/fast-route": "^1.3",
|
||||
"psr/container": ">=1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webman\\": "./src",
|
||||
"support\\": "./src/support",
|
||||
"Support\\": "./src/support",
|
||||
"Support\\Bootstrap\\": "./src/support/bootstrap",
|
||||
"Support\\Exception\\": "./src/support/exception",
|
||||
"Support\\View\\": "./src/support/view"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
+863
@@ -0,0 +1,863 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
use Closure;
|
||||
use FastRoute\Dispatcher;
|
||||
use Monolog\Logger;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionFunction;
|
||||
use ReflectionFunctionAbstract;
|
||||
use ReflectionMethod;
|
||||
use Throwable;
|
||||
use Webman\Exception\ExceptionHandler;
|
||||
use Webman\Exception\ExceptionHandlerInterface;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Route\Route as RouteObject;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\Http;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class App
|
||||
* @package Webman
|
||||
*/
|
||||
class App
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_callbacks = [];
|
||||
|
||||
/**
|
||||
* @var Worker
|
||||
*/
|
||||
protected static $_worker = null;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected static $_container = null;
|
||||
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected static $_logger = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $_appPath = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $_publicPath = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $_configPath = '';
|
||||
|
||||
/**
|
||||
* @var TcpConnection
|
||||
*/
|
||||
protected static $_connection = null;
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected static $_request = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $_requestClass = '';
|
||||
|
||||
/**
|
||||
* App constructor.
|
||||
*
|
||||
* @param string $request_class
|
||||
* @param Logger $logger
|
||||
* @param string $app_path
|
||||
* @param string $public_path
|
||||
*/
|
||||
public function __construct(string $request_class, Logger $logger, string $app_path, string $public_path)
|
||||
{
|
||||
static::$_requestClass = $request_class;
|
||||
static::$_logger = $logger;
|
||||
static::$_publicPath = $public_path;
|
||||
static::$_appPath = $app_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
* @param Request $request
|
||||
* @return null
|
||||
*/
|
||||
public function onMessage($connection, $request)
|
||||
{
|
||||
try {
|
||||
static::$_request = $request;
|
||||
static::$_connection = $connection;
|
||||
$path = $request->path();
|
||||
$key = $request->method() . $path;
|
||||
if (isset(static::$_callbacks[$key])) {
|
||||
[$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$_callbacks[$key];
|
||||
static::send($connection, $callback($request), $request);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
static::unsafeUri($connection, $path, $request) ||
|
||||
static::findFile($connection, $path, $key, $request) ||
|
||||
static::findRoute($connection, $path, $key, $request)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$controller_and_action = static::parseControllerAction($path);
|
||||
$plugin = $controller_and_action['plugin'] ?? static::getPluginByPath($path);
|
||||
if (!$controller_and_action || Route::hasDisableDefaultRoute($plugin)) {
|
||||
$callback = static::getFallback($plugin);
|
||||
$request->app = $request->controller = $request->action = '';
|
||||
static::send($connection, $callback($request), $request);
|
||||
return null;
|
||||
}
|
||||
$app = $controller_and_action['app'];
|
||||
$controller = $controller_and_action['controller'];
|
||||
$action = $controller_and_action['action'];
|
||||
$callback = static::getCallback($plugin, $app, [$controller, $action]);
|
||||
static::collectCallbacks($key, [$callback, $plugin, $app, $controller, $action, null]);
|
||||
[$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$_callbacks[$key];
|
||||
static::send($connection, $callback($request), $request);
|
||||
} catch (Throwable $e) {
|
||||
static::send($connection, static::exceptionResponse($e, $request), $request);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $worker
|
||||
* @return void
|
||||
*/
|
||||
public function onWorkerStart($worker)
|
||||
{
|
||||
static::$_worker = $worker;
|
||||
Http::requestClass(static::$_requestClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected static function collectCallbacks(string $key, array $data)
|
||||
{
|
||||
static::$_callbacks[$key] = $data;
|
||||
if (\count(static::$_callbacks) >= 1024) {
|
||||
unset(static::$_callbacks[\key(static::$_callbacks)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
* @param string $path
|
||||
* @param $request
|
||||
* @return bool
|
||||
*/
|
||||
protected static function unsafeUri(TcpConnection $connection, string $path, $request): bool
|
||||
{
|
||||
if (
|
||||
!$path ||
|
||||
\strpos($path, '..') !== false ||
|
||||
\strpos($path, "\\") !== false ||
|
||||
\strpos($path, "\0") !== false
|
||||
) {
|
||||
$callback = static::getFallback();
|
||||
$request->plugin = $request->app = $request->controller = $request->action = '';
|
||||
static::send($connection, $callback($request), $request);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plugin
|
||||
* @return Closure
|
||||
*/
|
||||
protected static function getFallback(string $plugin = ''): Closure
|
||||
{
|
||||
// when route, controller and action not found, try to use Route::fallback
|
||||
return Route::getFallback($plugin) ?: function () {
|
||||
try {
|
||||
$notFoundContent = \file_get_contents(static::$_publicPath . '/404.html');
|
||||
} catch (Throwable $e) {
|
||||
$notFoundContent = '404 Not Found';
|
||||
}
|
||||
return new Response(404, [], $notFoundContent);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $e
|
||||
* @param $request
|
||||
* @return Response
|
||||
*/
|
||||
protected static function exceptionResponse(Throwable $e, $request)
|
||||
{
|
||||
try {
|
||||
$app = $request->app ?: '';
|
||||
$plugin = $request->plugin ?: '';
|
||||
$exception_config = static::config($plugin, 'exception');
|
||||
$default_exception = $exception_config[''] ?? ExceptionHandler::class;
|
||||
$exception_handler_class = $exception_config[$app] ?? $default_exception;
|
||||
|
||||
/** @var ExceptionHandlerInterface $exception_handler */
|
||||
$exception_handler = static::container($plugin)->make($exception_handler_class, [
|
||||
'logger' => static::$_logger,
|
||||
'debug' => static::config($plugin, 'app.debug')
|
||||
]);
|
||||
$exception_handler->report($e);
|
||||
$response = $exception_handler->render($request, $e);
|
||||
$response->exception($e);
|
||||
return $response;
|
||||
} catch (Throwable $e) {
|
||||
$response = new Response(500, [], static::config($plugin ?? '', 'app.debug') ? (string)$e : $e->getMessage());
|
||||
$response->exception($e);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $app
|
||||
* @param $call
|
||||
* @param array|null $args
|
||||
* @param bool $with_global_middleware
|
||||
* @param RouteObject $route
|
||||
* @return callable
|
||||
*/
|
||||
protected static function getCallback(string $plugin, string $app, $call, array $args = null, bool $with_global_middleware = true, RouteObject $route = null)
|
||||
{
|
||||
$args = $args === null ? null : \array_values($args);
|
||||
$middlewares = [];
|
||||
if ($route) {
|
||||
$route_middlewares = \array_reverse($route->getMiddleware());
|
||||
foreach ($route_middlewares as $class_name) {
|
||||
$middlewares[] = [$class_name, 'process'];
|
||||
}
|
||||
}
|
||||
$middlewares = \array_merge($middlewares, Middleware::getMiddleware($plugin, $app, $with_global_middleware));
|
||||
|
||||
foreach ($middlewares as $key => $item) {
|
||||
$middleware = $item[0];
|
||||
if (is_string($middleware)) {
|
||||
$middleware = static::container($plugin)->get($middleware);
|
||||
} elseif ($middleware instanceof \Closure) {
|
||||
$middleware = call_user_func($middleware, static::container($plugin));
|
||||
}
|
||||
if (!$middleware instanceof MiddlewareInterface) {
|
||||
throw new \InvalidArgumentException('Not support middleware type');
|
||||
}
|
||||
$middlewares[$key][0] = $middleware;
|
||||
}
|
||||
|
||||
$need_inject = static::isNeedInject($call, $args);
|
||||
if (\is_array($call) && \is_string($call[0])) {
|
||||
$controller_reuse = static::config($plugin, 'app.controller_reuse', true);
|
||||
if (!$controller_reuse) {
|
||||
if ($need_inject) {
|
||||
$call = function ($request, ...$args) use ($call, $plugin) {
|
||||
$call[0] = static::container($plugin)->make($call[0]);
|
||||
$reflector = static::getReflector($call);
|
||||
$args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
|
||||
return $call(...$args);
|
||||
};
|
||||
$need_inject = false;
|
||||
} else {
|
||||
$call = function ($request, ...$args) use ($call, $plugin) {
|
||||
$call[0] = static::container($plugin)->make($call[0]);
|
||||
return $call($request, ...$args);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
$call[0] = static::container($plugin)->get($call[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($need_inject) {
|
||||
$call = static::resolveInject($plugin, $call);
|
||||
}
|
||||
|
||||
if ($middlewares) {
|
||||
$callback = \array_reduce($middlewares, function ($carry, $pipe) {
|
||||
return function ($request) use ($carry, $pipe) {
|
||||
try {
|
||||
return $pipe($request, $carry);
|
||||
} catch (Throwable $e) {
|
||||
return static::exceptionResponse($e, $request);
|
||||
}
|
||||
};
|
||||
}, function ($request) use ($call, $args) {
|
||||
try {
|
||||
if ($args === null) {
|
||||
$response = $call($request);
|
||||
} else {
|
||||
$response = $call($request, ...$args);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
return static::exceptionResponse($e, $request);
|
||||
}
|
||||
if (!$response instanceof Response) {
|
||||
if (\is_array($response)) {
|
||||
$response = 'Array';
|
||||
}
|
||||
$response = new Response(200, [], $response);
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
} else {
|
||||
if ($args === null) {
|
||||
$callback = $call;
|
||||
} else {
|
||||
$callback = function ($request) use ($call, $args) {
|
||||
return $call($request, ...$args);
|
||||
};
|
||||
}
|
||||
}
|
||||
return $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plugin
|
||||
* @param array|Closure $call
|
||||
* @param null|array $args
|
||||
* @return Closure
|
||||
* @see Dependency injection through reflection information
|
||||
*/
|
||||
protected static function resolveInject(string $plugin, $call)
|
||||
{
|
||||
return function (Request $request, ...$args) use ($plugin, $call) {
|
||||
$reflector = static::getReflector($call);
|
||||
$args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
|
||||
return $call(...$args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether inject is required
|
||||
*
|
||||
* @param $call
|
||||
* @param $args
|
||||
* @return bool
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected static function isNeedInject($call, $args)
|
||||
{
|
||||
if (\is_array($call) && !\method_exists($call[0], $call[1])) {
|
||||
return false;
|
||||
}
|
||||
$args = $args ?: [];
|
||||
$reflector = static::getReflector($call);
|
||||
$reflection_parameters = $reflector->getParameters();
|
||||
if (!$reflection_parameters) {
|
||||
return false;
|
||||
}
|
||||
$first_parameter = \current($reflection_parameters);
|
||||
unset($reflection_parameters[\key($reflection_parameters)]);
|
||||
$adapters_list = ['int', 'string', 'bool', 'array', 'object', 'float', 'mixed', 'resource'];
|
||||
foreach ($reflection_parameters as $parameter) {
|
||||
if ($parameter->hasType() && !\in_array($parameter->getType()->getName(), $adapters_list)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!$first_parameter->hasType()) {
|
||||
if (\count($args) <= count($reflection_parameters)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} elseif (!\is_a(static::$_request, $first_parameter->getType()->getName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reflector.
|
||||
*
|
||||
* @param $call
|
||||
* @return ReflectionFunction|ReflectionMethod
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected static function getReflector($call)
|
||||
{
|
||||
if ($call instanceof Closure || \is_string($call)) {
|
||||
return new ReflectionFunction($call);
|
||||
}
|
||||
return new ReflectionMethod($call[0], $call[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return dependent parameters
|
||||
*
|
||||
* @param string $plugin
|
||||
* @param Request $request
|
||||
* @param array $args
|
||||
* @param ReflectionFunctionAbstract $reflector
|
||||
* @return array
|
||||
*/
|
||||
protected static function resolveMethodDependencies(string $plugin, Request $request, array $args, ReflectionFunctionAbstract $reflector)
|
||||
{
|
||||
// Specification parameter information
|
||||
$args = \array_values($args);
|
||||
$parameters = [];
|
||||
// An array of reflection classes for loop parameters, with each $parameter representing a reflection object of parameters
|
||||
foreach ($reflector->getParameters() as $parameter) {
|
||||
// Parameter quota consumption
|
||||
if ($parameter->hasType()) {
|
||||
$name = $parameter->getType()->getName();
|
||||
switch ($name) {
|
||||
case 'int':
|
||||
case 'string':
|
||||
case 'bool':
|
||||
case 'array':
|
||||
case 'object':
|
||||
case 'float':
|
||||
case 'mixed':
|
||||
case 'resource':
|
||||
goto _else;
|
||||
default:
|
||||
if (\is_a(static::$_request, $name)) {
|
||||
//Inject Request
|
||||
$parameters[] = $request;
|
||||
} else {
|
||||
$parameters[] = static::container($plugin)->make($name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
_else:
|
||||
// The variable parameter
|
||||
if (null !== \key($args)) {
|
||||
$parameters[] = \current($args);
|
||||
} else {
|
||||
// Indicates whether the current parameter has a default value. If yes, return true
|
||||
$parameters[] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
|
||||
}
|
||||
// Quota of consumption variables
|
||||
\next($args);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the result of parameters replacement
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plugin
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public static function container(string $plugin = '')
|
||||
{
|
||||
return static::config($plugin, 'container');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Request|\support\Request
|
||||
*/
|
||||
public static function request()
|
||||
{
|
||||
return static::$_request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TcpConnection
|
||||
*/
|
||||
public static function connection()
|
||||
{
|
||||
return static::$_connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Worker
|
||||
*/
|
||||
public static function worker()
|
||||
{
|
||||
return static::$_worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
* @param string $path
|
||||
* @param string $key
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected static function findRoute(TcpConnection $connection, string $path, string $key, $request)
|
||||
{
|
||||
$ret = Route::dispatch($request->method(), $path);
|
||||
if ($ret[0] === Dispatcher::FOUND) {
|
||||
$ret[0] = 'route';
|
||||
$callback = $ret[1]['callback'];
|
||||
$route = clone $ret[1]['route'];
|
||||
$app = $controller = $action = '';
|
||||
$args = !empty($ret[2]) ? $ret[2] : null;
|
||||
if ($args) {
|
||||
$route->setParams($args);
|
||||
}
|
||||
if (\is_array($callback)) {
|
||||
$controller = $callback[0];
|
||||
$plugin = static::getPluginByClass($controller);
|
||||
$app = static::getAppByController($controller);
|
||||
$action = static::getRealMethod($controller, $callback[1]) ?? '';
|
||||
} else {
|
||||
$plugin = static::getPluginByPath($path);
|
||||
}
|
||||
$callback = static::getCallback($plugin, $app, $callback, $args, true, $route);
|
||||
static::collectCallbacks($key, [$callback, $plugin, $app, $controller ?: '', $action, $route]);
|
||||
[$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$_callbacks[$key];
|
||||
static::send($connection, $callback($request), $request);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
* @param string $path
|
||||
* @param string $key
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected static function findFile(TcpConnection $connection, string $path, string $key, $request): bool
|
||||
{
|
||||
if (preg_match('/%[0-9a-f]{2}/i', $path)) {
|
||||
$path = urldecode($path);
|
||||
if (static::unsafeUri($connection, $path, $request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$path_explodes = \explode('/', trim($path, '/'));
|
||||
$plugin = '';
|
||||
if (isset($path_explodes[1]) && $path_explodes[0] === 'app') {
|
||||
$public_dir = BASE_PATH . "/plugin/{$path_explodes[1]}/public";
|
||||
$plugin = $path_explodes[1];
|
||||
$path = \substr($path, strlen("/app/{$path_explodes[1]}/"));
|
||||
} else {
|
||||
$public_dir = static::$_publicPath;
|
||||
}
|
||||
$file = "$public_dir/$path";
|
||||
if (!\is_file($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\pathinfo($file, PATHINFO_EXTENSION) === 'php') {
|
||||
if (!static::config($plugin, 'app.support_php_files', false)) {
|
||||
return false;
|
||||
}
|
||||
static::collectCallbacks($key, [function () use ($file) {
|
||||
return static::execPhpFile($file);
|
||||
}, '', '', '', '', null]);
|
||||
[, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$_callbacks[$key];
|
||||
static::send($connection, static::execPhpFile($file), $request);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!static::config($plugin, 'static.enable', false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static::collectCallbacks($key, [static::getCallback($plugin, '__static__', function ($request) use ($file, $plugin) {
|
||||
\clearstatcache(true, $file);
|
||||
if (!\is_file($file)) {
|
||||
$callback = static::getFallback($plugin);
|
||||
return $callback($request);
|
||||
}
|
||||
return (new Response())->file($file);
|
||||
}, null, false), '', '', '', '', null]);
|
||||
[$callback, $request->plugin, $request->app, $request->controller, $request->action, $request->route] = static::$_callbacks[$key];
|
||||
static::send($connection, $callback($request), $request);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TcpConnection $connection
|
||||
* @param Response $response
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
protected static function send(TcpConnection $connection, $response, $request)
|
||||
{
|
||||
$keep_alive = $request->header('connection');
|
||||
static::$_request = static::$_connection = null;
|
||||
if (($keep_alive === null && $request->protocolVersion() === '1.1')
|
||||
|| $keep_alive === 'keep-alive' || $keep_alive === 'Keep-Alive'
|
||||
) {
|
||||
$connection->send($response);
|
||||
return;
|
||||
}
|
||||
$connection->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return array|false
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected static function parseControllerAction(string $path)
|
||||
{
|
||||
$path = \str_replace('-', '', $path);
|
||||
$path_explode = \explode('/', trim($path, '/'));
|
||||
$is_plugin = isset($path_explode[1]) && $path_explode[0] === 'app';
|
||||
$config_prefix = $is_plugin ? "plugin.{$path_explode[1]}." : '';
|
||||
$path_prefix = $is_plugin ? "/app/{$path_explode[1]}" : '';
|
||||
$class_prefix = $is_plugin ? "plugin\\{$path_explode[1]}" : '';
|
||||
$suffix = Config::get("{$config_prefix}app.controller_suffix", '');
|
||||
$relative_path = \trim(substr($path, strlen($path_prefix)), '/');
|
||||
$path_explode = $relative_path ? \explode('/', $relative_path) : [];
|
||||
|
||||
$action = 'index';
|
||||
if ($controller_action = static::guessControllerAction($path_explode, $action, $suffix, $class_prefix)) {
|
||||
return $controller_action;
|
||||
}
|
||||
$action = \end($path_explode);
|
||||
unset($path_explode[count($path_explode) - 1]);
|
||||
return static::guessControllerAction($path_explode, $action, $suffix, $class_prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $path_explode
|
||||
* @param $action
|
||||
* @param $suffix
|
||||
* @param $class_prefix
|
||||
* @return array|false
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected static function guessControllerAction($path_explode, $action, $suffix, $class_prefix)
|
||||
{
|
||||
$map[] = trim("$class_prefix\\app\\controller\\" . \implode('\\', $path_explode), '\\');
|
||||
foreach ($path_explode as $index => $section) {
|
||||
$tmp = $path_explode;
|
||||
\array_splice($tmp, $index, 1, [$section, 'controller']);
|
||||
$map[] = trim("$class_prefix\\" . \implode('\\', \array_merge(['app'], $tmp)), '\\');
|
||||
}
|
||||
foreach ($map as $item) {
|
||||
$map[] = $item . '\\index';
|
||||
}
|
||||
|
||||
foreach ($map as $controller_class) {
|
||||
$controller_class .= $suffix;
|
||||
if ($controller_action = static::getControllerAction($controller_class, $action)) {
|
||||
return $controller_action;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controller_class
|
||||
* @param string $action
|
||||
* @return array|false
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected static function getControllerAction(string $controller_class, string $action)
|
||||
{
|
||||
// Disable calling magic methods
|
||||
if (\strpos($action, '__') === 0) {
|
||||
return false;
|
||||
}
|
||||
if (($controller_class = static::getController($controller_class)) && ($action = static::getAction($controller_class, $action))) {
|
||||
return [
|
||||
'plugin' => static::getPluginByClass($controller_class),
|
||||
'app' => static::getAppByController($controller_class),
|
||||
'controller' => $controller_class,
|
||||
'action' => $action
|
||||
];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controller_class
|
||||
* @return string|false
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected static function getController(string $controller_class)
|
||||
{
|
||||
if (\class_exists($controller_class)) {
|
||||
return (new \ReflectionClass($controller_class))->name;
|
||||
}
|
||||
$explodes = \explode('\\', strtolower(ltrim($controller_class, '\\')));
|
||||
$base_path = $explodes[0] === 'plugin' ? BASE_PATH . '/plugin' : static::$_appPath;
|
||||
unset($explodes[0]);
|
||||
$file_name = \array_pop($explodes) . '.php';
|
||||
$found = true;
|
||||
foreach ($explodes as $path_section) {
|
||||
if (!$found) {
|
||||
break;
|
||||
}
|
||||
$dirs = Util::scanDir($base_path, false);
|
||||
$found = false;
|
||||
foreach ($dirs as $name) {
|
||||
$path = "$base_path/$name";
|
||||
if (\is_dir($path) && \strtolower($name) === $path_section) {
|
||||
$base_path = $path;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
return false;
|
||||
}
|
||||
foreach (\scandir($base_path) ?: [] as $name) {
|
||||
if (\strtolower($name) === $file_name) {
|
||||
require_once "$base_path/$name";
|
||||
if (\class_exists($controller_class, false)) {
|
||||
return (new \ReflectionClass($controller_class))->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controller_class
|
||||
* @param string $action
|
||||
* @return string|false
|
||||
*/
|
||||
protected static function getAction(string $controller_class, string $action)
|
||||
{
|
||||
$methods = \get_class_methods($controller_class);
|
||||
$action = \strtolower($action);
|
||||
$found = false;
|
||||
foreach ($methods as $candidate) {
|
||||
if (\strtolower($candidate) === $action) {
|
||||
$action = $candidate;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
return $action;
|
||||
}
|
||||
|
||||
// Action is not public method
|
||||
if (\method_exists($controller_class, $action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\method_exists($controller_class, '__call')) {
|
||||
return $action;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controller_class
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function getPluginByClass(string $controller_class)
|
||||
{
|
||||
$controller_class = \trim($controller_class, '\\');
|
||||
$tmp = \explode('\\', $controller_class, 3);
|
||||
if ($tmp[0] !== 'plugin') {
|
||||
return '';
|
||||
}
|
||||
return $tmp[1] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function getPluginByPath(string $path)
|
||||
{
|
||||
$path = \trim($path, '/');
|
||||
$tmp = \explode('/', $path, 3);
|
||||
if ($tmp[0] !== 'app') {
|
||||
return '';
|
||||
}
|
||||
return $tmp[1] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controller_class
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected static function getAppByController(string $controller_class)
|
||||
{
|
||||
$controller_class = \trim($controller_class, '\\');
|
||||
$tmp = \explode('\\', $controller_class, 5);
|
||||
$pos = $tmp[0] === 'plugin' ? 3 : 1;
|
||||
if (!isset($tmp[$pos])) {
|
||||
return '';
|
||||
}
|
||||
return \strtolower($tmp[$pos]) === 'controller' ? '' : $tmp[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return false|string
|
||||
*/
|
||||
public static function execPhpFile(string $file)
|
||||
{
|
||||
\ob_start();
|
||||
// Try to include php file.
|
||||
try {
|
||||
include $file;
|
||||
} catch (\Exception $e) {
|
||||
echo $e;
|
||||
}
|
||||
return \ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return string
|
||||
*/
|
||||
protected static function getRealMethod(string $class, string $method)
|
||||
{
|
||||
$method = \strtolower($method);
|
||||
$methods = \get_class_methods($class);
|
||||
foreach ($methods as $candidate) {
|
||||
if (\strtolower($candidate) === $method) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
return $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plugin
|
||||
* @param string $key
|
||||
* @param $default
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
protected static function config(string $plugin, string $key, $default = null)
|
||||
{
|
||||
return Config::get($plugin ? "plugin.$plugin.$key" : $key, $default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
interface Bootstrap
|
||||
{
|
||||
/**
|
||||
* onWorkerStart
|
||||
*
|
||||
* @param Worker $worker
|
||||
* @return mixed
|
||||
*/
|
||||
public static function start($worker);
|
||||
}
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
class Config
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_config = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $_configPath = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $_loaded = false;
|
||||
|
||||
/**
|
||||
* @param string $config_path
|
||||
* @param array $exclude_file
|
||||
* @param string|null $key
|
||||
* @return void
|
||||
*/
|
||||
public static function load(string $config_path, array $exclude_file = [], string $key = null)
|
||||
{
|
||||
static::$_configPath = $config_path;
|
||||
if (!$config_path) {
|
||||
return;
|
||||
}
|
||||
static::$_loaded = false;
|
||||
$config = static::loadFromDir($config_path, $exclude_file);
|
||||
if (!$config) {
|
||||
static::$_loaded = true;
|
||||
return;
|
||||
}
|
||||
if ($key !== null) {
|
||||
foreach (\array_reverse(\explode('.', $key)) as $k) {
|
||||
$config = [$k => $config];
|
||||
}
|
||||
}
|
||||
static::$_config = \array_replace_recursive(static::$_config, $config);
|
||||
static::formatConfig();
|
||||
static::$_loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This deprecated method will certainly be removed in the future
|
||||
*
|
||||
* @deprecated
|
||||
* @param string $config_path
|
||||
* @param array $exclude_file
|
||||
* @return void
|
||||
*/
|
||||
public static function reload(string $config_path, array $exclude_file = [])
|
||||
{
|
||||
static::load($config_path, $exclude_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function clear()
|
||||
{
|
||||
static::$_config = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected static function formatConfig()
|
||||
{
|
||||
$config = static::$_config;
|
||||
// Merge log config
|
||||
foreach ($config['plugin'] ?? [] as $firm => $projects) {
|
||||
if (isset($projects['app'])) {
|
||||
foreach ($projects['log'] ?? [] as $key => $item) {
|
||||
$config['log']["plugin.$firm.$key"] = $item;
|
||||
}
|
||||
}
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!\is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['log'] ?? [] as $key => $item) {
|
||||
$config['log']["plugin.$firm.$name.$key"] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Merge database config
|
||||
foreach ($config['plugin'] ?? [] as $firm => $projects) {
|
||||
if (isset($projects['app'])) {
|
||||
foreach ($projects['database']['connections'] ?? [] as $key => $connection) {
|
||||
$config['database']['connections']["plugin.$firm.$key"] = $connection;
|
||||
}
|
||||
}
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!\is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['database']['connections'] ?? [] as $key => $connection) {
|
||||
$config['database']['connections']["plugin.$firm.$name.$key"] = $connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($config['database']['connections'])) {
|
||||
$config['database']['default'] = $config['database']['default'] ?? key($config['database']['connections']);
|
||||
}
|
||||
// Merge thinkorm config
|
||||
foreach ($config['plugin'] ?? [] as $firm => $projects) {
|
||||
if (isset($projects['app'])) {
|
||||
foreach ($projects['thinkorm']['connections'] ?? [] as $key => $connection) {
|
||||
$config['thinkorm']['connections']["plugin.$firm.$key"] = $connection;
|
||||
}
|
||||
}
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!\is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['thinkorm']['connections'] ?? [] as $key => $connection) {
|
||||
$config['thinkorm']['connections']["plugin.$firm.$name.$key"] = $connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($config['thinkorm']['connections'])) {
|
||||
$config['thinkorm']['default'] = $config['thinkorm']['default'] ?? \key($config['thinkorm']['connections']);
|
||||
}
|
||||
// Merge redis config
|
||||
foreach ($config['plugin'] ?? [] as $firm => $projects) {
|
||||
if (isset($projects['app'])) {
|
||||
foreach ($projects['redis'] ?? [] as $key => $connection) {
|
||||
$config['redis']["plugin.$firm.$key"] = $connection;
|
||||
}
|
||||
}
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!\is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['redis'] ?? [] as $key => $connection) {
|
||||
$config['redis']["plugin.$firm.$name.$key"] = $connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
static::$_config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $config_path
|
||||
* @param array $exclude_file
|
||||
* @return array
|
||||
*/
|
||||
public static function loadFromDir(string $config_path, array $exclude_file = [])
|
||||
{
|
||||
$all_config = [];
|
||||
$dir_iterator = new \RecursiveDirectoryIterator($config_path, \FilesystemIterator::FOLLOW_SYMLINKS);
|
||||
$iterator = new \RecursiveIteratorIterator($dir_iterator);
|
||||
foreach ($iterator as $file) {
|
||||
/** var SplFileInfo $file */
|
||||
if (\is_dir($file) || $file->getExtension() != 'php' || \in_array($file->getBaseName('.php'), $exclude_file)) {
|
||||
continue;
|
||||
}
|
||||
$app_config_file = $file->getPath() . '/app.php';
|
||||
if (!\is_file($app_config_file)) {
|
||||
continue;
|
||||
}
|
||||
$relative_path = \str_replace($config_path . DIRECTORY_SEPARATOR, '', substr($file, 0, -4));
|
||||
$explode = \array_reverse(\explode(DIRECTORY_SEPARATOR, $relative_path));
|
||||
if (\count($explode) >= 2) {
|
||||
$app_config = include $app_config_file;
|
||||
if (empty($app_config['enable'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$config = include $file;
|
||||
foreach ($explode as $section) {
|
||||
$tmp = [];
|
||||
$tmp[$section] = $config;
|
||||
$config = $tmp;
|
||||
}
|
||||
$all_config = \array_replace_recursive($all_config, $config);
|
||||
}
|
||||
return $all_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $key
|
||||
* @param mixed $default
|
||||
* @return array|mixed|void|null
|
||||
*/
|
||||
public static function get(string $key = null, $default = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
return static::$_config;
|
||||
}
|
||||
$key_array = \explode('.', $key);
|
||||
$value = static::$_config;
|
||||
$found = true;
|
||||
foreach ($key_array as $index) {
|
||||
if (!isset($value[$index])) {
|
||||
if (static::$_loaded) {
|
||||
return $default;
|
||||
}
|
||||
$found = false;
|
||||
break;
|
||||
}
|
||||
$value = $value[$index];
|
||||
}
|
||||
if ($found) {
|
||||
return $value;
|
||||
}
|
||||
return static::read($key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
protected static function read(string $key, $default = null)
|
||||
{
|
||||
$path = static::$_configPath;
|
||||
if ($path === '') {
|
||||
return $default;
|
||||
}
|
||||
$keys = $key_array = \explode('.', $key);
|
||||
foreach ($key_array as $index => $section) {
|
||||
unset($keys[$index]);
|
||||
if (\is_file($file = "$path/$section.php")) {
|
||||
$config = include $file;
|
||||
return static::find($keys, $config, $default);
|
||||
}
|
||||
if (!\is_dir($path = "$path/$section")) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $key_array
|
||||
* @param mixed $stack
|
||||
* @param mixed $default
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected static function find(array $key_array, $stack, $default)
|
||||
{
|
||||
if (!\is_array($stack)) {
|
||||
return $default;
|
||||
}
|
||||
$value = $stack;
|
||||
foreach ($key_array as $index) {
|
||||
if (!isset($value[$index])) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$index];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Webman;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Webman\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Container
|
||||
* @package Webman
|
||||
*/
|
||||
class Container implements ContainerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_instances = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_definitions = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function get(string $name)
|
||||
{
|
||||
if (!isset($this->_instances[$name])) {
|
||||
if (isset($this->_definitions[$name])) {
|
||||
$this->_instances[$name] = call_user_func($this->_definitions[$name], $this);
|
||||
} else {
|
||||
if (!\class_exists($name)) {
|
||||
throw new NotFoundException("Class '$name' not found");
|
||||
}
|
||||
$this->_instances[$name] = new $name();
|
||||
}
|
||||
}
|
||||
return $this->_instances[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return \array_key_exists($name, $this->_instances)
|
||||
|| array_key_exists($name, $this->_definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $constructor
|
||||
* @return mixed
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function make(string $name, array $constructor = [])
|
||||
{
|
||||
if (!\class_exists($name)) {
|
||||
throw new NotFoundException("Class '$name' not found");
|
||||
}
|
||||
return new $name(... array_values($constructor));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $definitions
|
||||
* @return $this
|
||||
*/
|
||||
public function addDefinitions(array $definitions)
|
||||
{
|
||||
$this->_definitions = array_merge($this->_definitions, $definitions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Exception;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* Class Handler
|
||||
* @package support\exception
|
||||
*/
|
||||
class ExceptionHandler implements ExceptionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $_logger = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $_debug = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $dontReport = [];
|
||||
|
||||
/**
|
||||
* ExceptionHandler constructor.
|
||||
* @param $logger
|
||||
* @param $debug
|
||||
*/
|
||||
public function __construct($logger, $debug)
|
||||
{
|
||||
$this->_logger = $logger;
|
||||
$this->_debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
* @return void
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
if ($this->shouldntReport($exception)) {
|
||||
return;
|
||||
}
|
||||
$logs = '';
|
||||
if ($request = \request()) {
|
||||
$logs = $request->getRealIp() . ' ' . $request->method() . ' ' . \trim($request->fullUrl(), '/');
|
||||
}
|
||||
$this->_logger->error($logs . PHP_EOL . $exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Throwable $exception
|
||||
* @return Response
|
||||
*/
|
||||
public function render(Request $request, Throwable $exception): Response
|
||||
{
|
||||
$code = $exception->getCode();
|
||||
if ($request->expectsJson()) {
|
||||
$json = ['code' => $code ? $code : 500, 'msg' => $this->_debug ? $exception->getMessage() : 'Server internal error'];
|
||||
$this->_debug && $json['traces'] = (string)$exception;
|
||||
return new Response(200, ['Content-Type' => 'application/json'],
|
||||
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
$error = $this->_debug ? \nl2br((string)$exception) : 'Server internal error';
|
||||
return new Response(500, [], $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $e
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldntReport(Throwable $e)
|
||||
{
|
||||
foreach ($this->dontReport as $type) {
|
||||
if ($e instanceof $type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Exception;
|
||||
|
||||
use Throwable;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
interface ExceptionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @param Throwable $e
|
||||
* @return mixed
|
||||
*/
|
||||
public function report(Throwable $e);
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Throwable $e
|
||||
* @return Response
|
||||
*/
|
||||
public function render(Request $request, Throwable $e): Response;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class FileException
|
||||
* @package Webman\Exception
|
||||
*/
|
||||
class FileException extends RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Exception;
|
||||
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class NotFoundException
|
||||
* @package Webman\Exception
|
||||
*/
|
||||
class NotFoundException extends \Exception implements NotFoundExceptionInterface
|
||||
{
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
use Webman\Exception\FileException;
|
||||
|
||||
class File extends \SplFileInfo
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $destination
|
||||
* @return File
|
||||
*/
|
||||
public function move(string $destination)
|
||||
{
|
||||
\set_error_handler(function ($type, $msg) use (&$error) {
|
||||
$error = $msg;
|
||||
});
|
||||
$path = \pathinfo($destination, PATHINFO_DIRNAME);
|
||||
if (!\is_dir($path) && !\mkdir($path, 0777, true)) {
|
||||
\restore_error_handler();
|
||||
throw new FileException(\sprintf('Unable to create the "%s" directory (%s)', $path, \strip_tags($error)));
|
||||
}
|
||||
if (!rename($this->getPathname(), $destination)) {
|
||||
\restore_error_handler();
|
||||
throw new FileException(\sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $destination, \strip_tags($error)));
|
||||
}
|
||||
\restore_error_handler();
|
||||
@\chmod($destination, 0666 & ~\umask());
|
||||
return new self($destination);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Webman;
|
||||
|
||||
/**
|
||||
* This deprecated class will certainly be removed in the future.
|
||||
* Please use Webman\Session\FileSessionHandler
|
||||
* @deprecated
|
||||
* @package Webman
|
||||
*/
|
||||
class FileSessionHandler extends Session\FileSessionHandler
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Http;
|
||||
|
||||
use Webman\App;
|
||||
use Webman\Route\Route;
|
||||
|
||||
/**
|
||||
* Class Request
|
||||
* @package Webman\Http
|
||||
*/
|
||||
class Request extends \Workerman\Protocols\Http\Request
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $plugin = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $app = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $controller = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $action = null;
|
||||
|
||||
/**
|
||||
* @var Route
|
||||
*/
|
||||
public $route = null;
|
||||
|
||||
/**
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->post() + $this->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function input($name, $default = null)
|
||||
{
|
||||
$post = $this->post();
|
||||
if (isset($post[$name])) {
|
||||
return $post[$name];
|
||||
}
|
||||
$get = $this->get();
|
||||
return isset($get[$name]) ? $get[$name] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return array
|
||||
*/
|
||||
public function only(array $keys)
|
||||
{
|
||||
$all = $this->all();
|
||||
$result = [];
|
||||
foreach ($keys as $key) {
|
||||
if (isset($all[$key])) {
|
||||
$result[$key] = $all[$key];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function except(array $keys)
|
||||
{
|
||||
$all = $this->all();
|
||||
foreach ($keys as $key) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @return null|UploadFile[]|UploadFile
|
||||
*/
|
||||
public function file($name = null)
|
||||
{
|
||||
$files = parent::file($name);
|
||||
if (null === $files) {
|
||||
return $name === null ? [] : null;
|
||||
}
|
||||
if ($name !== null) {
|
||||
// Multi files
|
||||
if (\is_array(\current($files))) {
|
||||
return $this->parseFiles($files);
|
||||
}
|
||||
return $this->parseFile($files);
|
||||
}
|
||||
$upload_files = [];
|
||||
foreach ($files as $name => $file) {
|
||||
// Multi files
|
||||
if (\is_array(\current($file))) {
|
||||
$upload_files[$name] = $this->parseFiles($file);
|
||||
} else {
|
||||
$upload_files[$name] = $this->parseFile($file);
|
||||
}
|
||||
}
|
||||
return $upload_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $file
|
||||
* @return UploadFile
|
||||
*/
|
||||
protected function parseFile(array $file)
|
||||
{
|
||||
return new UploadFile($file['tmp_name'], $file['name'], $file['type'], $file['error']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
* @return array
|
||||
*/
|
||||
protected function parseFiles(array $files)
|
||||
{
|
||||
$upload_files = [];
|
||||
foreach ($files as $key => $file) {
|
||||
if (\is_array(\current($file))) {
|
||||
$upload_files[$key] = $this->parseFiles($file);
|
||||
} else {
|
||||
$upload_files[$key] = $this->parseFile($file);
|
||||
}
|
||||
}
|
||||
return $upload_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteIp()
|
||||
{
|
||||
return App::connection()->getRemoteIp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRemotePort()
|
||||
{
|
||||
return App::connection()->getRemotePort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalIp()
|
||||
{
|
||||
return App::connection()->getLocalIp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLocalPort()
|
||||
{
|
||||
return App::connection()->getLocalPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $safe_mode
|
||||
* @return string
|
||||
*/
|
||||
public function getRealIp(bool $safe_mode = true)
|
||||
{
|
||||
$remote_ip = $this->getRemoteIp();
|
||||
if ($safe_mode && !static::isIntranetIp($remote_ip)) {
|
||||
return $remote_ip;
|
||||
}
|
||||
return $this->header('x-real-ip', $this->header('x-forwarded-for',
|
||||
$this->header('client-ip', $this->header('x-client-ip',
|
||||
$this->header('via', $remote_ip)))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
return '//' . $this->host() . $this->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function fullUrl()
|
||||
{
|
||||
return '//' . $this->host() . $this->uri();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isAjax()
|
||||
{
|
||||
return $this->header('X-Requested-With') === 'XMLHttpRequest';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isPjax()
|
||||
{
|
||||
return (bool)$this->header('X-PJAX');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function expectsJson()
|
||||
{
|
||||
return ($this->isAjax() && !$this->isPjax()) || $this->acceptJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function acceptJson()
|
||||
{
|
||||
return false !== \strpos($this->header('accept', ''), 'json');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
* @return bool
|
||||
*/
|
||||
public static function isIntranetIp(string $ip)
|
||||
{
|
||||
// Not validate ip .
|
||||
if (!\filter_var($ip, \FILTER_VALIDATE_IP)) {
|
||||
return false;
|
||||
}
|
||||
// Is intranet ip ? For IPv4, the result of false may not be accurate, so we need to check it manually later .
|
||||
if (!\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE)) {
|
||||
return true;
|
||||
}
|
||||
// Manual check only for IPv4 .
|
||||
if (!\filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
|
||||
return false;
|
||||
}
|
||||
// Manual check .
|
||||
$reserved_ips = [
|
||||
1681915904 => 1686110207, // 100.64.0.0 - 100.127.255.255
|
||||
3221225472 => 3221225727, // 192.0.0.0 - 192.0.0.255
|
||||
3221225984 => 3221226239, // 192.0.2.0 - 192.0.2.255
|
||||
3227017984 => 3227018239, // 192.88.99.0 - 192.88.99.255
|
||||
3323068416 => 3323199487, // 198.18.0.0 - 198.19.255.255
|
||||
3325256704 => 3325256959, // 198.51.100.0 - 198.51.100.255
|
||||
3405803776 => 3405804031, // 203.0.113.0 - 203.0.113.255
|
||||
3758096384 => 4026531839, // 224.0.0.0 - 239.255.255.255
|
||||
];
|
||||
$ip_long = \ip2long($ip);
|
||||
foreach ($reserved_ips as $ip_start => $ip_end) {
|
||||
if (($ip_long >= $ip_start) && ($ip_long <= $ip_end)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Http;
|
||||
|
||||
use Webman\App;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
* @package Webman\Http
|
||||
*/
|
||||
class Response extends \Workerman\Protocols\Http\Response
|
||||
{
|
||||
/**
|
||||
* @var Throwable
|
||||
*/
|
||||
protected $_exception = null;
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return $this
|
||||
*/
|
||||
public function file(string $file)
|
||||
{
|
||||
if ($this->notModifiedSince($file)) {
|
||||
return $this->withStatus(304);
|
||||
}
|
||||
return $this->withFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $download_name
|
||||
* @return $this
|
||||
*/
|
||||
public function download(string $file, string $download_name = '')
|
||||
{
|
||||
$this->withFile($file);
|
||||
if ($download_name) {
|
||||
$this->header('Content-Disposition', "attachment; filename=\"$download_name\"");
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
protected function notModifiedSince(string $file)
|
||||
{
|
||||
$if_modified_since = App::request()->header('if-modified-since');
|
||||
if ($if_modified_since === null || !($mtime = \filemtime($file))) {
|
||||
return false;
|
||||
}
|
||||
return $if_modified_since === \gmdate('D, d M Y H:i:s', $mtime) . ' GMT';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $exception
|
||||
* @return Throwable
|
||||
*/
|
||||
public function exception($exception = null)
|
||||
{
|
||||
if ($exception) {
|
||||
$this->_exception = $exception;
|
||||
}
|
||||
return $this->_exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Http;
|
||||
|
||||
use Webman\File;
|
||||
|
||||
/**
|
||||
* Class UploadFile
|
||||
* @package Webman\Http
|
||||
*/
|
||||
class UploadFile extends File
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_uploadName = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_uploadMimeType = null;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $_uploadErrorCode = null;
|
||||
|
||||
/**
|
||||
* UploadFile constructor.
|
||||
*
|
||||
* @param string $file_name
|
||||
* @param string $upload_name
|
||||
* @param string $upload_mime_type
|
||||
* @param int $upload_error_code
|
||||
*/
|
||||
public function __construct(string $file_name, string $upload_name, string $upload_mime_type, int $upload_error_code)
|
||||
{
|
||||
$this->_uploadName = $upload_name;
|
||||
$this->_uploadMimeType = $upload_mime_type;
|
||||
$this->_uploadErrorCode = $upload_error_code;
|
||||
parent::__construct($file_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUploadName()
|
||||
{
|
||||
return $this->_uploadName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUploadMimeType()
|
||||
{
|
||||
return $this->_uploadMimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUploadExtension()
|
||||
{
|
||||
return \pathinfo($this->_uploadName, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getUploadErrorCode()
|
||||
{
|
||||
return $this->_uploadErrorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->_uploadErrorCode === UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return string
|
||||
*/
|
||||
public function getUploadMineType()
|
||||
{
|
||||
return $this->_uploadMimeType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Webman;
|
||||
|
||||
class Install
|
||||
{
|
||||
const WEBMAN_PLUGIN = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $pathRelation = [
|
||||
'start.php' => 'start.php',
|
||||
'windows.php' => 'windows.php',
|
||||
'support/bootstrap.php' => 'support/bootstrap.php',
|
||||
'support/helpers.php' => 'support/helpers.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* Install
|
||||
* @return void
|
||||
*/
|
||||
public static function install()
|
||||
{
|
||||
static::installByRelation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall
|
||||
* @return void
|
||||
*/
|
||||
public static function uninstall()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* installByRelation
|
||||
* @return void
|
||||
*/
|
||||
public static function installByRelation()
|
||||
{
|
||||
foreach (static::$pathRelation as $source => $dest) {
|
||||
if ($pos = strrpos($dest, '/')) {
|
||||
$parent_dir = base_path() . '/' . substr($dest, 0, $pos);
|
||||
if (!is_dir($parent_dir)) {
|
||||
mkdir($parent_dir, 0777, true);
|
||||
}
|
||||
}
|
||||
$source_file = __DIR__ . "/$source";
|
||||
copy_dir($source_file, base_path() . "/$dest", true);
|
||||
echo "Create $dest\r\n";
|
||||
if (is_file($source_file)) {
|
||||
@unlink($source_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Webman;
|
||||
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
class Middleware
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_instances = [];
|
||||
|
||||
/**
|
||||
* @param array $all_middlewares
|
||||
* @param string $plugin
|
||||
* @return void
|
||||
*/
|
||||
public static function load($all_middlewares, string $plugin = '')
|
||||
{
|
||||
if (!\is_array($all_middlewares)) {
|
||||
return;
|
||||
}
|
||||
foreach ($all_middlewares as $app_name => $middlewares) {
|
||||
if (!\is_array($middlewares)) {
|
||||
throw new \RuntimeException('Bad middleware config');
|
||||
}
|
||||
foreach ($middlewares as $class_name) {
|
||||
if (\method_exists($class_name, 'process')) {
|
||||
static::$_instances[$plugin][$app_name][] = [$class_name, 'process'];
|
||||
} else {
|
||||
// @todo Log
|
||||
echo "middleware $class_name::process not exsits\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plugin
|
||||
* @param string $app_name
|
||||
* @param bool $with_global_middleware
|
||||
* @return array|mixed
|
||||
*/
|
||||
public static function getMiddleware(string $plugin, string $app_name, bool $with_global_middleware = true)
|
||||
{
|
||||
$global_middleware = $with_global_middleware && isset(static::$_instances[$plugin]['']) ? static::$_instances[$plugin][''] : [];
|
||||
if ($app_name === '') {
|
||||
return \array_reverse($global_middleware);
|
||||
}
|
||||
$app_middleware = static::$_instances[$plugin][$app_name] ?? [];
|
||||
return \array_reverse(\array_merge($global_middleware, $app_middleware));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return void
|
||||
*/
|
||||
public static function container($_)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
interface MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Process an incoming server request.
|
||||
*
|
||||
* Processes an incoming server request in order to produce a response.
|
||||
* If unable to produce the response itself, it may delegate to the provided
|
||||
* request handler to do so.
|
||||
*/
|
||||
public function process(Request $request, callable $handler): Response;
|
||||
}
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
use FastRoute\Dispatcher\GroupCountBased;
|
||||
use FastRoute\RouteCollector;
|
||||
use Webman\Route\Route as RouteObject;
|
||||
use function FastRoute\simpleDispatcher;
|
||||
|
||||
/**
|
||||
* Class Route
|
||||
* @package Webman
|
||||
*/
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* @var Route
|
||||
*/
|
||||
protected static $_instance = null;
|
||||
|
||||
/**
|
||||
* @var GroupCountBased
|
||||
*/
|
||||
protected static $_dispatcher = null;
|
||||
|
||||
/**
|
||||
* @var RouteCollector
|
||||
*/
|
||||
protected static $_collector = null;
|
||||
|
||||
/**
|
||||
* @var null|callable
|
||||
*/
|
||||
protected static $_fallback = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_nameList = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $_groupPrefix = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $_disableDefaultRoute = [];
|
||||
|
||||
/**
|
||||
* @var RouteObject[]
|
||||
*/
|
||||
protected static $_allRoutes = [];
|
||||
|
||||
/**
|
||||
* @var RouteObject[]
|
||||
*/
|
||||
protected $_routes = [];
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function get(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('GET', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function post(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('POST', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function put(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('PUT', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function patch(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('PATCH', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function delete(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('DELETE', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function head(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('HEAD', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function options(string $path, $callback)
|
||||
{
|
||||
return static::addRoute('OPTIONS', $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function any(string $path, $callback)
|
||||
{
|
||||
return static::addRoute(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
public static function add($method, string $path, $callback)
|
||||
{
|
||||
return static::addRoute($method, $path, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|callable $path
|
||||
* @param callable|null $callback
|
||||
* @return static
|
||||
*/
|
||||
public static function group($path, callable $callback = null)
|
||||
{
|
||||
if ($callback === null) {
|
||||
$callback = $path;
|
||||
$path = '';
|
||||
}
|
||||
$previous_group_prefix = static::$_groupPrefix;
|
||||
static::$_groupPrefix = $previous_group_prefix . $path;
|
||||
$instance = static::$_instance = new static;
|
||||
static::$_collector->addGroup($path, $callback);
|
||||
static::$_instance = null;
|
||||
static::$_groupPrefix = $previous_group_prefix;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $controller
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public static function resource(string $name, string $controller, array $options = [])
|
||||
{
|
||||
$name = trim($name, '/');
|
||||
if (\is_array($options) && !empty($options)) {
|
||||
$diff_options = \array_diff($options, ['index', 'create', 'store', 'update', 'show', 'edit', 'destroy', 'recovery']);
|
||||
if (!empty($diff_options)) {
|
||||
foreach ($diff_options as $action) {
|
||||
static::any("/{$name}/{$action}[/{id}]", [$controller, $action])->name("{$name}.{$action}");
|
||||
}
|
||||
}
|
||||
// 注册路由 由于顺序不同会导致路由无效 因此不适用循环注册
|
||||
if (\in_array('index', $options)) static::get("/{$name}", [$controller, 'index'])->name("{$name}.index");
|
||||
if (\in_array('create', $options)) static::get("/{$name}/create", [$controller, 'create'])->name("{$name}.create");
|
||||
if (\in_array('store', $options)) static::post("/{$name}", [$controller, 'store'])->name("{$name}.store");
|
||||
if (\in_array('update', $options)) static::put("/{$name}/{id}", [$controller, 'update'])->name("{$name}.update");
|
||||
if (\in_array('show', $options)) static::get("/{$name}/{id}", [$controller, 'show'])->name("{$name}.show");
|
||||
if (\in_array('edit', $options)) static::get("/{$name}/{id}/edit", [$controller, 'edit'])->name("{$name}.edit");
|
||||
if (\in_array('destroy', $options)) static::delete("/{$name}/{id}", [$controller, 'destroy'])->name("{$name}.destroy");
|
||||
if (\in_array('recovery', $options)) static::put("/{$name}/{id}/recovery", [$controller, 'recovery'])->name("{$name}.recovery");
|
||||
} else {
|
||||
//为空时自动注册所有常用路由
|
||||
if (\method_exists($controller, 'index')) static::get("/{$name}", [$controller, 'index'])->name("{$name}.index");
|
||||
if (\method_exists($controller, 'create')) static::get("/{$name}/create", [$controller, 'create'])->name("{$name}.create");
|
||||
if (\method_exists($controller, 'store')) static::post("/{$name}", [$controller, 'store'])->name("{$name}.store");
|
||||
if (\method_exists($controller, 'update')) static::put("/{$name}/{id}", [$controller, 'update'])->name("{$name}.update");
|
||||
if (\method_exists($controller, 'show')) static::get("/{$name}/{id}", [$controller, 'show'])->name("{$name}.show");
|
||||
if (\method_exists($controller, 'edit')) static::get("/{$name}/{id}/edit", [$controller, 'edit'])->name("{$name}.edit");
|
||||
if (\method_exists($controller, 'destroy')) static::delete("/{$name}/{id}", [$controller, 'destroy'])->name("{$name}.destroy");
|
||||
if (\method_exists($controller, 'recovery')) static::put("/{$name}/{id}/recovery", [$controller, 'recovery'])->name("{$name}.recovery");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RouteObject[]
|
||||
*/
|
||||
public static function getRoutes()
|
||||
{
|
||||
return static::$_allRoutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* disableDefaultRoute.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function disableDefaultRoute($plugin = '')
|
||||
{
|
||||
static::$_disableDefaultRoute[$plugin] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasDisableDefaultRoute($plugin = '')
|
||||
{
|
||||
return static::$_disableDefaultRoute[$plugin] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $middleware
|
||||
* @return $this
|
||||
*/
|
||||
public function middleware($middleware)
|
||||
{
|
||||
foreach ($this->_routes as $route) {
|
||||
$route->middleware($middleware);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RouteObject $route
|
||||
*/
|
||||
public function collect(RouteObject $route)
|
||||
{
|
||||
$this->_routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param RouteObject $instance
|
||||
*/
|
||||
public static function setByName(string $name, RouteObject $instance)
|
||||
{
|
||||
static::$_nameList[$name] = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return null|RouteObject
|
||||
*/
|
||||
public static function getByName(string $name)
|
||||
{
|
||||
return static::$_nameList[$name] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public static function dispatch($method, string $path)
|
||||
{
|
||||
return static::$_dispatcher->dispatch($method, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return callable|false|string[]
|
||||
*/
|
||||
public static function convertToCallable(string $path, $callback)
|
||||
{
|
||||
if (\is_string($callback) && \strpos($callback, '@')) {
|
||||
$callback = \explode('@', $callback, 2);
|
||||
}
|
||||
|
||||
if (!\is_array($callback)) {
|
||||
if (!\is_callable($callback)) {
|
||||
$call_str = \is_scalar($callback) ? $callback : 'Closure';
|
||||
echo "Route $path $call_str is not callable\n";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$callback = \array_values($callback);
|
||||
if (!isset($callback[1]) || !\class_exists($callback[0]) || !\method_exists($callback[0], $callback[1])) {
|
||||
echo "Route $path " . \json_encode($callback) . " is not callable\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $methods
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
* @return RouteObject
|
||||
*/
|
||||
protected static function addRoute($methods, string $path, $callback)
|
||||
{
|
||||
$route = new RouteObject($methods, static::$_groupPrefix . $path, $callback);
|
||||
static::$_allRoutes[] = $route;
|
||||
|
||||
if ($callback = static::convertToCallable($path, $callback)) {
|
||||
static::$_collector->addRoute($methods, $path, ['callback' => $callback, 'route' => $route]);
|
||||
}
|
||||
if (static::$_instance) {
|
||||
static::$_instance->collect($route);
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $paths
|
||||
* @return void
|
||||
*/
|
||||
public static function load($paths)
|
||||
{
|
||||
if (!\is_array($paths)) {
|
||||
return;
|
||||
}
|
||||
static::$_dispatcher = simpleDispatcher(function (RouteCollector $route) use ($paths) {
|
||||
Route::setCollector($route);
|
||||
foreach ($paths as $config_path) {
|
||||
$route_config_file = $config_path . '/route.php';
|
||||
if (\is_file($route_config_file)) {
|
||||
require_once $route_config_file;
|
||||
}
|
||||
if (!is_dir($plugin_config_path = $config_path . '/plugin')) {
|
||||
continue;
|
||||
}
|
||||
$dir_iterator = new \RecursiveDirectoryIterator($plugin_config_path, \FilesystemIterator::FOLLOW_SYMLINKS);
|
||||
$iterator = new \RecursiveIteratorIterator($dir_iterator);
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->getBaseName('.php') !== 'route') {
|
||||
continue;
|
||||
}
|
||||
$app_config_file = pathinfo($file, PATHINFO_DIRNAME) . '/app.php';
|
||||
if (!is_file($app_config_file)) {
|
||||
continue;
|
||||
}
|
||||
$app_config = include $app_config_file;
|
||||
if (empty($app_config['enable'])) {
|
||||
continue;
|
||||
}
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RouteCollector $route
|
||||
* @return void
|
||||
*/
|
||||
public static function setCollector(RouteCollector $route)
|
||||
{
|
||||
static::$_collector = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @param string $plugin
|
||||
* @return void
|
||||
*/
|
||||
public static function fallback(callable $callback, string $plugin = '')
|
||||
{
|
||||
static::$_fallback[$plugin] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable|null
|
||||
*/
|
||||
public static function getFallback(string $plugin = '')
|
||||
{
|
||||
return static::$_fallback[$plugin] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return void
|
||||
*/
|
||||
public static function container()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Route;
|
||||
|
||||
use FastRoute\Dispatcher\GroupCountBased;
|
||||
use FastRoute\RouteCollector;
|
||||
use Webman\Route as Router;
|
||||
|
||||
/**
|
||||
* Class Route
|
||||
* @package Webman
|
||||
*/
|
||||
class Route
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_name = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_methods = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_path = '';
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $_callback = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_middlewares = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_params = [];
|
||||
|
||||
/**
|
||||
* Route constructor.
|
||||
*
|
||||
* @param array $methods
|
||||
* @param string $path
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function __construct($methods, string $path, $callback)
|
||||
{
|
||||
$this->_methods = (array)$methods;
|
||||
$this->_path = $path;
|
||||
$this->_callback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->_name ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function name(string $name)
|
||||
{
|
||||
$this->_name = $name;
|
||||
Router::setByName($name, $this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $middleware
|
||||
* @return $this|array
|
||||
*/
|
||||
public function middleware($middleware = null)
|
||||
{
|
||||
if ($middleware === null) {
|
||||
return $this->_middlewares;
|
||||
}
|
||||
$this->_middlewares = \array_merge($this->_middlewares, is_array($middleware) ? $middleware : [$middleware]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMethods()
|
||||
{
|
||||
return $this->_methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getCallback()
|
||||
{
|
||||
return $this->_callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMiddleware()
|
||||
{
|
||||
return $this->_middlewares;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @param $default
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public function param(string $name = null, $default = null)
|
||||
{
|
||||
if ($name === null) {
|
||||
return $this->_params;
|
||||
}
|
||||
return $this->_params[$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->_params = \array_merge($this->_params, $params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $parameters
|
||||
* @return string
|
||||
*/
|
||||
public function url($parameters = [])
|
||||
{
|
||||
if (empty($parameters)) {
|
||||
return $this->_path;
|
||||
}
|
||||
$path = \str_replace(['[', ']'], '', $this->_path);
|
||||
$path = \preg_replace_callback('/\{(.*?)(?:\:[^\}]*?)*?\}/', function ($matches) use (&$parameters) {
|
||||
if (!$parameters) {
|
||||
return $matches[0];
|
||||
}
|
||||
if (isset($parameters[$matches[1]])) {
|
||||
$value = $parameters[$matches[1]];
|
||||
unset($parameters[$matches[1]]);
|
||||
return $value;
|
||||
}
|
||||
$key = key($parameters);
|
||||
if (is_int($key)) {
|
||||
$value = $parameters[$key];
|
||||
unset($parameters[$key]);
|
||||
return $value;
|
||||
}
|
||||
return $matches[0];
|
||||
}, $path);
|
||||
return \count($parameters) > 0 ? $path . '?' . http_build_query($parameters) : $path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Session;
|
||||
|
||||
use FastRoute\Dispatcher\GroupCountBased;
|
||||
use FastRoute\RouteCollector;
|
||||
use Workerman\Protocols\Http\Session\FileSessionHandler as FileHandler;
|
||||
|
||||
/**
|
||||
* Class FileSessionHandler
|
||||
* @package Webman
|
||||
*/
|
||||
class FileSessionHandler extends FileHandler
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Session;
|
||||
|
||||
use Workerman\Protocols\Http\Session\RedisClusterSessionHandler as RedisClusterHandler;
|
||||
|
||||
class RedisClusterSessionHandler extends RedisClusterHandler
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman\Session;
|
||||
|
||||
use FastRoute\Dispatcher\GroupCountBased;
|
||||
use FastRoute\RouteCollector;
|
||||
use Workerman\Protocols\Http\Session\RedisSessionHandler as RedisHandler;
|
||||
|
||||
/**
|
||||
* Class FileSessionHandler
|
||||
* @package Webman
|
||||
*/
|
||||
class RedisSessionHandler extends RedisHandler
|
||||
{
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
/**
|
||||
* Class Util
|
||||
* @package Webman
|
||||
*/
|
||||
class Util
|
||||
{
|
||||
/**
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public static function scanDir(string $base_path, $with_base_path = true): array
|
||||
{
|
||||
if (!is_dir($base_path)) {
|
||||
return [];
|
||||
}
|
||||
$paths = \array_diff(\scandir($base_path), array('.', '..')) ?: [];
|
||||
return $with_base_path ? \array_map(function($path) use ($base_path) {
|
||||
return $base_path . DIRECTORY_SEPARATOR . $path;
|
||||
}, $paths) : $paths;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Webman;
|
||||
|
||||
interface View
|
||||
{
|
||||
/**
|
||||
* @param $template
|
||||
* @param $vars
|
||||
* @param null $app
|
||||
* @return string
|
||||
*/
|
||||
static function render(string $template, array $vars, string $app = null);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace support;
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use Webman\Config;
|
||||
use Webman\Util;
|
||||
use Workerman\Connection\TcpConnection;
|
||||
use Workerman\Protocols\Http;
|
||||
use Workerman\Worker;
|
||||
|
||||
class App
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function run()
|
||||
{
|
||||
ini_set('display_errors', 'on');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (class_exists(Dotenv::class) && file_exists(run_path('.env'))) {
|
||||
if (method_exists(Dotenv::class, 'createUnsafeImmutable')) {
|
||||
Dotenv::createUnsafeImmutable(run_path())->load();
|
||||
} else {
|
||||
Dotenv::createMutable(run_path())->load();
|
||||
}
|
||||
}
|
||||
|
||||
static::loadAllConfig(['route', 'container']);
|
||||
|
||||
$error_reporting = config('app.error_reporting');
|
||||
if (isset($error_reporting)) {
|
||||
error_reporting($error_reporting);
|
||||
}
|
||||
if ($timezone = config('app.default_timezone')) {
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
|
||||
$runtime_logs_path = runtime_path() . DIRECTORY_SEPARATOR . 'logs';
|
||||
if (!file_exists($runtime_logs_path) || !is_dir($runtime_logs_path)) {
|
||||
if (!mkdir($runtime_logs_path, 0777, true)) {
|
||||
throw new \RuntimeException("Failed to create runtime logs directory. Please check the permission.");
|
||||
}
|
||||
}
|
||||
|
||||
$runtime_views_path = runtime_path() . DIRECTORY_SEPARATOR . 'views';
|
||||
if (!file_exists($runtime_views_path) || !is_dir($runtime_views_path)) {
|
||||
if (!mkdir($runtime_views_path, 0777, true)) {
|
||||
throw new \RuntimeException("Failed to create runtime views directory. Please check the permission.");
|
||||
}
|
||||
}
|
||||
|
||||
Worker::$onMasterReload = function () {
|
||||
if (function_exists('opcache_get_status')) {
|
||||
if ($status = \opcache_get_status()) {
|
||||
if (isset($status['scripts']) && $scripts = $status['scripts']) {
|
||||
foreach (array_keys($scripts) as $file) {
|
||||
\opcache_invalidate($file, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$config = config('server');
|
||||
Worker::$pidFile = $config['pid_file'];
|
||||
Worker::$stdoutFile = $config['stdout_file'];
|
||||
Worker::$logFile = $config['log_file'];
|
||||
Worker::$eventLoopClass = $config['event_loop'] ?? '';
|
||||
TcpConnection::$defaultMaxPackageSize = $config['max_package_size'] ?? 10 * 1024 * 1024;
|
||||
if (property_exists(Worker::class, 'statusFile')) {
|
||||
Worker::$statusFile = $config['status_file'] ?? '';
|
||||
}
|
||||
if (property_exists(Worker::class, 'stopTimeout')) {
|
||||
Worker::$stopTimeout = $config['stop_timeout'] ?? 2;
|
||||
}
|
||||
|
||||
if ($config['listen']) {
|
||||
$worker = new Worker($config['listen'], $config['context']);
|
||||
$property_map = [
|
||||
'name',
|
||||
'count',
|
||||
'user',
|
||||
'group',
|
||||
'reusePort',
|
||||
'transport',
|
||||
'protocol'
|
||||
];
|
||||
foreach ($property_map as $property) {
|
||||
if (isset($config[$property])) {
|
||||
$worker->$property = $config[$property];
|
||||
}
|
||||
}
|
||||
|
||||
$worker->onWorkerStart = function ($worker) {
|
||||
require_once \base_path() . '/support/bootstrap.php';
|
||||
$app = new \Webman\App(config('app.request_class', Request::class), Log::channel('default'), app_path(), public_path());
|
||||
$worker->onMessage = [$app, 'onMessage'];
|
||||
\call_user_func([$app, 'onWorkerStart'], $worker);
|
||||
};
|
||||
}
|
||||
|
||||
// Windows does not support custom processes.
|
||||
if (\DIRECTORY_SEPARATOR === '/') {
|
||||
foreach (config('process', []) as $process_name => $config) {
|
||||
worker_start($process_name, $config);
|
||||
}
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['process'] ?? [] as $process_name => $config) {
|
||||
worker_start("plugin.$firm.$name.$process_name", $config);
|
||||
}
|
||||
}
|
||||
foreach ($projects['process'] ?? [] as $process_name => $config) {
|
||||
worker_start("plugin.$firm.$process_name", $config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Worker::runAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $excludes
|
||||
* @return void
|
||||
*/
|
||||
public static function loadAllConfig(array $excludes = [])
|
||||
{
|
||||
Config::load(config_path(), $excludes);
|
||||
$directory = base_path() . '/plugin';
|
||||
foreach (Util::scanDir($directory, false) as $name) {
|
||||
$dir = "$directory/$name/config";
|
||||
if (\is_dir($dir)) {
|
||||
Config::load($dir, $excludes, "plugin.$name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace support;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||
use Symfony\Component\Cache\Psr16Cache;
|
||||
|
||||
/**
|
||||
* Class Cache
|
||||
* @package support\bootstrap
|
||||
*
|
||||
* Strings methods
|
||||
* @method static mixed get($key, $default = null)
|
||||
* @method static bool set($key, $value, $ttl = null)
|
||||
* @method static bool delete($key)
|
||||
* @method static bool clear()
|
||||
* @method static iterable getMultiple($keys, $default = null)
|
||||
* @method static bool setMultiple($values, $ttl = null)
|
||||
* @method static bool deleteMultiple($keys)
|
||||
* @method static bool has($key)
|
||||
*/
|
||||
class Cache
|
||||
{
|
||||
/**
|
||||
* @var Psr16Cache
|
||||
*/
|
||||
public static $_instance = null;
|
||||
|
||||
/**
|
||||
* @return Psr16Cache
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
if (!static::$_instance) {
|
||||
$adapter = new RedisAdapter(Redis::connection()->client());
|
||||
self::$_instance = new Psr16Cache($adapter);
|
||||
}
|
||||
return static::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
return static::instance()->{$name}(... $arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Webman\Config;
|
||||
|
||||
/**
|
||||
* Class Container
|
||||
* @package support
|
||||
* @method static mixed get($name)
|
||||
* @method static mixed make($name, array $parameters)
|
||||
* @method static bool has($name)
|
||||
*/
|
||||
class Container
|
||||
{
|
||||
/**
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public static function instance(string $plugin = '')
|
||||
{
|
||||
return Config::get($plugin ? "plugin.$plugin.container" : 'container');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
$plugin = \Webman\App::getPluginByClass($name);
|
||||
return static::instance($plugin)->{$name}(... $arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager;
|
||||
|
||||
/**
|
||||
* Class Db
|
||||
* @package support
|
||||
* @method static array select(string $query, $bindings = [], $useReadPdo = true)
|
||||
* @method static int insert(string $query, $bindings = [])
|
||||
* @method static int update(string $query, $bindings = [])
|
||||
* @method static int delete(string $query, $bindings = [])
|
||||
* @method static bool statement(string $query, $bindings = [])
|
||||
* @method static mixed transaction(\Closure $callback, $attempts = 1)
|
||||
* @method static void beginTransaction()
|
||||
* @method static void rollBack($toLevel = null)
|
||||
* @method static void commit()
|
||||
*/
|
||||
class Db extends Manager
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
use Monolog\Formatter\FormatterInterface;
|
||||
use Monolog\Handler\FormattableHandlerInterface;
|
||||
use Monolog\Handler\HandlerInterface;
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Class Log
|
||||
* @package support
|
||||
*
|
||||
* @method static void log($level, $message, array $context = [])
|
||||
* @method static void debug($message, array $context = [])
|
||||
* @method static void info($message, array $context = [])
|
||||
* @method static void notice($message, array $context = [])
|
||||
* @method static void warning($message, array $context = [])
|
||||
* @method static void error($message, array $context = [])
|
||||
* @method static void critical($message, array $context = [])
|
||||
* @method static void alert($message, array $context = [])
|
||||
* @method static void emergency($message, array $context = [])
|
||||
*/
|
||||
class Log
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_instance = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Logger
|
||||
*/
|
||||
public static function channel(string $name = 'default')
|
||||
{
|
||||
if (!isset(static::$_instance[$name])) {
|
||||
$config = \config('log', [])[$name];
|
||||
$handlers = self::handlers($config);
|
||||
$processors = self::processors($config);
|
||||
static::$_instance[$name] = new Logger($name, $handlers, $processors);
|
||||
}
|
||||
return static::$_instance[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected static function handlers(array $config): array
|
||||
{
|
||||
$handlerConfigs = $config['handlers'] ?? [[]];
|
||||
$handlers = [];
|
||||
foreach ($handlerConfigs as $value) {
|
||||
$class = $value['class'] ?? [];
|
||||
$constructor = $value['constructor'] ?? [];
|
||||
|
||||
$formatterConfig = $value['formatter'] ?? [];
|
||||
|
||||
$class && $handlers[] = self::handler($class, $constructor, $formatterConfig);
|
||||
}
|
||||
|
||||
return $handlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param array $constructor
|
||||
* @param array $formatterConfig
|
||||
* @return HandlerInterface
|
||||
*/
|
||||
protected static function handler(string $class, array $constructor, array $formatterConfig): HandlerInterface
|
||||
{
|
||||
/** @var HandlerInterface $handler */
|
||||
$handler = new $class(... \array_values($constructor));
|
||||
|
||||
if ($handler instanceof FormattableHandlerInterface && $formatterConfig) {
|
||||
$formatterClass = $formatterConfig['class'];
|
||||
$formatterConstructor = $formatterConfig['constructor'];
|
||||
|
||||
/** @var FormatterInterface $formatter */
|
||||
$formatter = new $formatterClass(... \array_values($formatterConstructor));
|
||||
|
||||
$handler->setFormatter($formatter);
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @return array
|
||||
*/
|
||||
protected static function processors(array $config): array
|
||||
{
|
||||
$result = [];
|
||||
if (!isset($config['processors']) && isset($config['processor'])) {
|
||||
$config['processors'] = [$config['processor']];
|
||||
}
|
||||
|
||||
foreach ($config['processors'] ?? [] as $value) {
|
||||
if (\is_array($value) && isset($value['class'])) {
|
||||
$value = new $value['class'](... \array_values($value['constructor'] ?? []));;
|
||||
}
|
||||
$result[] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
return static::channel('default')->{$name}(... $arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model as BaseModel;
|
||||
|
||||
/**
|
||||
* @method static \Illuminate\Database\Eloquent\Model make($attributes = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withGlobalScope($identifier, $scope)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withoutGlobalScope($scope)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withoutGlobalScopes($scopes = null)
|
||||
* @method static array removedScopes()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder whereKey($id)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder whereKeyNot($id)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder where($column, $operator = null, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Eloquent\Model|null firstWhere($column, $operator = null, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orWhere($column, $operator = null, $value = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder latest($column = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder oldest($column = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Collection hydrate($items)
|
||||
* @method static \Illuminate\Database\Eloquent\Collection fromQuery($query, $bindings = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static[]|static|null find($id, $columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Collection findMany($ids, $columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|static|static[] findOrFail($id, $columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|static findOrNew($id, $columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|static firstOrNew($attributes = [], $values = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|static firstOrCreate($attributes = [], $values = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|static updateOrCreate($attributes, $values = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|static firstOrFail($columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|static|mixed firstOr($columns = [], $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Model sole($columns = [])
|
||||
* @method static mixed value($column)
|
||||
* @method static \Illuminate\Database\Eloquent\Collection[]|static[] get($columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model[]|static[] getModels($columns = [])
|
||||
* @method static array eagerLoadRelations($models)
|
||||
* @method static \Illuminate\Support\LazyCollection cursor()
|
||||
* @method static \Illuminate\Support\Collection pluck($column, $key = null)
|
||||
* @method static \Illuminate\Contracts\Pagination\LengthAwarePaginator paginate($perPage = null, $columns = [], $pageName = 'page', $page = null)
|
||||
* @method static \Illuminate\Contracts\Pagination\Paginator simplePaginate($perPage = null, $columns = [], $pageName = 'page', $page = null)
|
||||
* @method static \Illuminate\Contracts\Pagination\CursorPaginator cursorPaginate($perPage = null, $columns = [], $cursorName = 'cursor', $cursor = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Model|$this create($attributes = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|$this forceCreate($attributes)
|
||||
* @method static int upsert($values, $uniqueBy, $update = null)
|
||||
* @method static void onDelete($callback)
|
||||
* @method static static|mixed scopes($scopes)
|
||||
* @method static static applyScopes()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder without($relations)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withOnly($relations)
|
||||
* @method static \Illuminate\Database\Eloquent\Model newModelInstance($attributes = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withCasts($casts)
|
||||
* @method static \Illuminate\Database\Query\Builder getQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder setQuery($query)
|
||||
* @method static \Illuminate\Database\Query\Builder toBase()
|
||||
* @method static array getEagerLoads()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder setEagerLoads($eagerLoad)
|
||||
* @method static \Illuminate\Database\Eloquent\Model getModel()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder setModel($model)
|
||||
* @method static \Closure getMacro($name)
|
||||
* @method static bool hasMacro($name)
|
||||
* @method static \Closure getGlobalMacro($name)
|
||||
* @method static bool hasGlobalMacro($name)
|
||||
* @method static static clone()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orHas($relation, $operator = '>=', $count = 1)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder doesntHave($relation, $boolean = 'and', $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orDoesntHave($relation)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder whereHas($relation, $callback = null, $operator = '>=', $count = 1)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orWhereHas($relation, $callback = null, $operator = '>=', $count = 1)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder whereDoesntHave($relation, $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orWhereDoesntHave($relation, $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orHasMorph($relation, $types, $operator = '>=', $count = 1)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder doesntHaveMorph($relation, $types, $boolean = 'and', $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orDoesntHaveMorph($relation, $types)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder whereHasMorph($relation, $types, $callback = null, $operator = '>=', $count = 1)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orWhereHasMorph($relation, $types, $callback = null, $operator = '>=', $count = 1)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder whereDoesntHaveMorph($relation, $types, $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder orWhereDoesntHaveMorph($relation, $types, $callback = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withAggregate($relations, $column, $function = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withCount($relations)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withMax($relation, $column)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withMin($relation, $column)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withSum($relation, $column)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withAvg($relation, $column)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder withExists($relation)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder mergeConstraintsFrom($from)
|
||||
* @method static \Illuminate\Support\Collection explain()
|
||||
* @method static bool chunk($count, $callback)
|
||||
* @method static \Illuminate\Support\Collection chunkMap($callback, $count = 1000)
|
||||
* @method static bool each($callback, $count = 1000)
|
||||
* @method static bool chunkById($count, $callback, $column = null, $alias = null)
|
||||
* @method static bool eachById($callback, $count = 1000, $column = null, $alias = null)
|
||||
* @method static \Illuminate\Support\LazyCollection lazy($chunkSize = 1000)
|
||||
* @method static \Illuminate\Support\LazyCollection lazyById($chunkSize = 1000, $column = null, $alias = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null first($columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|null baseSole($columns = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder tap($callback)
|
||||
* @method static mixed when($value, $callback, $default = null)
|
||||
* @method static mixed unless($value, $callback, $default = null)
|
||||
* @method static \Illuminate\Database\Query\Builder select($columns = [])
|
||||
* @method static \Illuminate\Database\Query\Builder selectSub($query, $as)
|
||||
* @method static \Illuminate\Database\Query\Builder selectRaw($expression, $bindings = [])
|
||||
* @method static \Illuminate\Database\Query\Builder fromSub($query, $as)
|
||||
* @method static \Illuminate\Database\Query\Builder fromRaw($expression, $bindings = [])
|
||||
* @method static \Illuminate\Database\Query\Builder addSelect($column)
|
||||
* @method static \Illuminate\Database\Query\Builder distinct()
|
||||
* @method static \Illuminate\Database\Query\Builder from($table, $as = null)
|
||||
* @method static \Illuminate\Database\Query\Builder join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
|
||||
* @method static \Illuminate\Database\Query\Builder joinWhere($table, $first, $operator, $second, $type = 'inner')
|
||||
* @method static \Illuminate\Database\Query\Builder joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false)
|
||||
* @method static \Illuminate\Database\Query\Builder leftJoin($table, $first, $operator = null, $second = null)
|
||||
* @method static \Illuminate\Database\Query\Builder leftJoinWhere($table, $first, $operator, $second)
|
||||
* @method static \Illuminate\Database\Query\Builder leftJoinSub($query, $as, $first, $operator = null, $second = null)
|
||||
* @method static \Illuminate\Database\Query\Builder rightJoin($table, $first, $operator = null, $second = null)
|
||||
* @method static \Illuminate\Database\Query\Builder rightJoinWhere($table, $first, $operator, $second)
|
||||
* @method static \Illuminate\Database\Query\Builder rightJoinSub($query, $as, $first, $operator = null, $second = null)
|
||||
* @method static \Illuminate\Database\Query\Builder crossJoin($table, $first = null, $operator = null, $second = null)
|
||||
* @method static \Illuminate\Database\Query\Builder crossJoinSub($query, $as)
|
||||
* @method static void mergeWheres($wheres, $bindings)
|
||||
* @method static array prepareValueAndOperator($value, $operator, $useDefault = false)
|
||||
* @method static \Illuminate\Database\Query\Builder whereColumn($first, $operator = null, $second = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereColumn($first, $operator = null, $second = null)
|
||||
* @method static \Illuminate\Database\Query\Builder whereRaw($sql, $bindings = [], $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereRaw($sql, $bindings = [])
|
||||
* @method static \Illuminate\Database\Query\Builder whereIn($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereIn($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder whereNotIn($column, $values, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereNotIn($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder whereIntegerInRaw($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereIntegerInRaw($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder whereIntegerNotInRaw($column, $values, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereIntegerNotInRaw($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder whereNull($columns, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereNull($column)
|
||||
* @method static \Illuminate\Database\Query\Builder whereNotNull($columns, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder whereBetween($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder whereBetweenColumns($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereBetween($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereBetweenColumns($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder whereNotBetween($column, $values, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder whereNotBetweenColumns($column, $values, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereNotBetween($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereNotBetweenColumns($column, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereNotNull($column)
|
||||
* @method static \Illuminate\Database\Query\Builder whereDate($column, $operator, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereDate($column, $operator, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder whereTime($column, $operator, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereTime($column, $operator, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder whereDay($column, $operator, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereDay($column, $operator, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder whereMonth($column, $operator, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereMonth($column, $operator, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder whereYear($column, $operator, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereYear($column, $operator, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder whereNested($callback, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder forNestedWhere()
|
||||
* @method static \Illuminate\Database\Query\Builder addNestedWhereQuery($query, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder whereExists($callback, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereExists($callback, $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder whereNotExists($callback, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereNotExists($callback)
|
||||
* @method static \Illuminate\Database\Query\Builder addWhereExistsQuery($query, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder whereRowValues($columns, $operator, $values, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereRowValues($columns, $operator, $values)
|
||||
* @method static \Illuminate\Database\Query\Builder whereJsonContains($column, $value, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereJsonContains($column, $value)
|
||||
* @method static \Illuminate\Database\Query\Builder whereJsonDoesntContain($column, $value, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereJsonDoesntContain($column, $value)
|
||||
* @method static \Illuminate\Database\Query\Builder whereJsonLength($column, $operator, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orWhereJsonLength($column, $operator, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder dynamicWhere($method, $parameters)
|
||||
* @method static \Illuminate\Database\Query\Builder groupBy(...$groups)
|
||||
* @method static \Illuminate\Database\Query\Builder groupByRaw($sql, $bindings = [])
|
||||
* @method static \Illuminate\Database\Query\Builder having($column, $operator = null, $value = null, $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orHaving($column, $operator = null, $value = null)
|
||||
* @method static \Illuminate\Database\Query\Builder havingBetween($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder havingRaw($sql, $bindings = [], $boolean = 'and')
|
||||
* @method static \Illuminate\Database\Query\Builder orHavingRaw($sql, $bindings = [])
|
||||
* @method static \Illuminate\Database\Query\Builder orderBy($column, $direction = 'asc')
|
||||
* @method static \Illuminate\Database\Query\Builder orderByDesc($column)
|
||||
* @method static \Illuminate\Database\Query\Builder inRandomOrder($seed = '')
|
||||
* @method static \Illuminate\Database\Query\Builder orderByRaw($sql, $bindings = [])
|
||||
* @method static \Illuminate\Database\Query\Builder skip($value)
|
||||
* @method static \Illuminate\Database\Query\Builder offset($value)
|
||||
* @method static \Illuminate\Database\Query\Builder take($value)
|
||||
* @method static \Illuminate\Database\Query\Builder limit($value)
|
||||
* @method static \Illuminate\Database\Query\Builder forPage($page, $perPage = 15)
|
||||
* @method static \Illuminate\Database\Query\Builder forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id')
|
||||
* @method static \Illuminate\Database\Query\Builder forPageAfterId($perPage = 15, $lastId = 0, $column = 'id')
|
||||
* @method static \Illuminate\Database\Query\Builder reorder($column = null, $direction = 'asc')
|
||||
* @method static \Illuminate\Database\Query\Builder union($query, $all = false)
|
||||
* @method static \Illuminate\Database\Query\Builder unionAll($query)
|
||||
* @method static \Illuminate\Database\Query\Builder lock($value = true)
|
||||
* @method static \Illuminate\Database\Query\Builder lockForUpdate()
|
||||
* @method static \Illuminate\Database\Query\Builder sharedLock()
|
||||
* @method static \Illuminate\Database\Query\Builder beforeQuery($callback)
|
||||
* @method static void applyBeforeQueryCallbacks()
|
||||
* @method static string toSql()
|
||||
* @method static int getCountForPagination($columns = [])
|
||||
* @method static string implode($column, $glue = '')
|
||||
* @method static bool exists()
|
||||
* @method static bool doesntExist()
|
||||
* @method static mixed existsOr($callback)
|
||||
* @method static mixed doesntExistOr($callback)
|
||||
* @method static int count($columns = '*')
|
||||
* @method static mixed min($column)
|
||||
* @method static mixed max($column)
|
||||
* @method static mixed sum($column)
|
||||
* @method static mixed avg($column)
|
||||
* @method static mixed average($column)
|
||||
* @method static mixed aggregate($function, $columns = [])
|
||||
* @method static float|int numericAggregate($function, $columns = [])
|
||||
* @method static bool insert($values)
|
||||
* @method static int insertOrIgnore($values)
|
||||
* @method static int insertGetId($values, $sequence = null)
|
||||
* @method static int insertUsing($columns, $query)
|
||||
* @method static bool updateOrInsert($attributes, $values = [])
|
||||
* @method static void truncate()
|
||||
* @method static \Illuminate\Database\Query\Expression raw($value)
|
||||
* @method static array getBindings()
|
||||
* @method static array getRawBindings()
|
||||
* @method static \Illuminate\Database\Query\Builder setBindings($bindings, $type = 'where')
|
||||
* @method static \Illuminate\Database\Query\Builder addBinding($value, $type = 'where')
|
||||
* @method static \Illuminate\Database\Query\Builder mergeBindings($query)
|
||||
* @method static array cleanBindings($bindings)
|
||||
* @method static \Illuminate\Database\Query\Processors\Processor getProcessor()
|
||||
* @method static \Illuminate\Database\Query\Grammars\Grammar getGrammar()
|
||||
* @method static \Illuminate\Database\Query\Builder useWritePdo()
|
||||
* @method static static cloneWithout($properties)
|
||||
* @method static static cloneWithoutBindings($except)
|
||||
* @method static \Illuminate\Database\Query\Builder dump()
|
||||
* @method static void dd()
|
||||
* @method static void macro($name, $macro)
|
||||
* @method static void mixin($mixin, $replace = true)
|
||||
* @method static mixed macroCall($method, $parameters)
|
||||
*/
|
||||
class Model extends BaseModel
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace support;
|
||||
|
||||
class Plugin
|
||||
{
|
||||
/**
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public static function install($event)
|
||||
{
|
||||
static::findHepler();
|
||||
$operation = $event->getOperation();
|
||||
$autoload = \method_exists($operation, 'getPackage') ? $operation->getPackage()->getAutoload() : $operation->getTargetPackage()->getAutoload();
|
||||
if (!isset($autoload['psr-4'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($autoload['psr-4'] as $namespace => $path) {
|
||||
$install_function = "\\{$namespace}Install::install";
|
||||
$plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN";
|
||||
if (\defined($plugin_const) && \is_callable($install_function)) {
|
||||
$install_function();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public static function update($event)
|
||||
{
|
||||
static::install($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public static function uninstall($event)
|
||||
{
|
||||
static::findHepler();
|
||||
$autoload = $event->getOperation()->getPackage()->getAutoload();
|
||||
if (!isset($autoload['psr-4'])) {
|
||||
return;
|
||||
}
|
||||
foreach ($autoload['psr-4'] as $namespace => $path) {
|
||||
$uninstall_function = "\\{$namespace}Install::uninstall";
|
||||
$plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN";
|
||||
if (defined($plugin_const) && is_callable($uninstall_function)) {
|
||||
$uninstall_function();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected static function findHepler()
|
||||
{
|
||||
// Plugin.php in vendor
|
||||
$file = __DIR__ . '/../../../../../support/helpers.php';
|
||||
if (\is_file($file)) {
|
||||
require_once $file;
|
||||
return;
|
||||
}
|
||||
// Plugin.php in webman
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Redis\Events\CommandExecuted;
|
||||
use Illuminate\Redis\RedisManager;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Worker;
|
||||
|
||||
|
||||
/**
|
||||
* Class Redis
|
||||
* @package support
|
||||
*
|
||||
* Strings methods
|
||||
* @method static int append($key, $value)
|
||||
* @method static int bitCount($key)
|
||||
* @method static int decr($key, $value = 1)
|
||||
* @method static int decrBy($key, $value)
|
||||
* @method static string|bool get($key)
|
||||
* @method static int getBit($key, $offset)
|
||||
* @method static string getRange($key, $start, $end)
|
||||
* @method static string getSet($key, $value)
|
||||
* @method static int incr($key, $value = 1)
|
||||
* @method static int incrBy($key, $value)
|
||||
* @method static float incrByFloat($key, $value)
|
||||
* @method static array mGet(array $keys)
|
||||
* @method static array getMultiple(array $keys)
|
||||
* @method static bool mSet($pairs)
|
||||
* @method static bool mSetNx($pairs)
|
||||
* @method static bool set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
||||
* @method static bool setBit($key, $offset, $value)
|
||||
* @method static bool setEx($key, $ttl, $value)
|
||||
* @method static bool pSetEx($key, $ttl, $value)
|
||||
* @method static bool setNx($key, $value)
|
||||
* @method static string setRange($key, $offset, $value)
|
||||
* @method static int strLen($key)
|
||||
* Keys methods
|
||||
* @method static int del(...$keys)
|
||||
* @method static int unlink(...$keys)
|
||||
* @method static false|string dump($key)
|
||||
* @method static int exists(...$keys)
|
||||
* @method static bool expire($key, $ttl)
|
||||
* @method static bool pexpire($key, $ttl)
|
||||
* @method static bool expireAt($key, $timestamp)
|
||||
* @method static bool pexpireAt($key, $timestamp)
|
||||
* @method static array keys($pattern)
|
||||
* @method static bool|array scan($it)
|
||||
* @method static void migrate($host, $port, $keys, $dbIndex, $timeout, $copy = false, $replace = false)
|
||||
* @method static bool select($dbIndex)
|
||||
* @method static bool move($key, $dbIndex)
|
||||
* @method static string|int|bool object($information, $key)
|
||||
* @method static bool persist($key)
|
||||
* @method static string randomKey()
|
||||
* @method static bool rename($srcKey, $dstKey)
|
||||
* @method static bool renameNx($srcKey, $dstKey)
|
||||
* @method static string type($key)
|
||||
* @method static int|array sort($key, $options = [])
|
||||
* @method static int ttl($key)
|
||||
* @method static int pttl($key)
|
||||
* @method static void restore($key, $ttl, $value)
|
||||
* Hashes methods
|
||||
* @method static false|int hSet($key, $hashKey, $value)
|
||||
* @method static bool hSetNx($key, $hashKey, $value)
|
||||
* @method static false|string hGet($key, $hashKey)
|
||||
* @method static false|int hLen($key)
|
||||
* @method static false|int hDel($key, ...$hashKeys)
|
||||
* @method static array hKeys($key)
|
||||
* @method static array hVals($key)
|
||||
* @method static array hGetAll($key)
|
||||
* @method static bool hExists($key, $hashKey)
|
||||
* @method static int hIncrBy($key, $hashKey, $value)
|
||||
* @method static float hIncrByFloat($key, $hashKey, $value)
|
||||
* @method static bool hMSet($key, $members)
|
||||
* @method static array hMGet($key, $memberKeys)
|
||||
* @method static array hScan($key, $iterator, $pattern = '', $count = 0)
|
||||
* @method static int hStrLen($key, $hashKey)
|
||||
* Lists methods
|
||||
* @method static array blPop($keys, $timeout)
|
||||
* @method static array brPop($keys, $timeout)
|
||||
* @method static false|string bRPopLPush($srcKey, $dstKey, $timeout)
|
||||
* @method static false|string lIndex($key, $index)
|
||||
* @method static int lInsert($key, $position, $pivot, $value)
|
||||
* @method static false|string lPop($key)
|
||||
* @method static false|int lPush($key, ...$entries)
|
||||
* @method static false|int lPushx($key, $value)
|
||||
* @method static array lRange($key, $start, $end)
|
||||
* @method static false|int lRem($key, $count, $value)
|
||||
* @method static bool lSet($key, $index, $value)
|
||||
* @method static false|array lTrim($key, $start, $end)
|
||||
* @method static false|string rPop($key)
|
||||
* @method static false|string rPopLPush($srcKey, $dstKey)
|
||||
* @method static false|int rPush($key, ...$entries)
|
||||
* @method static false|int rPushX($key, $value)
|
||||
* @method static false|int lLen($key)
|
||||
* Sets methods
|
||||
* @method static int sAdd($key, $value)
|
||||
* @method static int sCard($key)
|
||||
* @method static array sDiff($keys)
|
||||
* @method static false|int sDiffStore($dst, $keys)
|
||||
* @method static false|array sInter($keys)
|
||||
* @method static false|int sInterStore($dst, $keys)
|
||||
* @method static bool sIsMember($key, $member)
|
||||
* @method static array sMembers($key)
|
||||
* @method static bool sMove($src, $dst, $member)
|
||||
* @method static false|string|array sPop($key, $count = 0)
|
||||
* @method static false|string|array sRandMember($key, $count = 0)
|
||||
* @method static int sRem($key, ...$members)
|
||||
* @method static array sUnion(...$keys)
|
||||
* @method static false|int sUnionStore($dst, ...$keys)
|
||||
* @method static false|array sScan($key, $iterator, $pattern = '', $count = 0)
|
||||
* Sorted sets methods
|
||||
* @method static array bzPopMin($keys, $timeout)
|
||||
* @method static array bzPopMax($keys, $timeout)
|
||||
* @method static int zAdd($key, $score, $value)
|
||||
* @method static int zCard($key)
|
||||
* @method static int zCount($key, $start, $end)
|
||||
* @method static double zIncrBy($key, $value, $member)
|
||||
* @method static int zinterstore($keyOutput, $arrayZSetKeys, $arrayWeights = [], $aggregateFunction = '')
|
||||
* @method static array zPopMin($key, $count)
|
||||
* @method static array zPopMax($key, $count)
|
||||
* @method static array zRange($key, $start, $end, $withScores = false)
|
||||
* @method static array zRangeByScore($key, $start, $end, $options = [])
|
||||
* @method static array zRevRangeByScore($key, $start, $end, $options = [])
|
||||
* @method static array zRangeByLex($key, $min, $max, $offset = 0, $limit = 0)
|
||||
* @method static int zRank($key, $member)
|
||||
* @method static int zRevRank($key, $member)
|
||||
* @method static int zRem($key, ...$members)
|
||||
* @method static int zRemRangeByRank($key, $start, $end)
|
||||
* @method static int zRemRangeByScore($key, $start, $end)
|
||||
* @method static array zRevRange($key, $start, $end, $withScores = false)
|
||||
* @method static double zScore($key, $member)
|
||||
* @method static int zunionstore($keyOutput, $arrayZSetKeys, $arrayWeights = [], $aggregateFunction = '')
|
||||
* @method static false|array zScan($key, $iterator, $pattern = '', $count = 0)
|
||||
* HyperLogLogs methods
|
||||
* @method static int pfAdd($key, $values)
|
||||
* @method static int pfCount($keys)
|
||||
* @method static bool pfMerge($dstKey, $srcKeys)
|
||||
* Geocoding methods
|
||||
* @method static int geoAdd($key, $longitude, $latitude, $member, ...$items)
|
||||
* @method static array geoHash($key, ...$members)
|
||||
* @method static array geoPos($key, ...$members)
|
||||
* @method static double geoDist($key, $members, $unit = '')
|
||||
* @method static int|array geoRadius($key, $longitude, $latitude, $radius, $unit, $options = [])
|
||||
* @method static array geoRadiusByMember($key, $member, $radius, $units, $options = [])
|
||||
* Streams methods
|
||||
* @method static int xAck($stream, $group, $arrMessages)
|
||||
* @method static string xAdd($strKey, $strId, $arrMessage, $iMaxLen = 0, $booApproximate = false)
|
||||
* @method static array xClaim($strKey, $strGroup, $strConsumer, $minIdleTime, $arrIds, $arrOptions = [])
|
||||
* @method static int xDel($strKey, $arrIds)
|
||||
* @method static mixed xGroup($command, $strKey, $strGroup, $strMsgId, $booMKStream = null)
|
||||
* @method static mixed xInfo($command, $strStream, $strGroup = null)
|
||||
* @method static int xLen($stream)
|
||||
* @method static array xPending($strStream, $strGroup, $strStart = 0, $strEnd = 0, $iCount = 0, $strConsumer = null)
|
||||
* @method static array xRange($strStream, $strStart, $strEnd, $iCount = 0)
|
||||
* @method static array xRead($arrStreams, $iCount = 0, $iBlock = null)
|
||||
* @method static array xReadGroup($strGroup, $strConsumer, $arrStreams, $iCount = 0, $iBlock = null)
|
||||
* @method static array xRevRange($strStream, $strEnd, $strStart, $iCount = 0)
|
||||
* @method static int xTrim($strStream, $iMaxLen, $booApproximate = null)
|
||||
* Pub/sub methods
|
||||
* @method static mixed pSubscribe($patterns, $callback)
|
||||
* @method static mixed publish($channel, $message)
|
||||
* @method static mixed subscribe($channels, $callback)
|
||||
* @method static mixed pubSub($keyword, $argument = null)
|
||||
* Generic methods
|
||||
* @method static mixed rawCommand(...$commandAndArgs)
|
||||
* Transactions methods
|
||||
* @method static \Redis multi()
|
||||
* @method static mixed exec()
|
||||
* @method static mixed discard()
|
||||
* @method static mixed watch($keys)
|
||||
* @method static mixed unwatch($keys)
|
||||
* Scripting methods
|
||||
* @method static mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
||||
* @method static mixed evalSha($scriptSha, $numkeys, ...$arguments)
|
||||
* @method static mixed script($command, ...$scripts)
|
||||
* @method static mixed client(...$args)
|
||||
* @method static null|string getLastError()
|
||||
* @method static bool clearLastError()
|
||||
* @method static mixed _prefix($value)
|
||||
* @method static mixed _serialize($value)
|
||||
* @method static mixed _unserialize($value)
|
||||
* Introspection methods
|
||||
* @method static bool isConnected()
|
||||
* @method static mixed getHost()
|
||||
* @method static mixed getPort()
|
||||
* @method static false|int getDbNum()
|
||||
* @method static false|double getTimeout()
|
||||
* @method static mixed getReadTimeout()
|
||||
* @method static mixed getPersistentID()
|
||||
* @method static mixed getAuth()
|
||||
*/
|
||||
class Redis
|
||||
{
|
||||
|
||||
/**
|
||||
* @var RedisManager
|
||||
*/
|
||||
protected static $_instance = null;
|
||||
|
||||
/**
|
||||
* need to install phpredis extension
|
||||
*/
|
||||
const PHPREDIS_CLIENT = 'phpredis';
|
||||
|
||||
/**
|
||||
* need to install the 'predis/predis' packgage.
|
||||
* cmd: composer install predis/predis
|
||||
*/
|
||||
const PREDIS_CLIENT = 'predis';
|
||||
|
||||
/**
|
||||
* Support client collection
|
||||
*/
|
||||
static $_allowClient = [
|
||||
self::PHPREDIS_CLIENT,
|
||||
self::PREDIS_CLIENT
|
||||
];
|
||||
|
||||
/**
|
||||
* @return RedisManager
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
if (!static::$_instance) {
|
||||
$config = \config('redis');
|
||||
$client = $config['client'] ?? self::PHPREDIS_CLIENT;
|
||||
|
||||
if (!\in_array($client, static::$_allowClient)) {
|
||||
$client = self::PHPREDIS_CLIENT;
|
||||
}
|
||||
|
||||
static::$_instance = new RedisManager('', $client, $config);
|
||||
}
|
||||
return static::$_instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return \Illuminate\Redis\Connections\Connection
|
||||
*/
|
||||
public static function connection(string $name = 'default')
|
||||
{
|
||||
static $timers = [];
|
||||
$connection = static::instance()->connection($name);
|
||||
if (!isset($timers[$name])) {
|
||||
$timers[$name] = Worker::getAllWorkers() ? Timer::add(55, function () use ($connection) {
|
||||
$connection->get('ping');
|
||||
}) : 1;
|
||||
if (\class_exists(Dispatcher::class)) {
|
||||
$connection->setEventDispatcher(new Dispatcher());
|
||||
}
|
||||
}
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
return static::connection('default')->{$name}(... $arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
/**
|
||||
* Class Request
|
||||
* @package support
|
||||
*/
|
||||
class Request extends \Webman\Http\Request
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
* @package support
|
||||
*/
|
||||
class Response extends \Webman\Http\Response
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
use Symfony\Component\Translation\Translator;
|
||||
use Webman\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Translation
|
||||
* @package support
|
||||
* @method static string trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
|
||||
* @method static void setLocale(string $locale)
|
||||
* @method static string getLocale()
|
||||
*/
|
||||
class Translation
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Translator[]
|
||||
*/
|
||||
protected static $_instance = [];
|
||||
|
||||
/**
|
||||
* @return Translator
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public static function instance(string $plugin = '')
|
||||
{
|
||||
if (!isset(static::$_instance[$plugin])) {
|
||||
$config = \config($plugin ? "plugin.$plugin.translation" : 'translation', []);
|
||||
// Phar support. Compatible with the 'realpath' function in the phar file.
|
||||
if (!$translations_path = \get_realpath($config['path'])) {
|
||||
throw new NotFoundException("File {$config['path']} not found");
|
||||
}
|
||||
|
||||
static::$_instance[$plugin] = $translator = new Translator($config['locale']);
|
||||
$translator->setFallbackLocales($config['fallback_locale']);
|
||||
|
||||
$classes = [
|
||||
'Symfony\Component\Translation\Loader\PhpFileLoader' => [
|
||||
'extension' => '.php',
|
||||
'format' => 'phpfile'
|
||||
],
|
||||
'Symfony\Component\Translation\Loader\PoFileLoader' => [
|
||||
'extension' => '.po',
|
||||
'format' => 'pofile'
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($classes as $class => $opts) {
|
||||
$translator->addLoader($opts['format'], new $class);
|
||||
foreach (\glob($translations_path . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . '*' . $opts['extension']) as $file) {
|
||||
$domain = \basename($file, $opts['extension']);
|
||||
$dir_name = \pathinfo($file, PATHINFO_DIRNAME);
|
||||
$locale = \substr(strrchr($dir_name, DIRECTORY_SEPARATOR), 1);
|
||||
if ($domain && $locale) {
|
||||
$translator->addResource($opts['format'], $file, $locale, $domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return static::$_instance[$plugin];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments)
|
||||
{
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
return static::instance($plugin)->{$name}(... $arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
class View
|
||||
{
|
||||
/**
|
||||
* @param mixed $name
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public static function assign($name, $value = null)
|
||||
{
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
$handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler');
|
||||
$handler::assign($name, $value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\bootstrap;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Jenssegers\Mongodb\Connection as MongodbConnection;
|
||||
use support\Db;
|
||||
use Throwable;
|
||||
use Webman\Bootstrap;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Laravel
|
||||
* @package support\Bootstrap
|
||||
*/
|
||||
class LaravelDb implements Bootstrap
|
||||
{
|
||||
/**
|
||||
* @param Worker $worker
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function start($worker)
|
||||
{
|
||||
if (!class_exists(Capsule::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = \config('database', []);
|
||||
$connections = $config['connections'] ?? [];
|
||||
if (!$connections) {
|
||||
return;
|
||||
}
|
||||
|
||||
$capsule = new Capsule;
|
||||
|
||||
$capsule->getDatabaseManager()->extend('mongodb', function ($config, $name) {
|
||||
$config['name'] = $name;
|
||||
return new MongodbConnection($config);
|
||||
});
|
||||
|
||||
$default = $config['default'] ?? false;
|
||||
if ($default) {
|
||||
$default_config = $connections[$config['default']];
|
||||
$capsule->addConnection($default_config);
|
||||
}
|
||||
|
||||
foreach ($connections as $name => $config) {
|
||||
$capsule->addConnection($config, $name);
|
||||
}
|
||||
|
||||
if (\class_exists(Dispatcher::class)) {
|
||||
$capsule->setEventDispatcher(new Dispatcher(new Container));
|
||||
}
|
||||
|
||||
$capsule->setAsGlobal();
|
||||
|
||||
$capsule->bootEloquent();
|
||||
|
||||
// Heartbeat
|
||||
if ($worker) {
|
||||
Timer::add(55, function () use ($default, $connections, $capsule) {
|
||||
foreach ($capsule->getDatabaseManager()->getConnections() as $connection) {
|
||||
/* @var \Illuminate\Database\MySqlConnection $connection **/
|
||||
if ($connection->getConfig('driver') == 'mysql') {
|
||||
try {
|
||||
$connection->select('select 1');
|
||||
} catch (Throwable $e) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Paginator
|
||||
if (class_exists(Paginator::class)) {
|
||||
if (method_exists(Paginator::class, 'queryStringResolver')) {
|
||||
Paginator::queryStringResolver(function () {
|
||||
$request = request();
|
||||
return $request ? $request->queryString() : null;
|
||||
});
|
||||
}
|
||||
Paginator::currentPathResolver(function () {
|
||||
$request = request();
|
||||
return $request ? $request->path(): '/';
|
||||
});
|
||||
Paginator::currentPageResolver(function ($page_name = 'page') {
|
||||
$request = request();
|
||||
if (!$request) {
|
||||
return 1;
|
||||
}
|
||||
$page = (int)($request->input($page_name, 1));
|
||||
return $page > 0 ? $page : 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\bootstrap;
|
||||
|
||||
use Webman\Bootstrap;
|
||||
use Workerman\Protocols\Http;
|
||||
use Workerman\Protocols\Http\Session as SessionBase;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Session
|
||||
* @package support
|
||||
*/
|
||||
class Session implements Bootstrap
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Worker $worker
|
||||
* @return void
|
||||
*/
|
||||
public static function start($worker)
|
||||
{
|
||||
$config = \config('session');
|
||||
if (\property_exists(SessionBase::class, 'name')) {
|
||||
SessionBase::$name = $config['session_name'];
|
||||
} else {
|
||||
Http::sessionName($config['session_name']);
|
||||
}
|
||||
SessionBase::handlerClass($config['handler'], $config['config'][$config['type']]);
|
||||
$map = [
|
||||
'auto_update_timestamp' => 'autoUpdateTimestamp',
|
||||
'cookie_lifetime' => 'cookieLifetime',
|
||||
'gc_probability' => 'gcProbability',
|
||||
'cookie_path' => 'cookiePath',
|
||||
'http_only' => 'httpOnly',
|
||||
'same_site' => 'sameSite',
|
||||
'lifetime' => 'lifetime',
|
||||
'domain' => 'domain',
|
||||
'secure' => 'secure',
|
||||
];
|
||||
foreach ($map as $key => $name) {
|
||||
if (isset($config[$key]) && \property_exists(SessionBase::class, $name)) {
|
||||
SessionBase::${$name} = $config[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\exception;
|
||||
|
||||
use Exception;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
/**
|
||||
* Class BusinessException
|
||||
* @package support\exception
|
||||
*/
|
||||
class BusinessException extends Exception
|
||||
{
|
||||
public function render(Request $request): ?Response
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
$code = $this->getCode();
|
||||
$json = ['code' => $code ? $code : 500, 'msg' => $this->getMessage()];
|
||||
return new Response(200, ['Content-Type' => 'application/json'],
|
||||
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
return new Response(200, [], $this->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\exception;
|
||||
|
||||
use Throwable;
|
||||
use Webman\Exception\ExceptionHandler;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* Class Handler
|
||||
* @package support\exception
|
||||
*/
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
public $dontReport = [
|
||||
BusinessException::class,
|
||||
];
|
||||
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
public function render(Request $request, Throwable $exception): Response
|
||||
{
|
||||
if(($exception instanceof BusinessException) && ($response = $exception->render($request)))
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\view;
|
||||
|
||||
use Jenssegers\Blade\Blade as BladeView;
|
||||
use Webman\View;
|
||||
|
||||
/**
|
||||
* Class Blade
|
||||
* composer require jenssegers/blade
|
||||
* @package support\view
|
||||
*/
|
||||
class Blade implements View
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_vars = [];
|
||||
|
||||
/**
|
||||
* @param string|array $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assign($name, $value = null)
|
||||
{
|
||||
static::$_vars = \array_merge(static::$_vars, \is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return string
|
||||
*/
|
||||
public static function render(string $template, array $vars, string $app = null)
|
||||
{
|
||||
static $views = [];
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
$app = $app === null ? $request->app : $app;
|
||||
$config_prefix = $plugin ? "plugin.$plugin." : '';
|
||||
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
|
||||
$key = "{$plugin}-{$request->app}";
|
||||
if (!isset($views[$key])) {
|
||||
$view_path = $app === '' ? "$base_view_path/view" : "$base_view_path/$app/view";
|
||||
$views[$key] = new BladeView($view_path, \runtime_path() . '/views');
|
||||
$extension = \config("{$config_prefix}view.extension");
|
||||
if ($extension) {
|
||||
$extension($views[$key]);
|
||||
}
|
||||
}
|
||||
$vars = \array_merge(static::$_vars, $vars);
|
||||
$content = $views[$key]->render($template, $vars);
|
||||
static::$_vars = [];
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\view;
|
||||
|
||||
use Webman\View;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class Raw
|
||||
* @package support\view
|
||||
*/
|
||||
class Raw implements View
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_vars = [];
|
||||
|
||||
/**
|
||||
* @param string|array $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assign($name, $value = null)
|
||||
{
|
||||
static::$_vars = \array_merge(static::$_vars, \is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return false|string
|
||||
*/
|
||||
public static function render(string $template, array $vars, string $app = null)
|
||||
{
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
$config_prefix = $plugin ? "plugin.$plugin." : '';
|
||||
$view_suffix = \config("{$config_prefix}view.options.view_suffix", 'html');
|
||||
$app = $app === null ? $request->app : $app;
|
||||
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
|
||||
$__template_path__ = $app === '' ? "$base_view_path/view/$template.$view_suffix" : "$base_view_path/$app/view/$template.$view_suffix";
|
||||
|
||||
\extract(static::$_vars);
|
||||
\extract($vars);
|
||||
\ob_start();
|
||||
// Try to include php file.
|
||||
try {
|
||||
include $__template_path__;
|
||||
} catch (Throwable $e) {
|
||||
static::$_vars = [];
|
||||
\ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
static::$_vars = [];
|
||||
return \ob_get_clean();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\view;
|
||||
|
||||
use think\Template;
|
||||
use Webman\View;
|
||||
|
||||
/**
|
||||
* Class Blade
|
||||
* @package support\view
|
||||
*/
|
||||
class ThinkPHP implements View
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_vars = [];
|
||||
|
||||
/**
|
||||
* @param string|array $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assign($name, $value = null)
|
||||
{
|
||||
static::$_vars = \array_merge(static::$_vars, \is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return false|string
|
||||
*/
|
||||
public static function render(string $template, array $vars, string $app = null)
|
||||
{
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
$app = $app === null ? $request->app : $app;
|
||||
$config_prefix = $plugin ? "plugin.$plugin." : '';
|
||||
$view_suffix = \config("{$config_prefix}view.options.view_suffix", 'html');
|
||||
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
|
||||
$view_path = $app === '' ? "$base_view_path/view/" : "$base_view_path/$app/view/";
|
||||
$default_options = [
|
||||
'view_path' => $view_path,
|
||||
'cache_path' => \runtime_path() . '/views/',
|
||||
'view_suffix' => $view_suffix
|
||||
];
|
||||
$options = $default_options + \config("{$config_prefix}view.options", []);
|
||||
$views = new Template($options);
|
||||
\ob_start();
|
||||
$vars = \array_merge(static::$_vars, $vars);
|
||||
$views->fetch($template, $vars);
|
||||
$content = \ob_get_clean();
|
||||
static::$_vars = [];
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support\view;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Webman\View;
|
||||
|
||||
/**
|
||||
* Class Blade
|
||||
* @package support\view
|
||||
*/
|
||||
class Twig implements View
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $_vars = [];
|
||||
|
||||
/**
|
||||
* @param string|array $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function assign($name, $value = null)
|
||||
{
|
||||
static::$_vars = \array_merge(static::$_vars, \is_array($name) ? $name : [$name => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return string
|
||||
*/
|
||||
public static function render(string $template, array $vars, string $app = null)
|
||||
{
|
||||
static $views = [];
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
$app = $app === null ? $request->app : $app;
|
||||
$config_prefix = $plugin ? "plugin.$plugin." : '';
|
||||
$view_suffix = \config("{$config_prefix}view.options.view_suffix", 'html');
|
||||
$key = "{$plugin}-{$request->app}";
|
||||
if (!isset($views[$key])) {
|
||||
$base_view_path = $plugin ? \base_path() . "/plugin/$plugin/app" : \app_path();
|
||||
$view_path = $app === '' ? "$base_view_path/view/" : "$base_view_path/$app/view/";
|
||||
$views[$key] = new Environment(new FilesystemLoader($view_path), \config("{$config_prefix}view.options", []));
|
||||
$extension = \config("{$config_prefix}view.extension");
|
||||
if ($extension) {
|
||||
$extension($views[$key]);
|
||||
}
|
||||
}
|
||||
$vars = \array_merge(static::$_vars, $vars);
|
||||
$content = $views[$key]->render("$template.$view_suffix", $vars);
|
||||
static::$_vars = [];
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: workerman
|
||||
patreon: walkor
|
||||
@@ -0,0 +1,6 @@
|
||||
logs
|
||||
.buildpath
|
||||
.project
|
||||
.settings
|
||||
.idea
|
||||
.DS_Store
|
||||
+69
@@ -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');
|
||||
@@ -0,0 +1,376 @@
|
||||
<?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\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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 array
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
<?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()
|
||||
{
|
||||
foreach ($this->_allEvents as $event) {
|
||||
$event->stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
+215
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
interface EventInterface
|
||||
{
|
||||
/**
|
||||
* Read event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_READ = 1;
|
||||
|
||||
/**
|
||||
* Write event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_WRITE = 2;
|
||||
|
||||
/**
|
||||
* Except event
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_EXCEPT = 3;
|
||||
|
||||
/**
|
||||
* Signal event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_SIGNAL = 4;
|
||||
|
||||
/**
|
||||
* Timer event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_TIMER = 8;
|
||||
|
||||
/**
|
||||
* Timer once event.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const EV_TIMER_ONCE = 16;
|
||||
|
||||
/**
|
||||
* Add event listener to event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @param callable $func
|
||||
* @param array $args
|
||||
* @return bool
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array());
|
||||
|
||||
/**
|
||||
* Remove event listener from event loop.
|
||||
*
|
||||
* @param mixed $fd
|
||||
* @param int $flag
|
||||
* @return bool
|
||||
*/
|
||||
public function del($fd, $flag);
|
||||
|
||||
/**
|
||||
* Remove all timers.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer();
|
||||
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop();
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function destroy();
|
||||
|
||||
/**
|
||||
* Get Timer count.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimerCount();
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events;
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* libevent eventloop
|
||||
*/
|
||||
class Libevent implements EventInterface
|
||||
{
|
||||
/**
|
||||
* Event base.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $_eventBase = null;
|
||||
|
||||
/**
|
||||
* All listeners for read/write event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_allEvents = array();
|
||||
|
||||
/**
|
||||
* Event listeners of signal.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventSignal = array();
|
||||
|
||||
/**
|
||||
* All timer event listeners.
|
||||
* [func, args, event, flag, time_interval]
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_eventTimer = array();
|
||||
|
||||
/**
|
||||
* construct
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventBase = \event_base_new();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($fd, $flag, $func, $args = array())
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = \EV_SIGNAL | \EV_PERSIST;
|
||||
$this->_eventSignal[$fd_key] = \event_new();
|
||||
if (!\event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) {
|
||||
return false;
|
||||
}
|
||||
if (!\event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
if (!\event_add($this->_eventSignal[$fd_key])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
$event = \event_new();
|
||||
$timer_id = (int)$event;
|
||||
if (!\event_set($event, 0, \EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\event_base_set($event, $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time_interval = $fd * 1000000;
|
||||
if (!\event_add($event, $time_interval)) {
|
||||
return false;
|
||||
}
|
||||
$this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval);
|
||||
return $timer_id;
|
||||
|
||||
default :
|
||||
$fd_key = (int)$fd;
|
||||
$real_flag = $flag === self::EV_READ ? \EV_READ | \EV_PERSIST : \EV_WRITE | \EV_PERSIST;
|
||||
|
||||
$event = \event_new();
|
||||
|
||||
if (!\event_set($event, $fd, $real_flag, $func, null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\event_base_set($event, $this->_eventBase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\event_add($event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_allEvents[$fd_key][$flag] = $event;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag) {
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
\event_del($this->_allEvents[$fd_key][$flag]);
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
\event_del($this->_eventSignal[$fd_key]);
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
// 这里 fd 为timerid
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
\event_del($this->_eventTimer[$fd][2]);
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer callback.
|
||||
*
|
||||
* @param mixed $_null1
|
||||
* @param int $_null2
|
||||
* @param mixed $timer_id
|
||||
*/
|
||||
protected function timerCallback($_null1, $_null2, $timer_id)
|
||||
{
|
||||
if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) {
|
||||
\event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]);
|
||||
}
|
||||
try {
|
||||
\call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) {
|
||||
$this->del($timer_id, self::EV_TIMER_ONCE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
foreach ($this->_eventTimer as $task_data) {
|
||||
\event_del($task_data[2]);
|
||||
}
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
\event_base_loop($this->_eventBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
foreach ($this->_eventSignal as $event) {
|
||||
\event_del($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timer count.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
|
||||
+264
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Events\React;
|
||||
|
||||
/**
|
||||
* Class StreamSelectLoop
|
||||
* @package Workerman\Events\React
|
||||
*/
|
||||
class StreamSelectLoop extends Base
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->_eventLoop = new \React\EventLoop\StreamSelectLoop();
|
||||
}
|
||||
}
|
||||
+357
@@ -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);
|
||||
}
|
||||
}
|
||||
+230
@@ -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
@@ -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, 1, $repeat, function($watcher)use($param){
|
||||
call_user_func_array([$this, 'timerCallback'], [$param]);
|
||||
});
|
||||
$this->_eventTimer[self::$_timerId] = $timerWatcher;
|
||||
return self::$_timerId++;
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
$ioCallback = function($watcher, $status, $events, $fd)use($func){
|
||||
try {
|
||||
\call_user_func($func, $fd);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
};
|
||||
$ioWatcher = \uv_poll_init($this->_eventLoop, $fd);
|
||||
$real_flag = $flag === self::EV_READ ? \Uv::READABLE : \Uv::WRITABLE;
|
||||
\uv_poll_start($ioWatcher, $real_flag, $ioCallback);
|
||||
$this->_allEvents[$fd_key][$flag] = $ioWatcher;
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a timer
|
||||
*
|
||||
* @param resource $fd
|
||||
* @param int $flag
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function del($fd, $flag)
|
||||
{
|
||||
switch ($flag)
|
||||
{
|
||||
case self::EV_READ:
|
||||
case self::EV_WRITE:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_allEvents[$fd_key][$flag])) {
|
||||
$watcher = $this->_allEvents[$fd_key][$flag];
|
||||
\uv_is_active($watcher) && \uv_poll_stop($watcher);
|
||||
unset($this->_allEvents[$fd_key][$flag]);
|
||||
}
|
||||
if (empty($this->_allEvents[$fd_key])) {
|
||||
unset($this->_allEvents[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_SIGNAL:
|
||||
$fd_key = (int)$fd;
|
||||
if (isset($this->_eventSignal[$fd_key])) {
|
||||
$watcher = $this->_eventSignal[$fd_key];
|
||||
\uv_is_active($watcher) && \uv_signal_stop($watcher);
|
||||
unset($this->_eventSignal[$fd_key]);
|
||||
}
|
||||
break;
|
||||
case self::EV_TIMER:
|
||||
case self::EV_TIMER_ONCE:
|
||||
if (isset($this->_eventTimer[$fd])) {
|
||||
$watcher = $this->_eventTimer[$fd];
|
||||
\uv_is_active($watcher) && \uv_timer_stop($watcher);
|
||||
unset($this->_eventTimer[$fd]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Timer callback
|
||||
*
|
||||
* @param array $input
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function timerCallback($input)
|
||||
{
|
||||
if(!is_array($input)) return;
|
||||
|
||||
$timer_id = $input[4];
|
||||
|
||||
if ($input[2] === self::EV_TIMER_ONCE)
|
||||
{
|
||||
$watcher = $this->_eventTimer[$timer_id];
|
||||
\uv_is_active($watcher) && \uv_timer_stop($watcher);
|
||||
unset($this->_eventTimer[$timer_id]);
|
||||
}
|
||||
|
||||
try {
|
||||
\call_user_func_array($input[0], $input[1]);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove all timers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAllTimer()
|
||||
{
|
||||
if(!is_array($this->_eventTimer)) return;
|
||||
|
||||
foreach($this->_eventTimer as $watcher)
|
||||
{
|
||||
\uv_is_active($watcher) && \uv_timer_stop($watcher);
|
||||
}
|
||||
|
||||
$this->_eventTimer = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start loop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loop()
|
||||
{
|
||||
\Uv_run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy loop
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
!empty($this->_eventLoop) && \uv_loop_delete($this->_eventLoop);
|
||||
$this->_allEvents = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get timer count
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTimerCount()
|
||||
{
|
||||
return \count($this->_eventTimer);
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*
|
||||
* @link http://www.workerman.net/
|
||||
*/
|
||||
|
||||
// Pcre.jit is not stable, temporarily disabled.
|
||||
ini_set('pcre.jit', 0);
|
||||
|
||||
// For onError callback.
|
||||
const WORKERMAN_CONNECT_FAIL = 1;
|
||||
// For onError callback.
|
||||
const WORKERMAN_SEND_FAIL = 2;
|
||||
|
||||
// Define OS Type
|
||||
const OS_TYPE_LINUX = 'linux';
|
||||
const OS_TYPE_WINDOWS = 'windows';
|
||||
|
||||
// Compatible with php7
|
||||
if (!class_exists('Error')) {
|
||||
class Error extends Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!interface_exists('SessionHandlerInterface')) {
|
||||
interface SessionHandlerInterface {
|
||||
public function close();
|
||||
public function destroy($session_id);
|
||||
public function gc($maxlifetime);
|
||||
public function open($save_path ,$session_name);
|
||||
public function read($session_id);
|
||||
public function write($session_id , $session_data);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Lib;
|
||||
|
||||
/**
|
||||
* Do not use Workerman\Lib\Timer.
|
||||
* Please use Workerman\Timer.
|
||||
* This class is only used for compatibility with workerman 3.*
|
||||
* @package Workerman\Lib
|
||||
*/
|
||||
class Timer extends \Workerman\Timer {}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2009-2015 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/workerman/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\TcpConnection;
|
||||
|
||||
/**
|
||||
* Frame Protocol.
|
||||
*/
|
||||
class Frame
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function input($buffer, TcpConnection $connection)
|
||||
{
|
||||
if (\strlen($buffer) < 4) {
|
||||
return 0;
|
||||
}
|
||||
$unpack_data = \unpack('Ntotal_length', $buffer);
|
||||
return $unpack_data['total_length'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function decode($buffer)
|
||||
{
|
||||
return \substr($buffer, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($buffer)
|
||||
{
|
||||
$total_length = 4 + \strlen($buffer);
|
||||
return \pack('N', $total_length) . $buffer;
|
||||
}
|
||||
}
|
||||
+323
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* 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 && $pos = \strpos($host, ':')) {
|
||||
return \substr($host, 0, $pos);
|
||||
}
|
||||
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 = '';
|
||||
$size = \strlen($boundary_value);
|
||||
$tmp_upload_dir = HTTP::uploadTmpDir();
|
||||
if (!$tmp_upload_dir) {
|
||||
$error = UPLOAD_ERR_NO_TMP_DIR;
|
||||
} else if ($boundary_value === '') {
|
||||
$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' => $match[2],
|
||||
'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;
|
||||
}
|
||||
|
||||
/**
|
||||
* __destruct.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->_data['files'])) {
|
||||
\clearstatcache();
|
||||
\array_walk_recursive($this->_data['files'], function($value, $key){
|
||||
if ($key === 'tmp_name') {
|
||||
if (\is_file($value)) {
|
||||
\unlink($value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 bool $same_site
|
||||
* @return $this
|
||||
*/
|
||||
public function cookie($name, $value = '', $max_age = null, $path = '', $domain = '', $secure = false, $http_only = false, $same_site = false)
|
||||
{
|
||||
$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 "$head\r\n".dechex($body_len)."\r\n{$this->_body}\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();
|
||||
@@ -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['data'])) {
|
||||
$buffer .= 'data: ' . \str_replace("\n", "\ndata: ", $data['data']) . "\n\n";
|
||||
}
|
||||
if (isset($data['id'])) {
|
||||
$buffer .= "id: {$data['id']}\n";
|
||||
}
|
||||
if (isset($data['retry'])) {
|
||||
$buffer .= "retry: {$data['retry']}\n";
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* __destruct.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$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();
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols\Http\Session;
|
||||
|
||||
use Workerman\Protocols\Http\Session;
|
||||
|
||||
/**
|
||||
* Class FileSessionHandler
|
||||
* @package Workerman\Protocols\Http\Session
|
||||
*/
|
||||
class FileSessionHandler implements SessionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Session save path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_sessionSavePath = null;
|
||||
|
||||
/**
|
||||
* Session file prefix.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $_sessionFilePrefix = 'session_';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
$save_path = @\session_save_path();
|
||||
if (!$save_path || \strpos($save_path, 'tcp://') === 0) {
|
||||
$save_path = \sys_get_temp_dir();
|
||||
}
|
||||
static::sessionSavePath($save_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* FileSessionHandler constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct($config = array()) {
|
||||
if (isset($config['save_path'])) {
|
||||
static::sessionSavePath($config['save_path']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($save_path, $name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($session_id)
|
||||
{
|
||||
$session_file = static::sessionFile($session_id);
|
||||
\clearstatcache();
|
||||
if (\is_file($session_file)) {
|
||||
if (\time() - \filemtime($session_file) > Session::$lifetime) {
|
||||
\unlink($session_file);
|
||||
return '';
|
||||
}
|
||||
$data = \file_get_contents($session_file);
|
||||
return $data ? $data : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($session_id, $session_data)
|
||||
{
|
||||
$temp_file = static::$_sessionSavePath . uniqid(bin2hex(random_bytes(8)), true);
|
||||
if (!\file_put_contents($temp_file, $session_data)) {
|
||||
return false;
|
||||
}
|
||||
return \rename($temp_file, static::sessionFile($session_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sesstion modify time.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/class.sessionupdatetimestamphandlerinterface.php
|
||||
* @see https://www.php.net/manual/zh/function.touch.php
|
||||
*
|
||||
* @param string $id Session id.
|
||||
* @param string $data Session Data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateTimestamp($id, $data = "")
|
||||
{
|
||||
$session_file = static::sessionFile($id);
|
||||
if (!file_exists($session_file)) {
|
||||
return false;
|
||||
}
|
||||
// set file modify time to current time
|
||||
$set_modify_time = \touch($session_file);
|
||||
// clear file stat cache
|
||||
\clearstatcache();
|
||||
return $set_modify_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($session_id)
|
||||
{
|
||||
$session_file = static::sessionFile($session_id);
|
||||
if (\is_file($session_file)) {
|
||||
\unlink($session_file);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gc($maxlifetime) {
|
||||
$time_now = \time();
|
||||
foreach (\glob(static::$_sessionSavePath . static::$_sessionFilePrefix . '*') as $file) {
|
||||
if(\is_file($file) && $time_now - \filemtime($file) > $maxlifetime) {
|
||||
\unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session file path.
|
||||
*
|
||||
* @param string $session_id
|
||||
* @return string
|
||||
*/
|
||||
protected static function sessionFile($session_id) {
|
||||
return static::$_sessionSavePath.static::$_sessionFilePrefix.$session_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set session file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public static function sessionSavePath($path) {
|
||||
if ($path) {
|
||||
if ($path[\strlen($path)-1] !== DIRECTORY_SEPARATOR) {
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
static::$_sessionSavePath = $path;
|
||||
if (!\is_dir($path)) {
|
||||
\mkdir($path, 0777, true);
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
FileSessionHandler::init();
|
||||
+46
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols\Http\Session;
|
||||
|
||||
use Workerman\Protocols\Http\Session;
|
||||
use Workerman\Timer;
|
||||
use RedisException;
|
||||
|
||||
/**
|
||||
* Class RedisSessionHandler
|
||||
* @package Workerman\Protocols\Http\Session
|
||||
*/
|
||||
class RedisSessionHandler implements SessionHandlerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \Redis
|
||||
*/
|
||||
protected $_redis;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_config;
|
||||
|
||||
/**
|
||||
* RedisSessionHandler constructor.
|
||||
* @param array $config = [
|
||||
* 'host' => '127.0.0.1',
|
||||
* 'port' => 6379,
|
||||
* 'timeout' => 2,
|
||||
* 'auth' => '******',
|
||||
* 'database' => 2,
|
||||
* 'prefix' => 'redis_session_',
|
||||
* 'ping' => 55,
|
||||
* ]
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
if (false === extension_loaded('redis')) {
|
||||
throw new \RuntimeException('Please install redis extension.');
|
||||
}
|
||||
|
||||
if (!isset($config['timeout'])) {
|
||||
$config['timeout'] = 2;
|
||||
}
|
||||
|
||||
$this->_config = $config;
|
||||
|
||||
$this->connect();
|
||||
|
||||
Timer::add(!empty($config['ping']) ? $config['ping'] : 55, function () {
|
||||
$this->_redis->get('ping');
|
||||
});
|
||||
}
|
||||
|
||||
public function connect()
|
||||
{
|
||||
$config = $this->_config;
|
||||
|
||||
$this->_redis = new \Redis();
|
||||
if (false === $this->_redis->connect($config['host'], $config['port'], $config['timeout'])) {
|
||||
throw new \RuntimeException("Redis connect {$config['host']}:{$config['port']} fail.");
|
||||
}
|
||||
if (!empty($config['auth'])) {
|
||||
$this->_redis->auth($config['auth']);
|
||||
}
|
||||
if (!empty($config['database'])) {
|
||||
$this->_redis->select($config['database']);
|
||||
}
|
||||
if (empty($config['prefix'])) {
|
||||
$config['prefix'] = 'redis_session_';
|
||||
}
|
||||
$this->_redis->setOption(\Redis::OPT_PREFIX, $config['prefix']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($save_path, $name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($session_id)
|
||||
{
|
||||
try {
|
||||
return $this->_redis->get($session_id);
|
||||
} catch (RedisException $e) {
|
||||
$msg = strtolower($e->getMessage());
|
||||
if ($msg === 'connection lost' || strpos($msg, 'went away')) {
|
||||
$this->connect();
|
||||
return $this->_redis->get($session_id);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($session_id, $session_data)
|
||||
{
|
||||
return true === $this->_redis->setex($session_id, Session::$lifetime, $session_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTimestamp($id, $data = "")
|
||||
{
|
||||
return true === $this->_redis->expire($id, Session::$lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy($session_id)
|
||||
{
|
||||
$this->_redis->del($session_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gc($maxlifetime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+114
@@ -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 = "");
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Workerman\Protocols;
|
||||
|
||||
use Workerman\Connection\ConnectionInterface;
|
||||
|
||||
/**
|
||||
* Protocol interface
|
||||
*/
|
||||
interface ProtocolInterface
|
||||
{
|
||||
/**
|
||||
* Check the integrity of the package.
|
||||
* Please return the length of package.
|
||||
* If length is unknow please return 0 that mean wating more data.
|
||||
* If the package has something wrong please return false the connection will be closed.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return int|false
|
||||
*/
|
||||
public static function input($recv_buffer, ConnectionInterface $connection);
|
||||
|
||||
/**
|
||||
* Decode package and emit onMessage($message) callback, $message is the result that decode returned.
|
||||
*
|
||||
* @param string $recv_buffer
|
||||
* @param ConnectionInterface $connection
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decode($recv_buffer, ConnectionInterface $connection);
|
||||
|
||||
/**
|
||||
* Encode package brefore sending to client.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param ConnectionInterface $connection
|
||||
* @return string
|
||||
*/
|
||||
public static function encode($data, ConnectionInterface $connection);
|
||||
}
|
||||
+70
@@ -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");
|
||||
}
|
||||
}
|
||||
+564
@@ -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, ConnectionInterface::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'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+460
@@ -0,0 +1,460 @@
|
||||
<?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\Lib\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->handshakeStep)) {
|
||||
Worker::safeEcho("recv data before handshake. Buffer:" . \bin2hex($buffer) . "\n");
|
||||
return false;
|
||||
}
|
||||
// Recv handshake response
|
||||
if ($connection->handshakeStep === 1) {
|
||||
return self::dealHandshake($buffer, $connection);
|
||||
}
|
||||
$recv_len = \strlen($buffer);
|
||||
if ($recv_len < 2) {
|
||||
return 0;
|
||||
}
|
||||
// Buffer websocket frame data.
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
// We need more frame data.
|
||||
if ($connection->websocketCurrentFrameLength > $recv_len) {
|
||||
// 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]);
|
||||
$data_len = $secondbyte & 127;
|
||||
$is_fin_frame = $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:
|
||||
break;
|
||||
// Blob type.
|
||||
case 0x1:
|
||||
break;
|
||||
// Arraybuffer type.
|
||||
case 0x2:
|
||||
break;
|
||||
// Close package.
|
||||
case 0x8:
|
||||
// Try to emit onWebSocketClose callback.
|
||||
if (isset($connection->onWebSocketClose)) {
|
||||
try {
|
||||
\call_user_func($connection->onWebSocketClose, $connection);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
} // Close connection.
|
||||
else {
|
||||
$connection->close();
|
||||
}
|
||||
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:" . $buffer . "\n");
|
||||
$connection->close();
|
||||
return 0;
|
||||
}
|
||||
// Calculate packet length.
|
||||
if ($data_len === 126) {
|
||||
if (\strlen($buffer) < 4) {
|
||||
return 0;
|
||||
}
|
||||
$pack = \unpack('nn/ntotal_len', $buffer);
|
||||
$current_frame_length = $pack['total_len'] + 4;
|
||||
} else if ($data_len === 127) {
|
||||
if (\strlen($buffer) < 10) {
|
||||
return 0;
|
||||
}
|
||||
$arr = \unpack('n/N2c', $buffer);
|
||||
$current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10;
|
||||
} else {
|
||||
$current_frame_length = $data_len + 2;
|
||||
}
|
||||
|
||||
$total_package_size = \strlen($connection->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";
|
||||
if (isset($connection->onWebSocketPing)) {
|
||||
try {
|
||||
\call_user_func($connection->onWebSocketPing, $connection, $ping_data);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $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.
|
||||
if (isset($connection->onWebSocketPong)) {
|
||||
try {
|
||||
\call_user_func($connection->onWebSocketPong, $connection, $pong_data);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $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->websocketCurrentFrameLength = $current_frame_length;
|
||||
}
|
||||
}
|
||||
// Received just a frame length data.
|
||||
if ($connection->websocketCurrentFrameLength === $recv_len) {
|
||||
self::decode($buffer, $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
return 0;
|
||||
} // The length of the received data is greater than the length of a frame.
|
||||
elseif ($connection->websocketCurrentFrameLength < $recv_len) {
|
||||
self::decode(\substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection);
|
||||
$connection->consumeRecvBuffer($connection->websocketCurrentFrameLength);
|
||||
$current_frame_length = $connection->websocketCurrentFrameLength;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
// Continue to read next frame.
|
||||
return self::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($payload, ConnectionInterface $connection)
|
||||
{
|
||||
if (empty($connection->websocketType)) {
|
||||
$connection->websocketType = self::BINARY_TYPE_BLOB;
|
||||
}
|
||||
$payload = (string)$payload;
|
||||
if (empty($connection->handshakeStep)) {
|
||||
static::sendHandshake($connection);
|
||||
}
|
||||
$mask = 1;
|
||||
$mask_key = "\x00\x00\x00\x00";
|
||||
|
||||
$pack = '';
|
||||
$length = $length_flag = \strlen($payload);
|
||||
if (65535 < $length) {
|
||||
$pack = \pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF);
|
||||
$length_flag = 127;
|
||||
} else if (125 < $length) {
|
||||
$pack = \pack('n*', $length);
|
||||
$length_flag = 126;
|
||||
}
|
||||
|
||||
$head = ($mask << 7) | $length_flag;
|
||||
$head = $connection->websocketType . \chr($head) . $pack;
|
||||
|
||||
$frame = $head . $mask_key;
|
||||
// append payload to frame:
|
||||
$mask_key = \str_repeat($mask_key, \floor($length / 4)) . \substr($mask_key, 0, $length % 4);
|
||||
$frame .= $payload ^ $mask_key;
|
||||
if ($connection->handshakeStep === 1) {
|
||||
// If buffer has already full then discard the current package.
|
||||
if (\strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) {
|
||||
if ($connection->onError) {
|
||||
try {
|
||||
\call_user_func($connection->onError, $connection, \WORKERMAN_SEND_FAIL, 'send buffer full and drop package');
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
$connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame;
|
||||
// Check buffer is full.
|
||||
if ($connection->maxSendBufferSize <= \strlen($connection->tmpWebsocketData)) {
|
||||
if ($connection->onBufferFull) {
|
||||
try {
|
||||
\call_user_func($connection->onBufferFull, $connection);
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $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)
|
||||
{
|
||||
$data_length = \ord($bytes[1]);
|
||||
|
||||
if ($data_length === 126) {
|
||||
$decoded_data = \substr($bytes, 4);
|
||||
} else if ($data_length === 127) {
|
||||
$decoded_data = \substr($bytes, 10);
|
||||
} else {
|
||||
$decoded_data = \substr($bytes, 2);
|
||||
}
|
||||
if ($connection->websocketCurrentFrameLength) {
|
||||
$connection->websocketDataBuffer .= $decoded_data;
|
||||
return $connection->websocketDataBuffer;
|
||||
} else {
|
||||
if ($connection->websocketDataBuffer !== '') {
|
||||
$decoded_data = $connection->websocketDataBuffer . $decoded_data;
|
||||
$connection->websocketDataBuffer = '';
|
||||
}
|
||||
return $decoded_data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send websocket handshake data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function onConnect($connection)
|
||||
{
|
||||
static::sendHandshake($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
*/
|
||||
public static function onClose($connection)
|
||||
{
|
||||
$connection->handshakeStep = null;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
$connection->tmpWebsocketData = '';
|
||||
$connection->websocketDataBuffer = '';
|
||||
if (!empty($connection->websocketPingTimer)) {
|
||||
Timer::del($connection->websocketPingTimer);
|
||||
$connection->websocketPingTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send websocket handshake.
|
||||
*
|
||||
* @param TcpConnection $connection
|
||||
* @return void
|
||||
*/
|
||||
public static function sendHandshake(TcpConnection $connection)
|
||||
{
|
||||
if (!empty($connection->handshakeStep)) {
|
||||
return;
|
||||
}
|
||||
// Get Host.
|
||||
$port = $connection->getRemotePort();
|
||||
$host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port;
|
||||
// Handshake header.
|
||||
$connection->websocketSecKey = \base64_encode(random_bytes(16));
|
||||
$user_header = isset($connection->headers) ? $connection->headers :
|
||||
(isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null);
|
||||
$user_header_str = '';
|
||||
if (!empty($user_header)) {
|
||||
if (\is_array($user_header)){
|
||||
foreach($user_header as $k=>$v){
|
||||
$user_header_str .= "$k: $v\r\n";
|
||||
}
|
||||
} else {
|
||||
$user_header_str .= $user_header;
|
||||
}
|
||||
$user_header_str = "\r\n".\trim($user_header_str);
|
||||
}
|
||||
$header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n".
|
||||
(!\preg_match("/\nHost:/i", $user_header_str) ? "Host: $host\r\n" : '').
|
||||
"Connection: Upgrade\r\n".
|
||||
"Upgrade: websocket\r\n".
|
||||
(isset($connection->websocketOrigin) ? "Origin: ".$connection->websocketOrigin."\r\n":'').
|
||||
(isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":'').
|
||||
"Sec-WebSocket-Version: 13\r\n".
|
||||
"Sec-WebSocket-Key: " . $connection->websocketSecKey . $user_header_str . "\r\n\r\n";
|
||||
$connection->send($header, true);
|
||||
$connection->handshakeStep = 1;
|
||||
$connection->websocketCurrentFrameLength = 0;
|
||||
$connection->websocketDataBuffer = '';
|
||||
$connection->tmpWebsocketData = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket handshake.
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param TcpConnection $connection
|
||||
* @return int
|
||||
*/
|
||||
public static function dealHandshake($buffer, TcpConnection $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->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->WSServerProtocol = \trim($match[1]);
|
||||
}
|
||||
|
||||
$connection->handshakeStep = 2;
|
||||
$handshake_response_length = $pos + 4;
|
||||
// Try to emit onWebSocketConnect callback.
|
||||
if (isset($connection->onWebSocketConnect)) {
|
||||
try {
|
||||
\call_user_func($connection->onWebSocketConnect, $connection, \substr($buffer, 0, $handshake_response_length));
|
||||
} catch (\Exception $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
} catch (\Error $e) {
|
||||
Worker::stopAll(250, $e);
|
||||
}
|
||||
}
|
||||
// Headbeat.
|
||||
if (!empty($connection->websocketPingInterval)) {
|
||||
$connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){
|
||||
if (false === $connection->send(\pack('H*', '898000000000'), true)) {
|
||||
Timer::del($connection->websocketPingTimer);
|
||||
$connection->websocketPingTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$connection->consumeRecvBuffer($handshake_response_length);
|
||||
if (!empty($connection->tmpWebsocketData)) {
|
||||
$connection->send($connection->tmpWebsocketData, true);
|
||||
$connection->tmpWebsocketData = '';
|
||||
}
|
||||
if (\strlen($buffer) > $handshake_response_length) {
|
||||
return self::input(\substr($buffer, $handshake_response_length), $connection);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function WSSetProtocol($connection, $params) {
|
||||
$connection->WSClientProtocol = $params[0];
|
||||
}
|
||||
|
||||
public static function WSGetServerProtocol($connection) {
|
||||
return (\property_exists($connection, 'WSServerProtocol') ? $connection->WSServerProtocol : null);
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+342
@@ -0,0 +1,342 @@
|
||||
# Workerman
|
||||
[](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
[](https://packagist.org/packages/workerman/workerman)
|
||||
|
||||
## What is it
|
||||
Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications.
|
||||
Workerman supports HTTP, Websocket, SSL and other custom protocols.
|
||||
Workerman supports event extension.
|
||||
|
||||
## Requires
|
||||
PHP 5.4 or Higher
|
||||
A POSIX compatible operating system (Linux, OSX, BSD)
|
||||
POSIX and PCNTL extensions required
|
||||
Event extension recommended for better performance
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
composer require workerman/workerman
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### A websocket server
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Create a Websocket server
|
||||
$ws_worker = new Worker('websocket://0.0.0.0:2346');
|
||||
|
||||
// Emitted when new connection come
|
||||
$ws_worker->onConnect = function ($connection) {
|
||||
echo "New connection\n";
|
||||
};
|
||||
|
||||
// Emitted when data received
|
||||
$ws_worker->onMessage = function ($connection, $data) {
|
||||
// Send hello $data
|
||||
$connection->send('Hello ' . $data);
|
||||
};
|
||||
|
||||
// Emitted when connection closed
|
||||
$ws_worker->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
// Run worker
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### An http server
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// #### http worker ####
|
||||
$http_worker = new Worker('http://0.0.0.0:2345');
|
||||
|
||||
// 4 processes
|
||||
$http_worker->count = 4;
|
||||
|
||||
// Emitted when data received
|
||||
$http_worker->onMessage = function ($connection, $request) {
|
||||
//$request->get();
|
||||
//$request->post();
|
||||
//$request->header();
|
||||
//$request->cookie();
|
||||
//$request->session();
|
||||
//$request->uri();
|
||||
//$request->path();
|
||||
//$request->method();
|
||||
|
||||
// Send data to client
|
||||
$connection->send("Hello World");
|
||||
};
|
||||
|
||||
// Run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### A tcp server
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// #### create socket and listen 1234 port ####
|
||||
$tcp_worker = new Worker('tcp://0.0.0.0:1234');
|
||||
|
||||
// 4 processes
|
||||
$tcp_worker->count = 4;
|
||||
|
||||
// Emitted when new connection come
|
||||
$tcp_worker->onConnect = function ($connection) {
|
||||
echo "New Connection\n";
|
||||
};
|
||||
|
||||
// Emitted when data received
|
||||
$tcp_worker->onMessage = function ($connection, $data) {
|
||||
// Send data to client
|
||||
$connection->send("Hello $data \n");
|
||||
};
|
||||
|
||||
// Emitted when connection is closed
|
||||
$tcp_worker->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### A udp server
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$worker = new Worker('udp://0.0.0.0:1234');
|
||||
|
||||
// 4 processes
|
||||
$tcp_worker->count = 4;
|
||||
|
||||
// Emitted when data received
|
||||
$worker->onMessage = function($connection, $data)
|
||||
{
|
||||
$connection->send($data);
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Enable SSL
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// SSL context.
|
||||
$context = array(
|
||||
'ssl' => array(
|
||||
'local_cert' => '/your/path/of/server.pem',
|
||||
'local_pk' => '/your/path/of/server.key',
|
||||
'verify_peer' => false,
|
||||
)
|
||||
);
|
||||
|
||||
// Create a Websocket server with ssl context.
|
||||
$ws_worker = new Worker('websocket://0.0.0.0:2346', $context);
|
||||
|
||||
// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://).
|
||||
// The similar approaches for Https etc.
|
||||
$ws_worker->transport = 'ssl';
|
||||
|
||||
$ws_worker->onMessage = function ($connection, $data) {
|
||||
// Send hello $data
|
||||
$connection->send('Hello ' . $data);
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Custom protocol
|
||||
Protocols/MyTextProtocol.php
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Protocols;
|
||||
|
||||
/**
|
||||
* User defined protocol
|
||||
* Format Text+"\n"
|
||||
*/
|
||||
class MyTextProtocol
|
||||
{
|
||||
public static function input($recv_buffer)
|
||||
{
|
||||
// Find the position of the first occurrence of "\n"
|
||||
$pos = strpos($recv_buffer, "\n");
|
||||
|
||||
// Not a complete package. Return 0 because the length of package can not be calculated
|
||||
if ($pos === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return length of the package
|
||||
return $pos+1;
|
||||
}
|
||||
|
||||
public static function decode($recv_buffer)
|
||||
{
|
||||
return trim($recv_buffer);
|
||||
}
|
||||
|
||||
public static function encode($data)
|
||||
{
|
||||
return $data . "\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// #### MyTextProtocol worker ####
|
||||
$text_worker = new Worker('MyTextProtocol://0.0.0.0:5678');
|
||||
|
||||
$text_worker->onConnect = function ($connection) {
|
||||
echo "New connection\n";
|
||||
};
|
||||
|
||||
$text_worker->onMessage = function ($connection, $data) {
|
||||
// Send data to client
|
||||
$connection->send("Hello world\n");
|
||||
};
|
||||
|
||||
$text_worker->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
|
||||
// Run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### Timer
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Timer;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$task = new Worker();
|
||||
$task->onWorkerStart = function ($task) {
|
||||
// 2.5 seconds
|
||||
$time_interval = 2.5;
|
||||
$timer_id = Timer::add($time_interval, function () {
|
||||
echo "Timer run\n";
|
||||
});
|
||||
};
|
||||
|
||||
// Run all workers
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
### AsyncTcpConnection (tcp/ws/text/frame etc...)
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Workerman\Worker;
|
||||
use Workerman\Connection\AsyncTcpConnection;
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$worker = new Worker();
|
||||
$worker->onWorkerStart = function () {
|
||||
// Websocket protocol for client.
|
||||
$ws_connection = new AsyncTcpConnection('ws://echo.websocket.org:80');
|
||||
$ws_connection->onConnect = function ($connection) {
|
||||
$connection->send('Hello');
|
||||
};
|
||||
$ws_connection->onMessage = function ($connection, $data) {
|
||||
echo "Recv: $data\n";
|
||||
};
|
||||
$ws_connection->onError = function ($connection, $code, $msg) {
|
||||
echo "Error: $msg\n";
|
||||
};
|
||||
$ws_connection->onClose = function ($connection) {
|
||||
echo "Connection closed\n";
|
||||
};
|
||||
$ws_connection->connect();
|
||||
};
|
||||
|
||||
Worker::runAll();
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Available commands
|
||||
```php start.php start ```
|
||||
```php start.php start -d ```
|
||||

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

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

|
||||
|
||||
## Sponsors
|
||||
[opencollective.com/walkor](https://opencollective.com/walkor)
|
||||
|
||||
[patreon.com/walkor](https://patreon.com/walkor)
|
||||
|
||||
## Donate
|
||||
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UQGGS9UB35WWG"><img src="http://donate.workerman.net/img/donate.png"></a>
|
||||
|
||||
## Other links with workerman
|
||||
|
||||
[webman](https://github.com/walkor/webman)
|
||||
[PHPSocket.IO](https://github.com/walkor/phpsocket.io)
|
||||
[php-socks5](https://github.com/walkor/php-socks5)
|
||||
[php-http-proxy](https://github.com/walkor/php-http-proxy)
|
||||
|
||||
## LICENSE
|
||||
|
||||
Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt).
|
||||
Vendored
+220
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+2625
File diff suppressed because it is too large
Load Diff
+38
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user