add
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Http\Response;
|
||||
use Swoole\Server;
|
||||
use Symfony\Component\VarDumper\VarDumper;
|
||||
use think\App;
|
||||
use think\Container;
|
||||
use think\Cookie;
|
||||
use think\Event;
|
||||
use think\exception\Handle;
|
||||
use think\Http;
|
||||
use think\Middleware;
|
||||
use think\swoole\middleware\ResetVarDumper;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithHttp
|
||||
* @package think\swoole\concerns
|
||||
* @property App $app
|
||||
* @property Container $container
|
||||
* @method Server getServer()
|
||||
*/
|
||||
trait InteractsWithHttp
|
||||
{
|
||||
public static $statusTexts = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing', // RFC2518
|
||||
103 => 'Early Hints',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status', // RFC4918
|
||||
208 => 'Already Reported', // RFC5842
|
||||
226 => 'IM Used', // RFC3229
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
307 => 'Temporary Redirect',
|
||||
308 => 'Permanent Redirect', // RFC7238
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Payload Too Large',
|
||||
414 => 'URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
418 => 'I\'m a teapot', // RFC2324
|
||||
421 => 'Misdirected Request', // RFC7540
|
||||
422 => 'Unprocessable Entity', // RFC4918
|
||||
423 => 'Locked', // RFC4918
|
||||
424 => 'Failed Dependency', // RFC4918
|
||||
425 => 'Too Early', // RFC-ietf-httpbis-replay-04
|
||||
426 => 'Upgrade Required', // RFC2817
|
||||
428 => 'Precondition Required', // RFC6585
|
||||
429 => 'Too Many Requests', // RFC6585
|
||||
431 => 'Request Header Fields Too Large', // RFC6585
|
||||
449 => 'Retry With',
|
||||
451 => 'Unavailable For Legal Reasons', // RFC7725
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
506 => 'Variant Also Negotiates', // RFC2295
|
||||
507 => 'Insufficient Storage', // RFC4918
|
||||
508 => 'Loop Detected', // RFC5842
|
||||
510 => 'Not Extended', // RFC2774
|
||||
511 => 'Network Authentication Required', // RFC6585
|
||||
];
|
||||
|
||||
/**
|
||||
* "onRequest" listener.
|
||||
*
|
||||
* @param Request $req
|
||||
* @param Response $res
|
||||
*/
|
||||
public function onRequest($req, $res)
|
||||
{
|
||||
$args = func_get_args();
|
||||
$this->runInSandbox(function (Http $http, Event $event, App $app, Middleware $middleware) use ($args, $req, $res) {
|
||||
$event->trigger('swoole.request', $args);
|
||||
|
||||
//兼容var-dumper
|
||||
if (class_exists(VarDumper::class)) {
|
||||
$middleware->add(ResetVarDumper::class);
|
||||
}
|
||||
|
||||
$request = $this->prepareRequest($req);
|
||||
try {
|
||||
$response = $this->handleRequest($http, $request);
|
||||
} catch (Throwable $e) {
|
||||
$response = $this->app
|
||||
->make(Handle::class)
|
||||
->render($request, $e);
|
||||
}
|
||||
|
||||
$this->sendResponse($res, $response, $app->cookie);
|
||||
});
|
||||
}
|
||||
|
||||
protected function handleRequest(Http $http, $request)
|
||||
{
|
||||
$level = ob_get_level();
|
||||
ob_start();
|
||||
|
||||
$response = $http->run($request);
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
if (ob_get_level() == 0) {
|
||||
ob_start();
|
||||
}
|
||||
|
||||
$http->end($response);
|
||||
|
||||
if (ob_get_length() > 0) {
|
||||
$response->content(ob_get_contents() . $content);
|
||||
}
|
||||
|
||||
while (ob_get_level() > $level) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function prepareRequest(Request $req)
|
||||
{
|
||||
$header = $req->header ?: [];
|
||||
$server = $req->server ?: [];
|
||||
|
||||
foreach ($header as $key => $value) {
|
||||
$server["http_" . str_replace('-', '_', $key)] = $value;
|
||||
}
|
||||
|
||||
// 重新实例化请求对象 处理swoole请求数据
|
||||
/** @var \think\Request $request */
|
||||
$request = $this->app->make('request', [], true);
|
||||
|
||||
return $request->withHeader($header)
|
||||
->withServer($server)
|
||||
->withGet($req->get ?: [])
|
||||
->withPost($req->post ?: [])
|
||||
->withCookie($req->cookie ?: [])
|
||||
->withFiles($req->files ?: [])
|
||||
->withInput($req->rawContent())
|
||||
->setBaseUrl($req->server['request_uri'])
|
||||
->setUrl($req->server['request_uri'] . (!empty($req->server['query_string']) ? '?' . $req->server['query_string'] : ''))
|
||||
->setPathinfo(ltrim($req->server['path_info'], '/'));
|
||||
}
|
||||
|
||||
protected function sendResponse(Response $res, \think\Response $response, Cookie $cookie)
|
||||
{
|
||||
// 发送Header
|
||||
foreach ($response->getHeader() as $key => $val) {
|
||||
$res->header($key, $val);
|
||||
}
|
||||
|
||||
// 发送状态码
|
||||
$code = $response->getCode();
|
||||
$res->status($code, isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status');
|
||||
|
||||
foreach ($cookie->getCookie() as $name => $val) {
|
||||
[$value, $expire, $option] = $val;
|
||||
|
||||
$res->cookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'] ? true : false, $option['httponly'] ? true : false);
|
||||
}
|
||||
|
||||
$content = $response->getContent();
|
||||
|
||||
$this->sendByChunk($res, $content);
|
||||
}
|
||||
|
||||
protected function sendByChunk(Response $res, $content)
|
||||
{
|
||||
if (!empty($content)) {
|
||||
$chunkSize = 8192;
|
||||
|
||||
foreach (str_split($content, $chunkSize) as $chunk) {
|
||||
$res->write($chunk);
|
||||
}
|
||||
}
|
||||
$res->end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Exception;
|
||||
use Smf\ConnectionPool\ConnectionPool;
|
||||
use Smf\ConnectionPool\Connectors\ConnectorInterface;
|
||||
use Swoole\Server;
|
||||
use think\App;
|
||||
use think\helper\Arr;
|
||||
use think\swoole\Pool;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithRpc
|
||||
* @package think\swoole\concerns
|
||||
* @property App $app
|
||||
* @method Server getServer()
|
||||
*/
|
||||
trait InteractsWithPools
|
||||
{
|
||||
/**
|
||||
* @return Pool
|
||||
*/
|
||||
public function getPools()
|
||||
{
|
||||
return $this->app->make(Pool::class);
|
||||
}
|
||||
|
||||
protected function preparePools()
|
||||
{
|
||||
$createPools = function () {
|
||||
/** @var Pool $pool */
|
||||
$pools = $this->getPools();
|
||||
|
||||
foreach ($this->getConfig('pool', []) as $name => $config) {
|
||||
$type = Arr::pull($config, 'type');
|
||||
if ($type && is_subclass_of($type, ConnectorInterface::class)) {
|
||||
$pool = new ConnectionPool(
|
||||
Pool::pullPoolConfig($config),
|
||||
$this->app->make($type),
|
||||
$config
|
||||
);
|
||||
$pools->add($name, $pool);
|
||||
//注入到app
|
||||
$this->app->instance("swoole.pool.{$name}", $pool);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$closePools = function () {
|
||||
try {
|
||||
$this->getPools()->closeAll();
|
||||
} catch (Exception | Throwable $e) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
$this->onEvent('workerStart', $createPools);
|
||||
$this->onEvent('workerStop', $closePools);
|
||||
$this->onEvent('workerError', $closePools);
|
||||
$this->onEvent('workerExit', $closePools);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Smf\ConnectionPool\ConnectionPool;
|
||||
use Swoole\Server;
|
||||
use think\App;
|
||||
use think\swoole\exception\RpcClientException;
|
||||
use think\swoole\Pool;
|
||||
use think\swoole\pool\Client;
|
||||
use think\swoole\rpc\client\Connector;
|
||||
use think\swoole\rpc\client\Gateway;
|
||||
use think\swoole\rpc\client\Proxy;
|
||||
use think\swoole\rpc\JsonParser;
|
||||
use think\swoole\rpc\Packer;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithRpcClient
|
||||
* @package think\swoole\concerns
|
||||
* @property App $app
|
||||
* @property App $container
|
||||
* @method Server getServer()
|
||||
* @method Pool getPools()
|
||||
*/
|
||||
trait InteractsWithRpcClient
|
||||
{
|
||||
protected $rpcServices = [];
|
||||
|
||||
protected function prepareRpcClient()
|
||||
{
|
||||
//引入rpc接口文件
|
||||
if (file_exists($rpc = $this->container->getBasePath() . 'rpc.php')) {
|
||||
$this->rpcServices = (array) include $rpc;
|
||||
}
|
||||
|
||||
$this->onEvent('workerStart', function () {
|
||||
$this->bindRpcClientPool();
|
||||
});
|
||||
}
|
||||
|
||||
protected function bindRpcClientPool()
|
||||
{
|
||||
if (!empty($clients = $this->getConfig('rpc.client'))) {
|
||||
//创建client连接池
|
||||
foreach ($clients as $name => $config) {
|
||||
$pool = new ConnectionPool(
|
||||
Pool::pullPoolConfig($config),
|
||||
new Client(),
|
||||
array_merge(
|
||||
$config,
|
||||
[
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => Packer::HEADER_PACK,
|
||||
'package_length_offset' => 0,
|
||||
'package_body_offset' => 8,
|
||||
]
|
||||
)
|
||||
);
|
||||
$this->getPools()->add("rpc.client.{$name}", $pool);
|
||||
}
|
||||
|
||||
//绑定rpc接口
|
||||
try {
|
||||
foreach ($this->rpcServices as $name => $abstracts) {
|
||||
$parserClass = $this->getConfig("rpc.client.{$name}.parser", JsonParser::class);
|
||||
$parser = $this->app->make($parserClass);
|
||||
$gateway = new Gateway($this->createRpcConnector($name), $parser);
|
||||
|
||||
foreach ($abstracts as $abstract) {
|
||||
$this->app->bind($abstract, function () use ($gateway, $name, $abstract) {
|
||||
return $this->app->invokeClass(Proxy::getClassName($name, $abstract), [$gateway]);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (\Exception | \Throwable $e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createRpcConnector($name)
|
||||
{
|
||||
$pool = $this->getPools()->get("rpc.client.{$name}");
|
||||
|
||||
return new class($pool) implements Connector {
|
||||
protected $pool;
|
||||
|
||||
public function __construct(ConnectionPool $pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
public function sendAndRecv($data)
|
||||
{
|
||||
if (!$data instanceof \Generator) {
|
||||
$data = [$data];
|
||||
}
|
||||
|
||||
/** @var \Swoole\Coroutine\Client $client */
|
||||
$client = $this->pool->borrow();
|
||||
|
||||
try {
|
||||
foreach ($data as $string) {
|
||||
if (!$client->send($string)) {
|
||||
$this->onError($client);
|
||||
}
|
||||
}
|
||||
|
||||
$response = $client->recv();
|
||||
|
||||
if ($response === false || empty($response)) {
|
||||
$this->onError($client);
|
||||
}
|
||||
|
||||
return $response;
|
||||
} finally {
|
||||
$this->pool->return($client);
|
||||
}
|
||||
}
|
||||
|
||||
protected function onError(\Swoole\Coroutine\Client $client)
|
||||
{
|
||||
$client->close();
|
||||
throw new RpcClientException(swoole_strerror($client->errCode), $client->errCode);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Swoole\Server;
|
||||
use think\App;
|
||||
use think\swoole\Pool;
|
||||
use think\swoole\RpcManager;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithRpc
|
||||
* @package think\swoole\concerns
|
||||
* @property App $app
|
||||
* @property App $container
|
||||
* @method Server getServer()
|
||||
* @method Pool getPools()
|
||||
*/
|
||||
trait InteractsWithRpcServer
|
||||
{
|
||||
|
||||
protected function prepareRpcServer()
|
||||
{
|
||||
if ($this->getConfig('rpc.server.enable', false)) {
|
||||
$host = $this->getConfig('server.host');
|
||||
$port = $this->getConfig('rpc.server.port', 9000);
|
||||
|
||||
$rpcServer = $this->getServer()->addlistener($host, $port, SWOOLE_SOCK_TCP);
|
||||
|
||||
/** @var RpcManager $rpcManager */
|
||||
$rpcManager = $this->container->make(RpcManager::class);
|
||||
|
||||
$rpcManager->attachToServer($rpcServer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Exception;
|
||||
use Swoole\Process;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\Server;
|
||||
use Swoole\Server\Task;
|
||||
use think\App;
|
||||
use think\console\Output;
|
||||
use think\Event;
|
||||
use think\exception\Handle;
|
||||
use think\helper\Str;
|
||||
use think\swoole\FileWatcher;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithServer
|
||||
* @package think\swoole\concerns
|
||||
* @property App $container
|
||||
*/
|
||||
trait InteractsWithServer
|
||||
{
|
||||
|
||||
/**
|
||||
* 启动服务
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->getServer()->set([
|
||||
'task_enable_coroutine' => true,
|
||||
'send_yield' => true,
|
||||
'reload_async' => true,
|
||||
'enable_coroutine' => true,
|
||||
'max_request' => 0,
|
||||
'task_max_request' => 0,
|
||||
]);
|
||||
$this->initialize();
|
||||
$this->triggerEvent('init');
|
||||
|
||||
//热更新
|
||||
if ($this->getConfig('hot_update.enable', false)) {
|
||||
$this->addHotUpdateProcess();
|
||||
}
|
||||
|
||||
$this->getServer()->start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止服务
|
||||
*/
|
||||
public function stop(): void
|
||||
{
|
||||
$this->getServer()->shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* "onStart" listener.
|
||||
*/
|
||||
public function onStart()
|
||||
{
|
||||
$this->setProcessName('master process');
|
||||
|
||||
$this->triggerEvent("start", func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* The listener of "managerStart" event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onManagerStart()
|
||||
{
|
||||
$this->setProcessName('manager process');
|
||||
$this->triggerEvent("managerStart", func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* "onWorkerStart" listener.
|
||||
*
|
||||
* @param \Swoole\Http\Server|mixed $server
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onWorkerStart($server)
|
||||
{
|
||||
Runtime::enableCoroutine(
|
||||
$this->getConfig('coroutine.enable', true),
|
||||
$this->getConfig('coroutine.flags', SWOOLE_HOOK_ALL)
|
||||
);
|
||||
|
||||
$this->clearCache();
|
||||
|
||||
$this->setProcessName($server->taskworker ? 'task process' : 'worker process');
|
||||
|
||||
$this->prepareApplication();
|
||||
|
||||
$this->triggerEvent("workerStart", $this->app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set onTask listener.
|
||||
*
|
||||
* @param mixed $server
|
||||
* @param Task $task
|
||||
*/
|
||||
public function onTask($server, Task $task)
|
||||
{
|
||||
$this->runInSandbox(function (Event $event) use ($task) {
|
||||
$event->trigger('swoole.task', $task);
|
||||
}, $task->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set onShutdown listener.
|
||||
*/
|
||||
public function onShutdown()
|
||||
{
|
||||
$this->triggerEvent('shutdown');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Server
|
||||
*/
|
||||
public function getServer()
|
||||
{
|
||||
return $this->container->make(Server::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set swoole server listeners.
|
||||
*/
|
||||
protected function setSwooleServerListeners()
|
||||
{
|
||||
foreach ($this->events as $event) {
|
||||
$listener = Str::camel("on_$event");
|
||||
$callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) {
|
||||
$this->triggerEvent($event, func_get_args());
|
||||
};
|
||||
|
||||
$this->getServer()->on($event, $callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 热更新
|
||||
*/
|
||||
protected function addHotUpdateProcess()
|
||||
{
|
||||
$process = new Process(function () {
|
||||
$watcher = new FileWatcher(
|
||||
$this->getConfig('hot_update.include', []),
|
||||
$this->getConfig('hot_update.exclude', []),
|
||||
$this->getConfig('hot_update.name', [])
|
||||
);
|
||||
|
||||
$watcher->watch(function () {
|
||||
$this->getServer()->reload();
|
||||
});
|
||||
}, false, 0);
|
||||
|
||||
$this->addProcess($process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add process to http server
|
||||
*
|
||||
* @param Process $process
|
||||
*/
|
||||
public function addProcess(Process $process): void
|
||||
{
|
||||
$this->getServer()->addProcess($process);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除apc、op缓存
|
||||
*/
|
||||
protected function clearCache()
|
||||
{
|
||||
if (extension_loaded('apc')) {
|
||||
apc_clear_cache();
|
||||
}
|
||||
|
||||
if (extension_loaded('Zend OPcache')) {
|
||||
opcache_reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set process name.
|
||||
*
|
||||
* @param $process
|
||||
*/
|
||||
protected function setProcessName($process)
|
||||
{
|
||||
// Mac OSX不支持进程重命名
|
||||
if (stristr(PHP_OS, 'DAR')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$serverName = 'swoole_http_server';
|
||||
$appName = $this->container->config->get('app.name', 'ThinkPHP');
|
||||
|
||||
$name = sprintf('%s: %s for %s', $serverName, $process, $appName);
|
||||
|
||||
swoole_set_process_name($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log server error.
|
||||
*
|
||||
* @param Throwable|Exception $e
|
||||
*/
|
||||
public function logServerError(Throwable $e)
|
||||
{
|
||||
/** @var Handle $handle */
|
||||
$handle = $this->container->make(Handle::class);
|
||||
|
||||
$handle->renderForConsole(new Output(), $e);
|
||||
|
||||
$handle->report($e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Swoole\Table as SwooleTable;
|
||||
use think\App;
|
||||
use think\Container;
|
||||
use think\swoole\Table;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithSwooleTable
|
||||
*
|
||||
* @property Container $container
|
||||
* @property App $app
|
||||
*/
|
||||
trait InteractsWithSwooleTable
|
||||
{
|
||||
/**
|
||||
* @var Table
|
||||
*/
|
||||
protected $currentTable;
|
||||
|
||||
/**
|
||||
* Register customized swoole tables.
|
||||
*/
|
||||
protected function prepareTables()
|
||||
{
|
||||
$this->currentTable = new Table();
|
||||
$this->registerTables();
|
||||
$this->onEvent('workerStart', function () {
|
||||
$this->app->instance(Table::class, $this->currentTable);
|
||||
foreach ($this->currentTable->getAll() as $name => $table) {
|
||||
$this->app->instance("swoole.table.{$name}", $table);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register user-defined swoole tables.
|
||||
*/
|
||||
protected function registerTables()
|
||||
{
|
||||
$tables = $this->container->make('config')->get('swoole.tables', []);
|
||||
|
||||
foreach ($tables as $key => $value) {
|
||||
$table = new SwooleTable($value['size']);
|
||||
$columns = $value['columns'] ?? [];
|
||||
foreach ($columns as $column) {
|
||||
if (isset($column['size'])) {
|
||||
$table->column($column['name'], $column['type'], $column['size']);
|
||||
} else {
|
||||
$table->column($column['name'], $column['type']);
|
||||
}
|
||||
}
|
||||
$table->create();
|
||||
|
||||
$this->currentTable->add($key, $table);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Server\Task;
|
||||
use Swoole\Websocket\Frame;
|
||||
use Swoole\Websocket\Server;
|
||||
use think\App;
|
||||
use think\Container;
|
||||
use think\Event;
|
||||
use think\helper\Str;
|
||||
use think\Pipeline;
|
||||
use think\swoole\contract\websocket\HandlerInterface;
|
||||
use think\swoole\contract\websocket\ParserInterface;
|
||||
use think\swoole\contract\websocket\RoomInterface;
|
||||
use think\swoole\Websocket;
|
||||
use think\swoole\websocket\Pusher;
|
||||
use think\swoole\websocket\Room;
|
||||
use think\swoole\websocket\socketio\Handler;
|
||||
use think\swoole\websocket\socketio\Parser as SocketioParser;
|
||||
|
||||
/**
|
||||
* Trait InteractsWithWebsocket
|
||||
* @package think\swoole\concerns
|
||||
*
|
||||
* @property App $app
|
||||
* @property Container $container
|
||||
* @method \Swoole\Server getServer()
|
||||
*/
|
||||
trait InteractsWithWebsocket
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isWebsocketServer = false;
|
||||
|
||||
/**
|
||||
* @var RoomInterface
|
||||
*/
|
||||
protected $websocketRoom;
|
||||
|
||||
/**
|
||||
* Websocket server events.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $wsEvents = ['open', 'message', 'close'];
|
||||
|
||||
/**
|
||||
* "onOpen" listener.
|
||||
*
|
||||
* @param Server $server
|
||||
* @param Request $req
|
||||
*/
|
||||
public function onOpen($server, $req)
|
||||
{
|
||||
/** @var Websocket $websocket */
|
||||
$websocket = $this->app->make(Websocket::class);
|
||||
$websocket->setSender($req->fd);
|
||||
|
||||
$this->runInSandbox(function (Event $event, HandlerInterface $handler, App $app) use ($req) {
|
||||
$request = $this->prepareRequest($req);
|
||||
$request = $this->setRequestThroughMiddleware($app, $request);
|
||||
|
||||
if (!$handler->onOpen($req->fd, $request)) {
|
||||
$event->trigger("swoole.websocket.Connect", $request);
|
||||
}
|
||||
}, $req->fd, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* "onMessage" listener.
|
||||
*
|
||||
* @param Server $server
|
||||
* @param Frame $frame
|
||||
*/
|
||||
public function onMessage($server, $frame)
|
||||
{
|
||||
/** @var Websocket $websocket */
|
||||
$websocket = $this->app->make(Websocket::class);
|
||||
$websocket->setSender($frame->fd);
|
||||
|
||||
$this->runInSandbox(function (Event $event, ParserInterface $parser, HandlerInterface $handler) use ($frame) {
|
||||
if (!$handler->onMessage($frame)) {
|
||||
$payload = $parser->decode($frame);
|
||||
|
||||
['event' => $name, 'data' => $data] = $payload;
|
||||
$name = Str::studly($name);
|
||||
if (!in_array($name, ['Close', 'Connect'])) {
|
||||
$event->trigger("swoole.websocket." . $name, $data);
|
||||
}
|
||||
}
|
||||
}, $frame->fd, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* "onClose" listener.
|
||||
*
|
||||
* @param Server $server
|
||||
* @param int $fd
|
||||
* @param int $reactorId
|
||||
*/
|
||||
public function onClose($server, $fd, $reactorId)
|
||||
{
|
||||
if (!$this->isWebsocketServer($fd) || !$server instanceof Server) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Websocket $websocket */
|
||||
$websocket = $this->app->make(Websocket::class);
|
||||
$websocket->setSender($fd);
|
||||
|
||||
$this->runInSandbox(function (Event $event, HandlerInterface $handler) use ($websocket, $fd, $reactorId) {
|
||||
try {
|
||||
if (!$handler->onClose($fd, $reactorId)) {
|
||||
$event->trigger("swoole.websocket.Close");
|
||||
}
|
||||
} finally {
|
||||
// leave all rooms
|
||||
$websocket->leave();
|
||||
}
|
||||
}, $fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param App $app
|
||||
* @param \think\Request $request
|
||||
* @return \think\Request
|
||||
*/
|
||||
protected function setRequestThroughMiddleware(App $app, \think\Request $request)
|
||||
{
|
||||
$middleware = $this->getConfig('websocket.middleware', []);
|
||||
|
||||
return (new Pipeline())
|
||||
->send($request)
|
||||
->through(array_map(function ($middleware) use ($app) {
|
||||
return function ($request, $next) use ($app, $middleware) {
|
||||
if (is_array($middleware)) {
|
||||
list($middleware, $param) = $middleware;
|
||||
}
|
||||
if (is_string($middleware)) {
|
||||
$middleware = [$app->make($middleware), 'handle'];
|
||||
}
|
||||
return call_user_func($middleware, $request, $next, $param ?? null);
|
||||
};
|
||||
}, $middleware))
|
||||
->then(function ($request) {
|
||||
return $request;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare settings if websocket is enabled.
|
||||
*/
|
||||
protected function prepareWebsocket()
|
||||
{
|
||||
if (!$this->isWebsocketServer = $this->getConfig('websocket.enable', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->events = array_merge($this->events ?? [], $this->wsEvents);
|
||||
|
||||
$this->prepareWebsocketRoom();
|
||||
|
||||
$this->onEvent('workerStart', function () {
|
||||
$this->bindWebsocketRoom();
|
||||
$this->bindWebsocketParser();
|
||||
$this->bindWebsocketHandler();
|
||||
$this->prepareWebsocketListener();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's a websocket fd.
|
||||
*
|
||||
* @param int $fd
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isWebsocketServer(int $fd): bool
|
||||
{
|
||||
return $this->getServer()->getClientInfo($fd)['websocket_status'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare websocket room.
|
||||
*/
|
||||
protected function prepareWebsocketRoom()
|
||||
{
|
||||
// create room instance and initialize
|
||||
$this->websocketRoom = $this->container->make(Room::class);
|
||||
$this->websocketRoom->prepare();
|
||||
}
|
||||
|
||||
protected function prepareWebsocketListener()
|
||||
{
|
||||
$listeners = $this->getConfig('websocket.listen', []);
|
||||
|
||||
foreach ($listeners as $event => $listener) {
|
||||
$this->app->event->listen('swoole.websocket.' . Str::studly($event), $listener);
|
||||
}
|
||||
|
||||
$subscribers = $this->getConfig('websocket.subscribe', []);
|
||||
|
||||
foreach ($subscribers as $subscriber) {
|
||||
$this->app->event->observe($subscriber, 'swoole.websocket.');
|
||||
}
|
||||
|
||||
//消息推送任务
|
||||
$this->app->event->listen('swoole.task', function (Task $task, App $app) {
|
||||
if ($this->isWebsocketPushPayload($task->data)) {
|
||||
$pusher = $app->make(Pusher::class, $task->data['data']);
|
||||
$pusher->push();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare websocket handler for onOpen and onClose callback.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function bindWebsocketHandler()
|
||||
{
|
||||
$handlerClass = $this->getConfig('websocket.handler', Handler::class);
|
||||
|
||||
$this->app->bind(HandlerInterface::class, $handlerClass);
|
||||
|
||||
$this->app->make(HandlerInterface::class);
|
||||
}
|
||||
|
||||
protected function bindWebsocketParser()
|
||||
{
|
||||
$parserClass = $this->getConfig('websocket.parser', SocketioParser::class);
|
||||
|
||||
$this->app->bind(ParserInterface::class, $parserClass);
|
||||
|
||||
$this->app->make(ParserInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind room instance to app container.
|
||||
*/
|
||||
protected function bindWebsocketRoom(): void
|
||||
{
|
||||
$this->app->instance(Room::class, $this->websocketRoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the payload is websocket push.
|
||||
*
|
||||
* @param mixed $payload
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWebsocketPushPayload($payload): bool
|
||||
{
|
||||
if (!is_array($payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->isWebsocketServer
|
||||
&& ($payload['action'] ?? null) === Websocket::PUSH_ACTION
|
||||
&& array_key_exists('data', $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use ReflectionObject;
|
||||
|
||||
trait ModifyProperty
|
||||
{
|
||||
protected function modifyProperty($object, $value, $property = 'app')
|
||||
{
|
||||
$reflectObject = new ReflectionObject($object);
|
||||
if ($reflectObject->hasProperty($property)) {
|
||||
$reflectProperty = $reflectObject->getProperty($property);
|
||||
$reflectProperty->setAccessible(true);
|
||||
$reflectProperty->setValue($object, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace think\swoole\concerns;
|
||||
|
||||
use Closure;
|
||||
use Swoole\Server;
|
||||
use think\App;
|
||||
use think\swoole\App as SwooleApp;
|
||||
use think\swoole\pool\Cache;
|
||||
use think\swoole\pool\Db;
|
||||
use think\swoole\Sandbox;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Trait WithApplication
|
||||
* @package think\swoole\concerns
|
||||
* @property App $container
|
||||
*/
|
||||
trait WithApplication
|
||||
{
|
||||
|
||||
/**
|
||||
* @var SwooleApp
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @param string $name
|
||||
* @param null $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig(string $name, $default = null)
|
||||
{
|
||||
return $this->container->config->get("swoole.{$name}", $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param $event
|
||||
* @param $params
|
||||
*/
|
||||
protected function triggerEvent(string $event, $params = null): void
|
||||
{
|
||||
$this->container->event->trigger("swoole.{$event}", $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件
|
||||
* @param string $event
|
||||
* @param $listener
|
||||
* @param bool $first
|
||||
*/
|
||||
public function onEvent(string $event, $listener, bool $first = false): void
|
||||
{
|
||||
$this->container->event->listen("swoole.{$event}", $listener, $first);
|
||||
}
|
||||
|
||||
protected function prepareApplication()
|
||||
{
|
||||
if (!$this->app instanceof SwooleApp) {
|
||||
$this->app = new SwooleApp($this->container->getRootPath());
|
||||
$this->app->bind(SwooleApp::class, App::class);
|
||||
$this->app->bind(Server::class, $this->getServer());
|
||||
$this->app->bind("swoole.server", Server::class);
|
||||
//绑定连接池
|
||||
if ($this->getConfig('pool.db.enable', true)) {
|
||||
$this->app->bind('db', Db::class);
|
||||
}
|
||||
if ($this->getConfig('pool.cache.enable', true)) {
|
||||
$this->app->bind('cache', Cache::class);
|
||||
}
|
||||
$this->app->initialize();
|
||||
$this->prepareConcretes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载
|
||||
*/
|
||||
protected function prepareConcretes()
|
||||
{
|
||||
$defaultConcretes = ['db', 'cache', 'event'];
|
||||
|
||||
$concretes = array_merge($defaultConcretes, $this->getConfig('concretes', []));
|
||||
|
||||
foreach ($concretes as $concrete) {
|
||||
if ($this->app->has($concrete)) {
|
||||
$this->app->make($concrete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取沙箱
|
||||
* @return Sandbox
|
||||
*/
|
||||
protected function getSandbox()
|
||||
{
|
||||
return $this->app->make(Sandbox::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在沙箱中执行
|
||||
* @param Closure $callable
|
||||
* @param null $fd
|
||||
* @param bool $persistent
|
||||
*/
|
||||
protected function runInSandbox(Closure $callable, $fd = null, $persistent = false)
|
||||
{
|
||||
try {
|
||||
$this->getSandbox()->run($callable, $fd, $persistent);
|
||||
} catch (Throwable $e) {
|
||||
$this->logServerError($e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user