diff --git a/.gitignore b/.gitignore
index 8918ec05..b56fa873 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
.DS_Store
phpunit.xml
composer.lock
+.scannerwork
# Folders
vendor
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
new file mode 100644
index 00000000..90494730
--- /dev/null
+++ b/.scrutinizer.yml
@@ -0,0 +1,12 @@
+build:
+ environment:
+ php: 7.2
+
+filter:
+ excluded_paths:
+ - "tests/*"
+ - "routes/*"
+ - "config/*"
+ - "stubs/*"
+ dependency_paths:
+ - "vendor/*"
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 6709f198..a90e95ac 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,26 +4,24 @@ sudo: false
matrix:
include:
- - php: 7.2
- env: FRAMEWORK_VERSION=laravel/framework:5.1.*
- - php: 7.2
- env: FRAMEWORK_VERSION=laravel/framework:5.2.*
- - php: 7.2
- env: FRAMEWORK_VERSION=laravel/framework:5.3.*
- - php: 7.2
- env: FRAMEWORK_VERSION=laravel/framework:5.4.*
- php: 7.2
env: FRAMEWORK_VERSION=laravel/framework:5.5.*
- php: 7.2
- env: FRAMEWORK_VERSION=laravel/lumen-framework:5.1.*
+ env: FRAMEWORK_VERSION=laravel/framework:5.6.*
- php: 7.2
- env: FRAMEWORK_VERSION=laravel/lumen-framework:5.2.*
+ env: FRAMEWORK_VERSION=laravel/framework:5.7.*
- php: 7.2
- env: FRAMEWORK_VERSION=laravel/lumen-framework:5.3.*
+ env: FRAMEWORK_VERSION=laravel/framework:5.8.*
- php: 7.2
- env: FRAMEWORK_VERSION=laravel/lumen-framework:5.4.*
+ env: FRAMEWORK_VERSION=laravel/framework:6.0.*
- php: 7.2
env: FRAMEWORK_VERSION=laravel/lumen-framework:5.5.*
+ - php: 7.2
+ env: FRAMEWORK_VERSION=laravel/lumen-framework:5.6.*
+ - php: 7.2
+ env: FRAMEWORK_VERSION=laravel/lumen-framework:5.7.*
+ # - php: 7.2
+ # env: FRAMEWORK_VERSION=laravel/lumen-framework:5.8.*
before_install:
- printf "\n" | pecl install swoole
@@ -37,4 +35,4 @@ script:
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml
after_success:
- - vendor/bin/coveralls -v
+ - vendor/bin/php-coveralls -v
diff --git a/README.md b/README.md
index 5fef1611..1a244625 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Laravel-Swoole
-![php-badge](https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg)
+![php-badge](https://img.shields.io/badge/php-%3E%3D%207.2-8892BF.svg)
[![packagist-badge](https://img.shields.io/packagist/v/swooletw/laravel-swoole.svg)](https://packagist.org/packages/swooletw/laravel-swoole)
[![Total Downloads](https://poser.pugx.org/swooletw/laravel-swoole/downloads)](https://packagist.org/packages/swooletw/laravel-swoole)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/swooletw/laravel-swoole/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/swooletw/laravel-swoole/?branch=master)
@@ -12,7 +12,7 @@ This package provides a high performance HTTP server to speed up your Laravel/Lu
| PHP | Laravel | Lumen | Swoole |
|:-------:|:-------:|:-----:|:-------:|
-| >=7.1 | ~5.1 | ~5.1 | >=4.0.0 |
+| >=7.2 | >=5.5 | >=5.5 | >=4.3.1 |
## Features
diff --git a/composer.json b/composer.json
index 85b7537c..931c8ecb 100644
--- a/composer.json
+++ b/composer.json
@@ -1,60 +1,73 @@
{
- "name": "swooletw/laravel-swoole",
- "description": "High performance HTTP server based on Swoole. Speed up your Laravel and Lumen applications.",
- "keywords": ["swoole", "laravel", "lumen", "performance", "http", "server"],
- "license": "MIT",
- "authors": [
- {
- "name": "Albert Chen",
- "email": "albert@unisharp.com"
- },
- {
- "name": "Huang Yi",
- "email": "coodeer@163.com"
- }
- ],
- "require": {
- "php": "^7.1",
- "illuminate/console": "~5.1",
- "illuminate/contracts": "~5.1",
- "illuminate/http": "~5.1",
- "illuminate/support": "~5.1",
- "predis/predis": "^1.1"
- },
- "require-dev": {
- "laravel/lumen-framework": "~5.1",
- "phpunit/phpunit": "^6.1",
- "phpunit/php-code-coverage": "^5.2",
- "satooshi/php-coveralls": "^1.0",
- "mockery/mockery": "~1.0",
- "codedungeon/phpunit-result-printer": "^0.14.0",
- "php-mock/php-mock": "^2.0"
- },
- "autoload": {
- "files": [
- "src/Server/helpers.php"
- ],
- "psr-4": {
- "SwooleTW\\Http\\": "src"
- }
- },
- "autoload-dev": {
- "psr-4": {
- "SwooleTW\\Http\\Tests\\": "tests",
- "SwooleTW\\Http\\Tests\\Fixtures\\Laravel\\App\\": "tests/fixtures/laravel/app"
- }
+ "name": "swooletw/laravel-swoole",
+ "description": "High performance HTTP server based on Swoole. Speed up your Laravel and Lumen applications.",
+ "keywords": [
+ "swoole",
+ "laravel",
+ "lumen",
+ "performance",
+ "http",
+ "server"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Albert Chen",
+ "email": "albert@unisharp.com"
},
- "extra": {
- "laravel": {
- "providers": [
- "SwooleTW\\Http\\LaravelServiceProvider"
- ],
- "aliases": {
- "Server": "SwooleTW\\Http\\Server\\Facades\\Server",
- "Table": "SwooleTW\\Http\\Server\\Facades\\Table",
- "Room": "SwooleTW\\Http\\Websocket\\Facades\\Room",
- "Websocket": "SwooleTW\\Http\\Websocket\\Facades\\Websocket"
- }
- }
+ {
+ "name": "Huang Yi",
+ "email": "coodeer@163.com"
+ }
+ ],
+ "require": {
+ "php": "^7.2",
+ "illuminate/console": "~5.4|~6.0|~7.0|~8.0",
+ "illuminate/contracts": "~5.4|~6.0|~7.0|~8.0",
+ "illuminate/http": "~5.4|~6.0|~7.0|~8.0",
+ "illuminate/support": "~5.4|~6.0|~7.0|~8.0",
+ "predis/predis": "^1.1"
+ },
+ "require-dev": {
+ "laravel/lumen-framework": "~5.4|~6.0|~7.0|~8.0",
+ "phpunit/phpunit": "^7.5",
+ "phpunit/php-code-coverage": "^6.1",
+ "php-coveralls/php-coveralls": "^2.1",
+ "mockery/mockery": "~1.0",
+ "codedungeon/phpunit-result-printer": "^0.14.0",
+ "php-mock/php-mock": "^2.0",
+ "swoole/ide-helper": "@dev"
+ },
+ "autoload": {
+ "files": [
+ "src/Server/helpers.php"
+ ],
+ "psr-4": {
+ "SwooleTW\\Http\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "SwooleTW\\Http\\Tests\\": "tests",
+ "SwooleTW\\Http\\Tests\\Fixtures\\Laravel\\App\\": "tests/fixtures/laravel/app"
+ }
+ },
+ "extra": {
+ "laravel": {
+ "providers": [
+ "SwooleTW\\Http\\LaravelServiceProvider"
+ ],
+ "aliases": {
+ "Server": "SwooleTW\\Http\\Server\\Facades\\Server",
+ "Table": "SwooleTW\\Http\\Server\\Facades\\Table",
+ "Room": "SwooleTW\\Http\\Websocket\\Facades\\Room",
+ "Websocket": "SwooleTW\\Http\\Websocket\\Facades\\Websocket"
+ }
}
+ },
+ "scripts": {
+ "post-autoload-dump": [
+ "@php copy_versioned_files.php"
+ ]
+ }
}
diff --git a/config/swoole_http.php b/config/swoole_http.php
index 2f70159c..150e680f 100644
--- a/config/swoole_http.php
+++ b/config/swoole_http.php
@@ -1,7 +1,5 @@
base_path('public'),
// Determine if to use swoole to respond request for static files
'handle_static_files' => env('SWOOLE_HANDLE_STATIC', true),
+ 'access_log' => env('SWOOLE_HTTP_ACCESS_LOG', false),
// You must add --enable-openssl while compiling Swoole
// Put `SWOOLE_SOCK_TCP | SWOOLE_SSL` if you want to enable SSL
'socket_type' => SWOOLE_SOCK_TCP,
+ 'process_type' => SWOOLE_PROCESS,
'options' => [
'pid_file' => env('SWOOLE_HTTP_PID_FILE', base_path('storage/logs/swoole_http.pid')),
'log_file' => env('SWOOLE_HTTP_LOG_FILE', base_path('storage/logs/swoole_http.log')),
@@ -53,6 +53,19 @@
'enabled' => env('SWOOLE_HTTP_WEBSOCKET', false),
],
+ /*
+ |--------------------------------------------------------------------------
+ | Hot reload configuration
+ |--------------------------------------------------------------------------
+ */
+ 'hot_reload' => [
+ 'enabled' => env('SWOOLE_HOT_RELOAD_ENABLE', false),
+ 'recursively' => env('SWOOLE_HOT_RELOAD_RECURSIVELY', true),
+ 'directory' => env('SWOOLE_HOT_RELOAD_DIRECTORY', base_path()),
+ 'log' => env('SWOOLE_HOT_RELOAD_LOG', true),
+ 'filter' => env('SWOOLE_HOT_RELOAD_FILTER', '.php'),
+ ],
+
/*
|--------------------------------------------------------------------------
| Console output will be transferred to response content if enabled.
@@ -68,7 +81,7 @@
'pre_resolved' => [
'view', 'files', 'session', 'session.store', 'routes',
'db', 'db.factory', 'cache', 'cache.store', 'config', 'cookie',
- 'encrypter', 'hash', 'router', 'translator', 'url', 'log',
+ 'encrypter', 'hash', 'router', 'translator', 'url', 'log', 'auth',
],
/*
@@ -120,5 +133,5 @@
// ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024],
// ]
// ],
- ]
+ ],
];
diff --git a/config/swoole_websocket.php b/config/swoole_websocket.php
index a9c770da..867b9df8 100644
--- a/config/swoole_websocket.php
+++ b/config/swoole_websocket.php
@@ -35,6 +35,16 @@
// SwooleTW\Http\Websocket\Middleware\Authenticate::class,
],
+ /*
+ |--------------------------------------------------------------------------
+ | Websocket handler for customized onHandShake callback
+ |--------------------------------------------------------------------------
+ */
+ 'handshake' => [
+ 'enabled' => false,
+ 'handler' => SwooleTW\Http\Websocket\HandShakeHandler::class,
+ ],
+
/*
|--------------------------------------------------------------------------
| Default websocket driver
@@ -77,7 +87,7 @@
'room_rows' => 4096,
'room_size' => 2048,
'client_rows' => 8192,
- 'client_size' => 2048
+ 'client_size' => 2048,
],
'redis' => [
@@ -92,6 +102,6 @@
//
],
'prefix' => 'swoole:',
- ]
+ ],
],
];
diff --git a/copy_versioned_files.php b/copy_versioned_files.php
new file mode 100644
index 00000000..f4f0b718
--- /dev/null
+++ b/copy_versioned_files.php
@@ -0,0 +1,33 @@
+
-
-
- ./tests
-
-
./src
+
+
+ ./tests
+
+
diff --git a/routes/laravel_routes.php b/routes/laravel_routes.php
index 9afb5f6e..53564c96 100644
--- a/routes/laravel_routes.php
+++ b/routes/laravel_routes.php
@@ -1,5 +1,6 @@
get('socket.io', [
- 'as' => 'io.get', 'uses' => 'SocketIOController@upgrade'
-]);
-
-$app->post('socket.io', [
- 'as' => 'io.post', 'uses' => 'SocketIOController@reject'
-]);
+$router->get('socket.io', ['uses' => 'SocketIOController@upgrade']);
+$router->post('socket.io', ['uses' => 'SocketIOController@reject']);
diff --git a/routes/websocket.php b/routes/websocket.php
index a956ac7d..ddebfe14 100644
--- a/routes/websocket.php
+++ b/routes/websocket.php
@@ -1,5 +1,6 @@
emit('message', $data);
});
-
-// Websocket::on('test', 'ExampleController@method');
\ No newline at end of file
diff --git a/src/Commands/HttpServerCommand.php b/src/Commands/HttpServerCommand.php
index 157c9b54..5ed1d959 100644
--- a/src/Commands/HttpServerCommand.php
+++ b/src/Commands/HttpServerCommand.php
@@ -4,7 +4,20 @@
use Throwable;
use Swoole\Process;
+use Illuminate\Support\Arr;
+use SwooleTW\Http\Helpers\OS;
use Illuminate\Console\Command;
+use SwooleTW\Http\Server\Manager;
+use Illuminate\Console\OutputStyle;
+use SwooleTW\Http\HotReload\FSEvent;
+use SwooleTW\Http\HotReload\FSOutput;
+use SwooleTW\Http\HotReload\FSProcess;
+use SwooleTW\Http\Server\AccessOutput;
+use SwooleTW\Http\Server\PidManager;
+use SwooleTW\Http\Middleware\AccessLog;
+use SwooleTW\Http\Server\Facades\Server;
+use Illuminate\Contracts\Container\Container;
+use Symfony\Component\Console\Output\ConsoleOutput;
/**
* @codeCoverageIgnore
@@ -38,14 +51,14 @@ class HttpServerCommand extends Command
*
* @var int
*/
- protected $pid;
+ protected $currentPid;
/**
* The configs for this package.
*
* @var array
*/
- protected $configs;
+ protected $config;
/**
* Execute the console command.
@@ -65,7 +78,7 @@ public function handle()
*/
protected function loadConfigs()
{
- $this->configs = $this->laravel['config']->get('swoole_http');
+ $this->config = $this->laravel->make('config')->get('swoole_http');
}
/**
@@ -81,22 +94,38 @@ protected function runAction()
*/
protected function start()
{
- if ($this->isRunning($this->getPid())) {
+ if ($this->isRunning()) {
$this->error('Failed! swoole_http_server process is already running.');
- exit(1);
+
+ return;
}
- $host = $this->configs['server']['host'];
- $port = $this->configs['server']['port'];
+ $host = Arr::get($this->config, 'server.host');
+ $port = Arr::get($this->config, 'server.port');
+ $hotReloadEnabled = Arr::get($this->config, 'hot_reload.enabled');
+ $accessLogEnabled = Arr::get($this->config, 'server.access_log');
$this->info('Starting swoole http server...');
$this->info("Swoole http server started: ");
if ($this->isDaemon()) {
- $this->info('> (You can run this command to ensure the ' .
- 'swoole_http_server process is running: ps aux|grep "swoole")');
+ $this->info(
+ '> (You can run this command to ensure the ' .
+ 'swoole_http_server process is running: ps aux|grep "swoole")'
+ );
+ }
+
+ $manager = $this->laravel->make(Manager::class);
+ $server = $this->laravel->make(Server::class);
+
+ if ($accessLogEnabled) {
+ $this->registerAccessLog();
}
- $this->laravel->make('swoole.manager')->run();
+ if ($hotReloadEnabled) {
+ $manager->addProcess($this->getHotReloadProcess($server));
+ }
+
+ $manager->run();
}
/**
@@ -104,25 +133,25 @@ protected function start()
*/
protected function stop()
{
- $pid = $this->getPid();
-
- if (! $this->isRunning($pid)) {
+ if (! $this->isRunning()) {
$this->error("Failed! There is no swoole_http_server process running.");
- exit(1);
+
+ return;
}
$this->info('Stopping swoole http server...');
- $isRunning = $this->killProcess($pid, SIGTERM, 15);
+ $isRunning = $this->killProcess(SIGTERM, 15);
if ($isRunning) {
$this->error('Unable to stop the swoole_http_server process.');
- exit(1);
+
+ return;
}
// I don't known why Swoole didn't trigger "onShutdown" after sending SIGTERM.
// So we should manually remove the pid file.
- $this->removePidFile();
+ $this->laravel->make(PidManager::class)->delete();
$this->info('> success');
}
@@ -132,9 +161,7 @@ protected function stop()
*/
protected function restart()
{
- $pid = $this->getPid();
-
- if ($this->isRunning($pid)) {
+ if ($this->isRunning()) {
$this->stop();
}
@@ -146,20 +173,18 @@ protected function restart()
*/
protected function reload()
{
- $pid = $this->getPid();
-
- if (! $this->isRunning($pid)) {
+ if (! $this->isRunning()) {
$this->error("Failed! There is no swoole_http_server process running.");
- exit(1);
+
+ return;
}
$this->info('Reloading swoole_http_server...');
- $isRunning = $this->killProcess($pid, SIGUSR1);
-
- if (! $isRunning) {
+ if (! $this->killProcess(SIGUSR1)) {
$this->error('> failure');
- exit(1);
+
+ return;
}
$this->info('> success');
@@ -175,20 +200,21 @@ protected function infos()
/**
* Display PHP and Swoole miscs infos.
- *
- * @param bool $more
*/
protected function showInfos()
{
- $pid = $this->getPid();
- $isRunning = $this->isRunning($pid);
- $host = $this->configs['server']['host'];
- $port = $this->configs['server']['port'];
- $reactorNum = $this->configs['server']['options']['reactor_num'];
- $workerNum = $this->configs['server']['options']['worker_num'];
- $taskWorkerNum = $this->configs['server']['options']['task_worker_num'];
- $isWebsocket = $this->configs['websocket']['enabled'];
- $logFile = $this->configs['server']['options']['log_file'];
+ $isRunning = $this->isRunning();
+ $host = Arr::get($this->config, 'server.host');
+ $port = Arr::get($this->config, 'server.port');
+ $reactorNum = Arr::get($this->config, 'server.options.reactor_num');
+ $workerNum = Arr::get($this->config, 'server.options.worker_num');
+ $taskWorkerNum = Arr::get($this->config, 'server.options.task_worker_num');
+ $isWebsocket = Arr::get($this->config, 'websocket.enabled');
+ $hasTaskWorker = $isWebsocket || Arr::get($this->config, 'queue.default') === 'swoole';
+ $logFile = Arr::get($this->config, 'server.options.log_file');
+ $pids = $this->laravel->make(PidManager::class)->read();
+ $masterPid = $pids['masterPid'] ?? null;
+ $managerPid = $pids['managerPid'] ?? null;
$table = [
['PHP Version', 'Version' => phpversion()],
@@ -199,9 +225,10 @@ protected function showInfos()
['Server Status', $isRunning ? 'Online' : 'Offline'],
['Reactor Num', $reactorNum],
['Worker Num', $workerNum],
- ['Task Worker Num', $isWebsocket ? $taskWorkerNum : 0],
+ ['Task Worker Num', $hasTaskWorker ? $taskWorkerNum : 0],
['Websocket Mode', $isWebsocket ? 'On' : 'Off'],
- ['PID', $isRunning ? $pid : 'None'],
+ ['Master PID', $isRunning ? $masterPid : 'None'],
+ ['Manager PID', $isRunning && $managerPid ? $managerPid : 'None'],
['Log Path', $logFile],
];
@@ -215,48 +242,82 @@ protected function initAction()
{
$this->action = $this->argument('action');
- if (! in_array($this->action, ['start', 'stop', 'restart', 'reload', 'infos'])) {
- $this->error("Invalid argument '{$this->action}'. Expected 'start', 'stop', 'restart', 'reload' or 'infos'.");
- exit(1);
+ if (! in_array($this->action, ['start', 'stop', 'restart', 'reload', 'infos'], true)) {
+ $this->error(
+ "Invalid argument '{$this->action}'. Expected 'start', 'stop', 'restart', 'reload' or 'infos'."
+ );
+
+ return;
}
}
+ /**
+ * @param \SwooleTW\Http\Server\Facades\Server $server
+ *
+ * @return \Swoole\Process
+ */
+ protected function getHotReloadProcess($server)
+ {
+ $recursively = Arr::get($this->config, 'hot_reload.recursively');
+ $directory = Arr::get($this->config, 'hot_reload.directory');
+ $filter = Arr::get($this->config, 'hot_reload.filter');
+ $log = Arr::get($this->config, 'hot_reload.log');
+
+ $cb = function (FSEvent $event) use ($server, $log) {
+ $log ? $this->info(FSOutput::format($event)) : null;
+ $server->reload();
+ };
+
+ return (new FSProcess($filter, $recursively, $directory))->make($cb);
+ }
+
/**
* If Swoole process is running.
*
* @param int $pid
+ *
* @return bool
*/
- protected function isRunning($pid)
+ public function isRunning()
{
- if (! $pid) {
+ $pids = $this->laravel->make(PidManager::class)->read();
+
+ if (! count($pids)) {
return false;
}
- try {
- return Process::kill($pid, 0);
- } catch (Throwable $e) {
- return false;
+ $masterPid = $pids['masterPid'] ?? null;
+ $managerPid = $pids['managerPid'] ?? null;
+
+ if ($managerPid) {
+ // Swoole process mode
+ return $masterPid && $managerPid && Process::kill((int) $managerPid, 0);
}
+
+ // Swoole base mode, no manager process
+ return $masterPid && Process::kill((int) $masterPid, 0);
}
/**
* Kill process.
*
- * @param int $pid
* @param int $sig
* @param int $wait
+ *
* @return bool
*/
- protected function killProcess($pid, $sig, $wait = 0)
+ protected function killProcess($sig, $wait = 0)
{
- Process::kill($pid, $sig);
+ Process::kill(
+ Arr::first($this->laravel->make(PidManager::class)->read()),
+ $sig
+ );
if ($wait) {
$start = time();
do {
- if (! $this->isRunning($pid)) {
+ if (! $this->isRunning()) {
break;
}
@@ -264,78 +325,56 @@ protected function killProcess($pid, $sig, $wait = 0)
} while (time() < $start + $wait);
}
- return $this->isRunning($pid);
+ return $this->isRunning();
}
/**
- * Get pid.
- *
- * @return int|null
+ * Return daemonize config.
*/
- protected function getPid()
+ protected function isDaemon(): bool
{
- if ($this->pid) {
- return $this->pid;
- }
-
- $pid = null;
- $path = $this->getPidPath();
-
- if (file_exists($path)) {
- $pid = (int) file_get_contents($path);
-
- if (! $pid) {
- $this->removePidFile();
- } else {
- $this->pid = $pid;
- }
- }
-
- return $this->pid;
+ return Arr::get($this->config, 'server.options.daemonize', false);
}
/**
- * Get Pid file path.
- *
- * @return string
+ * Check running enironment.
*/
- protected function getPidPath()
+ protected function checkEnvironment()
{
- return $this->configs['server']['options']['pid_file'];
- }
+ if (OS::is(OS::WIN)) {
+ $this->error('Swoole extension doesn\'t support Windows OS.');
- /**
- * Remove Pid file.
- */
- protected function removePidFile()
- {
- if (file_exists($this->getPidPath())) {
- unlink($this->getPidPath());
+ exit(1);
}
- }
- /**
- * Return daemonize config.
- */
- protected function isDaemon()
- {
- return $this->configs['server']['options']['daemonize'];
+ if (! extension_loaded('swoole')) {
+ $this->error('Can\'t detect Swoole extension installed.');
+
+ exit(1);
+ }
+
+ if (! version_compare(swoole_version(), '4.3.1', 'ge')) {
+ $this->error('Your Swoole version must be higher than `4.3.1`.');
+
+ exit(1);
+ }
}
/**
- * Check running enironment.
+ * Register access log services.
*/
- protected function checkEnvironment()
+ protected function registerAccessLog()
{
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
- $this->error("Swoole extension doesn't support Windows OS yet.");
- exit;
- } elseif (! extension_loaded('swoole')) {
- $this->error("Can't detect Swoole extension installed.");
- exit;
- } elseif (! version_compare(swoole_version(), '4.0.0', 'ge')) {
- $this->error("Your Swoole version must be higher than 4.0 to use coroutine.");
- exit;
- }
+ $this->laravel->singleton(OutputStyle::class, function () {
+ return new OutputStyle($this->input, $this->output);
+ });
+
+ $this->laravel->singleton(AccessOutput::class, function () {
+ return new AccessOutput(new ConsoleOutput);
+ });
+
+ $this->laravel->singleton(AccessLog::class, function (Container $container) {
+ return new AccessLog($container->make(AccessOutput::class));
+ });
}
}
diff --git a/src/Concerns/InteractsWithSwooleQueue.php b/src/Concerns/InteractsWithSwooleQueue.php
new file mode 100644
index 00000000..efed9c26
--- /dev/null
+++ b/src/Concerns/InteractsWithSwooleQueue.php
@@ -0,0 +1,25 @@
+table = new SwooleTable;
+ $this->currentTable = new SwooleTable;
$this->registerTables();
}
@@ -26,7 +33,7 @@ protected function createTables()
*/
protected function registerTables()
{
- $tables = $this->container['config']->get('swoole_http.tables', []);
+ $tables = $this->container->make('config')->get('swoole_http.tables', []);
foreach ($tables as $key => $value) {
$table = new Table($value['size']);
@@ -40,7 +47,7 @@ protected function registerTables()
}
$table->create();
- $this->table->add($key, $table);
+ $this->currentTable->add($key, $table);
}
}
@@ -49,9 +56,12 @@ protected function registerTables()
*/
protected function bindSwooleTable()
{
- $this->app->singleton(SwooleTable::class, function () {
- return $this->table;
- });
- $this->app->alias(SwooleTable::class, 'swoole.table');
+ if (! $this->app instanceof ConsoleApp) {
+ $this->app->singleton(SwooleTable::class, function () {
+ return $this->currentTable;
+ });
+
+ $this->app->alias(SwooleTable::class, 'swoole.table');
+ }
}
}
diff --git a/src/Concerns/InteractsWithWebsocket.php b/src/Concerns/InteractsWithWebsocket.php
index c22e1cfc..6872529e 100644
--- a/src/Concerns/InteractsWithWebsocket.php
+++ b/src/Concerns/InteractsWithWebsocket.php
@@ -3,32 +3,47 @@
namespace SwooleTW\Http\Concerns;
use Throwable;
-use Swoole\Websocket\Frame;
-use Swoole\Websocket\Server;
use Illuminate\Pipeline\Pipeline;
+use SwooleTW\Http\Server\Sandbox;
use SwooleTW\Http\Websocket\Parser;
-use Illuminate\Support\Facades\Facade;
+use SwooleTW\Http\Websocket\Pusher;
use SwooleTW\Http\Websocket\Websocket;
use SwooleTW\Http\Transformers\Request;
+use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Websocket\HandlerContract;
+use Illuminate\Contracts\Container\Container;
+use Swoole\WebSocket\Server as WebsocketServer;
use SwooleTW\Http\Websocket\Rooms\RoomContract;
-
+use SwooleTW\Http\Exceptions\WebsocketNotSetInConfigException;
+
+/**
+ * Trait InteractsWithWebsocket
+ *
+ * @property \Illuminate\Contracts\Container\Container $container
+ * @property \Illuminate\Contracts\Container\Container $app
+ * @property array $types
+ */
trait InteractsWithWebsocket
{
/**
* @var boolean
*/
- protected $isWebsocket = false;
+ protected $isServerWebsocket = false;
/**
- * @var SwooleTW\Http\Websocket\HandlerContract
+ * @var \SwooleTW\Http\Websocket\HandlerContract
*/
protected $websocketHandler;
/**
- * @var SwooleTW\Http\Websocket\Parser
+ * @var \SwooleTW\Http\Websocket\Parser
*/
- protected $parser;
+ protected $payloadParser;
+
+ /**
+ * @var \SwooleTW\Http\Websocket\Rooms\RoomContract
+ */
+ protected $websocketRoom;
/**
* Websocket server events.
@@ -37,37 +52,60 @@ trait InteractsWithWebsocket
*/
protected $wsEvents = ['open', 'message', 'close'];
+ /**
+ * "onHandShake" listener.
+ * @param \Swoole\Http\Request $swooleRequest
+ * @param \Swoole\Http\Response $response
+ */
+ public function onHandShake($swooleRequest, $response)
+ {
+ $this->onOpen(
+ $this->app->make(Server::class),
+ $swooleRequest,
+ $response
+ );
+ }
+
/**
* "onOpen" listener.
*
* @param \Swoole\Websocket\Server $server
* @param \Swoole\Http\Request $swooleRequest
+ * @param \Swoole\Http\Response $response (optional)
*/
- public function onOpen($server, $swooleRequest)
+ public function onOpen($server, $swooleRequest, $response = null)
{
$illuminateRequest = Request::make($swooleRequest)->toIlluminate();
+ $websocket = $this->app->make(Websocket::class);
+ $sandbox = $this->app->make(Sandbox::class);
+ $handshakeHandler = $this->app->make('config')
+ ->get('swoole_websocket.handshake.handler');
try {
- $this->app['swoole.websocket']->reset(true)->setSender($swooleRequest->fd);
+ $websocket->reset(true)->setSender($swooleRequest->fd);
// set currnt request to sandbox
- $this->app['swoole.sandbox']->setRequest($illuminateRequest);
+ $sandbox->setRequest($illuminateRequest);
// enable sandbox
- $this->app['swoole.sandbox']->enable();
+ $sandbox->enable();
+ // call customized handshake handler
+ if ($response && ! $this->app->make($handshakeHandler)->handle($swooleRequest, $response)) {
+ return;
+ }
// check if socket.io connection established
if (! $this->websocketHandler->onOpen($swooleRequest->fd, $illuminateRequest)) {
return;
}
// trigger 'connect' websocket event
- if ($this->app['swoole.websocket']->eventExists('connect')) {
+ if ($websocket->eventExists('connect')) {
// set sandbox container to websocket pipeline
- $this->app['swoole.websocket']->setContainer($this->app['swoole.sandbox']->getApplication());
- $this->app['swoole.websocket']->call('connect', $illuminateRequest);
+ $websocket->setContainer($sandbox->getApplication());
+ $websocket->call('connect', $illuminateRequest);
}
} catch (Throwable $e) {
$this->logServerError($e);
} finally {
// disable and recycle sandbox resource
- $this->app['swoole.sandbox']->disable();
+ $sandbox->disable();
}
}
@@ -79,31 +117,33 @@ public function onOpen($server, $swooleRequest)
*/
public function onMessage($server, $frame)
{
- try {
- // execute parser strategies and skip non-message packet
- if ($this->parser->execute($server, $frame)) {
- return;
- }
+ // execute parser strategies and skip non-message packet
+ if ($this->payloadParser->execute($server, $frame)) {
+ return;
+ }
+ $websocket = $this->app->make(Websocket::class);
+ $sandbox = $this->app->make(Sandbox::class);
+
+ try {
// decode raw message via parser
- $payload = $this->parser->decode($frame);
+ $payload = $this->payloadParser->decode($frame);
- $this->app['swoole.websocket']->reset(true)->setSender($frame->fd);
+ $websocket->reset(true)->setSender($frame->fd);
// enable sandbox
- $this->app['swoole.sandbox']->enable();
+ $sandbox->enable();
// dispatch message to registered event callback
- if ($this->app['swoole.websocket']->eventExists($payload['event'])) {
- $this->app['swoole.websocket']->call($payload['event'], $payload['data']);
- } else {
- $this->websocketHandler->onMessage($frame);
- }
+ ['event' => $event, 'data' => $data] = $payload;
+ $websocket->eventExists($event)
+ ? $websocket->call($event, $data)
+ : $this->websocketHandler->onMessage($frame);
} catch (Throwable $e) {
$this->logServerError($e);
} finally {
// disable and recycle sandbox resource
- $this->app['swoole.sandbox']->disable();
+ $sandbox->disable();
}
}
@@ -116,25 +156,46 @@ public function onMessage($server, $frame)
*/
public function onClose($server, $fd, $reactorId)
{
- if (! $this->isWebsocket($fd)) {
+ if (! $this->isServerWebsocket($fd) || ! $server instanceof WebsocketServer) {
return;
}
+ $websocket = $this->app->make(Websocket::class);
+
try {
- $this->app['swoole.websocket']->reset(true)->setSender($fd);
+ $websocket->reset(true)->setSender($fd);
// trigger 'disconnect' websocket event
- if ($this->app['swoole.websocket']->eventExists('disconnect')) {
- $this->app['swoole.websocket']->call('disconnect');
+ if ($websocket->eventExists('disconnect')) {
+ $websocket->call('disconnect');
} else {
$this->websocketHandler->onClose($fd, $reactorId);
}
// leave all rooms
- $this->app['swoole.websocket']->leave();
+ $websocket->leave();
} catch (Throwable $e) {
$this->logServerError($e);
}
}
+ /**
+ * Indicates if a packet is websocket push action.
+ *
+ * @param mixed
+ *
+ * @return bool
+ */
+ protected function isWebsocketPushPacket($packet)
+ {
+ if (! is_array($packet)) {
+ return false;
+ }
+
+ return $this->isServerWebsocket
+ && array_key_exists('action', $packet)
+ && $packet['action'] === Websocket::PUSH_ACTION;
+ }
+
+
/**
* Push websocket message to clients.
*
@@ -143,40 +204,23 @@ public function onClose($server, $fd, $reactorId)
*/
public function pushMessage($server, array $data)
{
- [$opcode, $sender, $fds, $broadcast, $assigned, $event, $message] = $this->normalizePushData($data);
- $message = $this->parser->encode($event, $message);
-
- // attach sender if not broadcast
- if (! $broadcast && $sender && ! in_array($sender, $fds)) {
- $fds[] = $sender;
- }
-
- // check if to broadcast all clients
- if ($broadcast && empty($fds) && ! $assigned) {
- foreach ($server->connections as $fd) {
- if ($this->isWebsocket($fd)) {
- $fds[] = $fd;
- }
- }
- }
-
- // push message to designated fds
- foreach ($fds as $fd) {
- if (($broadcast && $sender === (integer) $fd) || ! $server->exist($fd)) {
- continue;
- }
- $server->push($fd, $message, $opcode);
- }
+ $pusher = Pusher::make($data, $server);
+ $pusher->push($this->payloadParser->encode(
+ $pusher->getEvent(),
+ $pusher->getMessage()
+ ));
}
/**
* Set frame parser for websocket.
*
- * @param \SwooleTW\Http\Websocket\Parser $parser
+ * @param \SwooleTW\Http\Websocket\Parser $payloadParser
+ *
+ * @return \SwooleTW\Http\Concerns\InteractsWithWebsocket
*/
- public function setParser(Parser $parser)
+ public function setPayloadParser(Parser $payloadParser)
{
- $this->parser = $parser;
+ $this->payloadParser = $payloadParser;
return $this;
}
@@ -184,9 +228,9 @@ public function setParser(Parser $parser)
/**
* Get frame parser for websocket.
*/
- public function getParser()
+ public function getPayloadParser()
{
- return $this->parser;
+ return $this->payloadParser;
}
/**
@@ -194,42 +238,74 @@ public function getParser()
*/
protected function prepareWebsocket()
{
- $isWebsocket = $this->container['config']->get('swoole_http.websocket.enabled');
- $parser = $this->container['config']->get('swoole_websocket.parser');
+ $config = $this->container->make('config');
+ $parser = $config->get('swoole_websocket.parser');
- if ($isWebsocket) {
- array_push($this->events, ...$this->wsEvents);
- $this->isWebsocket = true;
- $this->setParser(new $parser);
+ if (! $this->isServerWebsocket = $config->get('swoole_http.websocket.enabled')) {
+ return;
}
+
+ if ($config->get('swoole_websocket.handshake.enabled')) {
+ $this->wsEvents = array_merge($this->wsEvents, ['handshake']);
+ }
+
+ $this->events = array_merge($this->events ?? [], $this->wsEvents);
+ $this->prepareWebsocketRoom();
+ $this->setPayloadParser(new $parser);
}
/**
* Check if it's a websocket fd.
+ *
+ * @param int $fd
+ *
+ * @return bool
*/
- protected function isWebsocket(int $fd)
+ protected function isServerWebsocket(int $fd): bool
{
- $info = $this->container['swoole.server']->connection_info($fd);
-
- return array_key_exists('websocket_status', $info) && $info['websocket_status'];
+ return array_key_exists(
+ 'websocket_status',
+ $this->container->make(Server::class)
+ ->connection_info($fd)
+ );
}
/**
* Prepare websocket handler for onOpen and onClose callback.
+ *
+ * @throws \Exception
*/
protected function prepareWebsocketHandler()
{
- $handlerClass = $this->container['config']->get('swoole_websocket.handler');
+ $handlerClass = $this->container->make('config')->get('swoole_websocket.handler');
if (! $handlerClass) {
- throw new Exception('Websocket handler is not set in swoole_websocket config');
+ throw new WebsocketNotSetInConfigException;
}
$this->setWebsocketHandler($this->app->make($handlerClass));
}
+ /**
+ * Prepare websocket room.
+ */
+ protected function prepareWebsocketRoom()
+ {
+ $config = $this->container->make('config');
+ $driver = $config->get('swoole_websocket.default');
+ $websocketConfig = $config->get("swoole_websocket.settings.{$driver}");
+ $className = $config->get("swoole_websocket.drivers.{$driver}");
+
+ $this->websocketRoom = new $className($websocketConfig);
+ $this->websocketRoom->prepare();
+ }
+
/**
* Set websocket handler.
+ *
+ * @param \SwooleTW\Http\Websocket\HandlerContract $handler
+ *
+ * @return \SwooleTW\Http\Concerns\InteractsWithWebsocket
*/
public function setWebsocketHandler(HandlerContract $handler)
{
@@ -240,35 +316,34 @@ public function setWebsocketHandler(HandlerContract $handler)
/**
* Get websocket handler.
+ *
+ * @return \SwooleTW\Http\Websocket\HandlerContract
*/
- public function getWebsocketHandler()
+ public function getWebsocketHandler(): HandlerContract
{
return $this->websocketHandler;
}
/**
- * Get websocket handler for onOpen and onClose callback.
+ * @param string $class
+ * @param array $settings
+ *
+ * @return \SwooleTW\Http\Websocket\Rooms\RoomContract
*/
- protected function getWebsocketRoom()
+ protected function createRoom(string $class, array $settings): RoomContract
{
- $driver = $this->container['config']->get('swoole_websocket.default');
- $configs = $this->container['config']->get("swoole_websocket.settings.{$driver}");
- $className = $this->container['config']->get("swoole_websocket.drivers.{$driver}");
-
- $websocketRoom = new $className($configs);
- $websocketRoom->prepare();
-
- return $websocketRoom;
+ return new $class($settings);
}
/**
* Bind room instance to Laravel app container.
*/
- protected function bindRoom()
+ protected function bindRoom(): void
{
- $this->app->singleton(RoomContract::class, function ($app) {
- return $this->getWebsocketRoom();
+ $this->app->singleton(RoomContract::class, function () {
+ return $this->websocketRoom;
});
+
$this->app->alias(RoomContract::class, 'swoole.room');
}
@@ -277,9 +352,10 @@ protected function bindRoom()
*/
protected function bindWebsocket()
{
- $this->app->singleton(Websocket::class, function ($app) {
- return new Websocket($app['swoole.room'], new Pipeline($app));
+ $this->app->singleton(Websocket::class, function (Container $app) {
+ return new Websocket($app->make(RoomContract::class), new Pipeline($app));
});
+
$this->app->alias(Websocket::class, 'swoole.websocket');
}
@@ -288,7 +364,8 @@ protected function bindWebsocket()
*/
protected function loadWebsocketRoutes()
{
- $routePath = $this->container['config']->get('swoole_websocket.route_file');
+ $routePath = $this->container->make('config')
+ ->get('swoole_websocket.route_file');
if (! file_exists($routePath)) {
$routePath = __DIR__ . '/../../routes/websocket.php';
@@ -298,18 +375,20 @@ protected function loadWebsocketRoutes()
}
/**
- * Normalize data for message push.
+ * Indicates if the payload is websocket push.
+ *
+ * @param mixed $payload
+ *
+ * @return boolean
*/
- public function normalizePushData(array $data)
+ public function isWebsocketPushPayload($payload): bool
{
- $opcode = $data['opcode'] ?? 1;
- $sender = $data['sender'] ?? 0;
- $fds = $data['fds'] ?? [];
- $broadcast = $data['broadcast'] ?? false;
- $assigned = $data['assigned'] ?? false;
- $event = $data['event'] ?? null;
- $message = $data['message'] ?? null;
-
- return [$opcode, $sender, $fds, $broadcast, $assigned, $event, $message];
+ if (! is_array($payload)) {
+ return false;
+ }
+
+ return $this->isServerWebsocket
+ && ($payload['action'] ?? null) === Websocket::PUSH_ACTION
+ && array_key_exists('data', $payload);
}
}
diff --git a/src/Concerns/ResetApplication.php b/src/Concerns/ResetApplication.php
index 6f86569f..d6a49593 100644
--- a/src/Concerns/ResetApplication.php
+++ b/src/Concerns/ResetApplication.php
@@ -2,6 +2,7 @@
namespace SwooleTW\Http\Concerns;
+use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Container\Container;
use SwooleTW\Http\Exceptions\SandboxException;
use SwooleTW\Http\Server\Resetters\ResetterContract;
@@ -19,7 +20,7 @@ trait ResetApplication
protected $providers = [];
/**
- * @var array
+ * @var \SwooleTW\Http\Server\Resetters\ResetterContract[]|array
*/
protected $resetters = [];
@@ -28,7 +29,7 @@ trait ResetApplication
*/
protected function setInitialConfig()
{
- $this->config = clone $this->getBaseApp()->make('config');
+ $this->config = clone $this->getBaseApp()->make(Repository::class);
}
/**
@@ -90,6 +91,8 @@ public function getResetters()
/**
* Reset Laravel/Lumen Application.
+ *
+ * @param \Illuminate\Contracts\Container\Container $app
*/
public function resetApp(Container $app)
{
diff --git a/src/Concerns/WithApplication.php b/src/Concerns/WithApplication.php
index d34385dc..8a09a1fb 100644
--- a/src/Concerns/WithApplication.php
+++ b/src/Concerns/WithApplication.php
@@ -2,21 +2,23 @@
namespace SwooleTW\Http\Concerns;
-use Illuminate\Http\Request;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\Facades\Facade;
use Illuminate\Contracts\Container\Container;
-use Laravel\Lumen\Application as LumenApplication;
-use Symfony\Component\HttpFoundation\StreamedResponse;
-use Symfony\Component\HttpFoundation\BinaryFileResponse;
-use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
-
+use SwooleTW\Http\Exceptions\FrameworkNotSupportException;
+
+/**
+ * Trait WithApplication
+ *
+ * @property Container $container
+ * @property string $framework
+ */
trait WithApplication
{
/**
* Laravel|Lumen Application.
*
- * @var \Illuminate\Contracts\Container\Container
+ * @var \Illuminate\Contracts\Container\Container|mixed
*/
protected $app;
@@ -28,7 +30,12 @@ protected function bootstrap()
if ($this->framework === 'laravel') {
$bootstrappers = $this->getBootstrappers();
$this->app->bootstrapWith($bootstrappers);
- } elseif (is_null(Facade::getFacadeApplication())) {
+ } else {
+ // for Lumen 5.7
+ // https://github.com/laravel/lumen-framework/commit/42cbc998375718b1a8a11883e033617024e57260#diff-c9248b3167fc44af085b81db2e292837
+ if (method_exists($this->app, 'boot')) {
+ $this->app->boot();
+ }
$this->app->withFacades();
}
@@ -42,11 +49,12 @@ protected function bootstrap()
*/
protected function loadApplication()
{
- return require $this->basePath . '/bootstrap/app.php';
+ return require "{$this->basePath}/bootstrap/app.php";
}
/**
- * @return \Illuminate\Contracts\Container\Container
+ * @return \Illuminate\Contracts\Container\Container|mixed
+ * @throws \ReflectionException
*/
public function getApplication()
{
@@ -60,37 +68,19 @@ public function getApplication()
/**
* Set laravel application.
+ *
+ * @param \Illuminate\Contracts\Container\Container $app
*/
public function setApplication(Container $app)
{
$this->app = $app;
}
- /**
- * Get bootstrappers.
- *
- * @return array
- */
- protected function getBootstrappers()
- {
- $kernel = $this->getApplication()->make(Kernel::class);
-
- $reflection = new \ReflectionObject($kernel);
-
- $bootstrappersMethod = $reflection->getMethod('bootstrappers');
- $bootstrappersMethod->setAccessible(true);
-
- $bootstrappers = $bootstrappersMethod->invoke($kernel);
-
- array_splice($bootstrappers, -2, 0, ['Illuminate\Foundation\Bootstrap\SetRequestForConsole']);
-
- return $bootstrappers;
- }
-
/**
* Set framework.
*
* @param string $framework
+ *
* @throws \Exception
*/
protected function setFramework($framework)
@@ -98,7 +88,7 @@ protected function setFramework($framework)
$framework = strtolower($framework);
if (! in_array($framework, ['laravel', 'lumen'])) {
- throw new \Exception(sprintf('Not support framework "%s".', $framework));
+ throw new FrameworkNotSupportException($framework);
}
$this->framework = $framework;
@@ -132,10 +122,13 @@ public function getBasePath()
/**
* Reslove some instances before request.
+ *
+ * @throws \ReflectionException
*/
protected function preResolveInstances()
{
- $resolves = $this->container['config']->get('swoole_http.pre_resolved', []);
+ $resolves = $this->container->make('config')
+ ->get('swoole_http.pre_resolved', []);
foreach ($resolves as $abstract) {
if ($this->getApplication()->offsetExists($abstract)) {
@@ -143,4 +136,24 @@ protected function preResolveInstances()
}
}
}
+
+ /**
+ * Get bootstrappers.
+ *
+ * @return array
+ * @throws \ReflectionException
+ */
+ protected function getBootstrappers()
+ {
+ $kernel = $this->getApplication()->make(Kernel::class);
+
+ $reflection = new \ReflectionObject($kernel);
+ $bootstrappersMethod = $reflection->getMethod('bootstrappers');
+ $bootstrappersMethod->setAccessible(true);
+ $bootstrappers = $bootstrappersMethod->invoke($kernel);
+
+ array_splice($bootstrappers, -2, 0, ['Illuminate\Foundation\Bootstrap\SetRequestForConsole']);
+
+ return $bootstrappers;
+ }
}
diff --git a/src/Controllers/SocketIOController.php b/src/Controllers/SocketIOController.php
index be16a5ac..159adfd8 100644
--- a/src/Controllers/SocketIOController.php
+++ b/src/Controllers/SocketIOController.php
@@ -12,31 +12,39 @@ class SocketIOController
public function upgrade(Request $request)
{
if (! in_array($request->input('transport'), $this->transports)) {
- return response()->json([
- 'code' => 0,
- 'message' => 'Transport unknown'
- ], 400);
+ return response()->json(
+ [
+ 'code' => 0,
+ 'message' => 'Transport unknown',
+ ],
+ 400
+ );
}
if ($request->has('sid')) {
return '1:6';
}
- $payload = json_encode([
- 'sid' => base64_encode(uniqid()),
- 'upgrades' => ['websocket'],
- 'pingInterval' => Config::get('swoole_websocket.ping_interval'),
- 'pingTimeout' => Config::get('swoole_websocket.ping_timeout')
- ]);
+ $payload = json_encode(
+ [
+ 'sid' => base64_encode(uniqid()),
+ 'upgrades' => ['websocket'],
+ 'pingInterval' => Config::get('swoole_websocket.ping_interval'),
+ 'pingTimeout' => Config::get('swoole_websocket.ping_timeout'),
+ ]
+ );
return '97:0' . $payload . '2:40';
}
- public function reject(Request $request)
+ public function reject()
{
- return response()->json([
- 'code' => 3,
- 'message' => 'Bad request'
- ], 400);
+ return response()->json(
+ [
+ 'code' => 3,
+ 'message' => 'Bad request',
+ ],
+ 400
+ );
}
}
diff --git a/src/Coroutine/Connectors/ConnectorFactory.php b/src/Coroutine/Connectors/ConnectorFactory.php
new file mode 100644
index 00000000..fd402338
--- /dev/null
+++ b/src/Coroutine/Connectors/ConnectorFactory.php
@@ -0,0 +1,105 @@
+getDocComment(), $result)) {
+ $fileVersion = Arr::first($result);
+ }
+ }
+
+ return version_compare($fileVersion, $version, '>=');
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param string $version
+ *
+ * @return bool
+ */
+ protected static function hasBreakingChanges(string $version): bool
+ {
+ return version_compare($version, self::CHANGE_VERSION, '>=');
+ }
+}
diff --git a/src/Coroutine/Connectors/MySqlConnector.php b/src/Coroutine/Connectors/MySqlConnector.php
index 627aadd8..2efa720a 100644
--- a/src/Coroutine/Connectors/MySqlConnector.php
+++ b/src/Coroutine/Connectors/MySqlConnector.php
@@ -2,21 +2,26 @@
namespace SwooleTW\Http\Coroutine\Connectors;
-use Exception;
+use Illuminate\Database\Connectors\MySqlConnector as BaseConnector;
use Illuminate\Support\Str;
use SwooleTW\Http\Coroutine\PDO as SwoolePDO;
-use Illuminate\Database\Connectors\MySqlConnector as BaseConnector;
+use Throwable;
+/**
+ * Class MySqlConnector (5.6)
+ */
class MySqlConnector extends BaseConnector
{
/**
* Create a new PDO connection instance.
*
- * @param string $dsn
- * @param string $username
- * @param string $password
- * @param array $options
+ * @param string $dsn
+ * @param string $username
+ * @param string $password
+ * @param array $options
+ *
* @return \PDO
+ * @throws \SwooleTW\Http\Coroutine\ConnectionException
*/
protected function createPdoConnection($dsn, $username, $password, $options)
{
@@ -26,16 +31,16 @@ protected function createPdoConnection($dsn, $username, $password, $options)
/**
* Handle an exception that occurred during connect execution.
*
- * @param \Exception $e
- * @param string $dsn
- * @param string $username
- * @param string $password
- * @param array $options
- * @return \PDO
+ * @param \Throwable $e
+ * @param string $dsn
+ * @param string $username
+ * @param string $password
+ * @param array $options
*
- * @throws \Exception
+ * @return \PDO
+ * @throws \Throwable
*/
- protected function tryAgainIfCausedByLostConnection(Exception $e, $dsn, $username, $password, $options)
+ protected function tryAgainIfCausedByLostConnection(Throwable $e, $dsn, $username, $password, $options)
{
// https://github.com/swoole/swoole-src/blob/a414e5e8fec580abb3dbd772d483e12976da708f/swoole_mysql_coro.c#L196
if ($this->causedByLostConnection($e) || Str::contains($e->getMessage(), 'is closed')) {
diff --git a/src/Coroutine/Context.php b/src/Coroutine/Context.php
index a2f49a34..2ea8a20c 100644
--- a/src/Coroutine/Context.php
+++ b/src/Coroutine/Context.php
@@ -2,8 +2,8 @@
namespace SwooleTW\Http\Coroutine;
-use Swoole\Coroutine;
use Illuminate\Contracts\Container\Container;
+use Swoole\Coroutine;
class Context
{
@@ -31,6 +31,8 @@ public static function getApp()
/**
* Set app container by current coroutine id.
+ *
+ * @param \Illuminate\Contracts\Container\Container $app
*/
public static function setApp(Container $app)
{
@@ -39,6 +41,10 @@ public static function setApp(Container $app)
/**
* Get data by current coroutine id.
+ *
+ * @param string $key
+ *
+ * @return mixed|null
*/
public static function getData(string $key)
{
@@ -47,6 +53,9 @@ public static function getData(string $key)
/**
* Set data by current coroutine id.
+ *
+ * @param string $key
+ * @param $value
*/
public static function setData(string $key, $value)
{
@@ -55,6 +64,8 @@ public static function setData(string $key, $value)
/**
* Remove data by current coroutine id.
+ *
+ * @param string $key
*/
public static function removeData(string $key)
{
@@ -62,7 +73,7 @@ public static function removeData(string $key)
}
/**
- * Get data by current coroutine id.
+ * Get data keys by current coroutine id.
*/
public static function getDataKeys()
{
@@ -70,7 +81,7 @@ public static function getDataKeys()
}
/**
- * Get data by current coroutine id.
+ * Clear data by current coroutine id.
*/
public static function clear()
{
diff --git a/src/Coroutine/MySqlConnection.php b/src/Coroutine/MySqlConnection.php
index be73c294..631ad6bb 100644
--- a/src/Coroutine/MySqlConnection.php
+++ b/src/Coroutine/MySqlConnection.php
@@ -3,19 +3,20 @@
namespace SwooleTW\Http\Coroutine;
use Closure;
-use Illuminate\Support\Str;
-use Illuminate\Database\QueryException;
use Illuminate\Database\MySqlConnection as BaseConnection;
+use Illuminate\Database\QueryException;
+use Illuminate\Support\Str;
class MySqlConnection extends BaseConnection
{
/**
* Handle a query exception that occurred during query execution.
*
- * @param \Illuminate\Database\QueryException $e
- * @param string $query
- * @param array $bindings
- * @param \Closure $callback
+ * @param \Illuminate\Database\QueryException $e
+ * @param string $query
+ * @param array $bindings
+ * @param \Closure $callback
+ *
* @return mixed
*
* @throws \Illuminate\Database\QueryException
@@ -23,7 +24,10 @@ class MySqlConnection extends BaseConnection
protected function tryAgainIfCausedByLostConnection(QueryException $e, $query, $bindings, Closure $callback)
{
// https://github.com/swoole/swoole-src/blob/a414e5e8fec580abb3dbd772d483e12976da708f/swoole_mysql_coro.c#L1140
- if ($this->causedByLostConnection($e->getPrevious()) || Str::contains($e->getMessage(), ['is closed', 'is not established'])) {
+ if ($this->causedByLostConnection($e->getPrevious()) || Str::contains(
+ $e->getMessage(),
+ ['is closed', 'is not established']
+ )) {
$this->reconnect();
return $this->runQueryCallback($query, $bindings, $callback);
diff --git a/src/Coroutine/PDO.php b/src/Coroutine/PDO.php
index 5e4a7aeb..2ee646f2 100644
--- a/src/Coroutine/PDO.php
+++ b/src/Coroutine/PDO.php
@@ -10,26 +10,28 @@
namespace SwooleTW\Http\Coroutine;
use Exception;
-use PDO as BasePDO;
use Illuminate\Database\QueryException;
-use SwooleTW\Http\Coroutine\PDOStatement;
-use SwooleTW\Http\Coroutine\StatementException;
-use SwooleTW\Http\Coroutine\ConnectionException;
+use Illuminate\Support\Arr;
+use PDO as BasePDO;
+/**
+ * Class PDO
+ */
class PDO extends BasePDO
{
public static $keyMap = [
- 'dbname' => 'database'
+ 'dbname' => 'database',
];
- private static $defaultOptions = [
+ private static $options = [
'host' => '',
'port' => 3306,
'user' => '',
'password' => '',
'database' => '',
'charset' => 'utf8mb4',
- 'strict_type' => true
+ 'strict_type' => true,
+ 'timeout' => -1,
];
/** @var \Swoole\Coroutine\Mysql */
@@ -37,21 +39,37 @@ class PDO extends BasePDO
public $inTransaction = false;
- public function __construct(
- string $dsn,
- string $username = '',
- string $password = '',
- array $driverOptions = []
- ) {
+ /**
+ * PDO constructor.
+ *
+ * @param string $dsn
+ * @param string $username
+ * @param string $password
+ * @param array $options
+ *
+ * @throws \SwooleTW\Http\Coroutine\ConnectionException
+ */
+ public function __construct(string $dsn, string $username = '', string $password = '', array $options = [])
+ {
+ parent::__construct($dsn, $username, $password, $options);
$this->setClient();
$this->connect($this->getOptions(...func_get_args()));
}
+ /**
+ * @param mixed $client
+ */
protected function setClient($client = null)
{
- $this->client = $client ?: new \Swoole\Coroutine\Mysql();
+ $this->client = $client ?: new \Swoole\Coroutine\Mysql;
}
+ /**
+ * @param array $options
+ *
+ * @return $this
+ * @throws \SwooleTW\Http\Coroutine\ConnectionException
+ */
protected function connect(array $options = [])
{
$this->client->connect($options);
@@ -66,19 +84,27 @@ protected function connect(array $options = [])
return $this;
}
+ /**
+ * @param $dsn
+ * @param $username
+ * @param $password
+ * @param $driverOptions
+ *
+ * @return array
+ */
protected function getOptions($dsn, $username, $password, $driverOptions)
{
$dsn = explode(':', $dsn);
$driver = ucwords(array_shift($dsn));
$dsn = explode(';', implode(':', $dsn));
- $options = [];
+ $configuredOptions = [];
static::checkDriver($driver);
foreach ($dsn as $kv) {
$kv = explode('=', $kv);
- if ($kv) {
- $options[$kv[0]] = $kv[1] ?? '';
+ if (count($kv)) {
+ $configuredOptions[$kv[0]] = $kv[1] ?? '';
}
}
@@ -87,18 +113,21 @@ protected function getOptions($dsn, $username, $password, $driverOptions)
'password' => $password,
];
- $options = $driverOptions + $authorization + $options;
+ $configuredOptions = $driverOptions + $authorization + $configuredOptions;
foreach (static::$keyMap as $pdoKey => $swpdoKey) {
- if (isset($options[$pdoKey])) {
- $options[$swpdoKey] = $options[$pdoKey];
- unset($options[$pdoKey]);
+ if (isset($configuredOptions[$pdoKey])) {
+ $configuredOptions[$swpdoKey] = $configuredOptions[$pdoKey];
+ unset($configuredOptions[$pdoKey]);
}
}
- return $options + static::$defaultOptions;
+ return array_merge(static::$options, $configuredOptions);
}
+ /**
+ * @param string $driver
+ */
public static function checkDriver(string $driver)
{
if (! in_array($driver, static::getAvailableDrivers())) {
@@ -106,44 +135,70 @@ public static function checkDriver(string $driver)
}
}
+ /**
+ * @return array
+ */
public static function getAvailableDrivers()
{
return ['Mysql'];
}
+ /**
+ * @return bool|void
+ */
public function beginTransaction()
{
$this->client->begin();
$this->inTransaction = true;
}
+ /**
+ * @return bool|void
+ */
public function rollBack()
{
$this->client->rollback();
$this->inTransaction = false;
}
+ /**
+ * @return bool|void
+ */
public function commit()
{
$this->client->commit();
$this->inTransaction = true;
}
+ /**
+ * @return bool
+ */
public function inTransaction()
{
return $this->inTransaction;
}
+ /**
+ * @param null $seqname
+ *
+ * @return int|string
+ */
public function lastInsertId($seqname = null)
{
return $this->client->insert_id;
}
+ /**
+ * @return mixed|void
+ */
public function errorCode()
{
$this->client->errno;
}
+ /**
+ * @return array
+ */
public function errorInfo()
{
return [
@@ -153,6 +208,11 @@ public function errorInfo()
];
}
+ /**
+ * @param string $statement
+ *
+ * @return int
+ */
public function exec($statement): int
{
$this->query($statement);
@@ -160,9 +220,17 @@ public function exec($statement): int
return $this->client->affected_rows;
}
- public function query(string $statement, float $timeout = -1)
+ /**
+ * @param string $statement
+ * @param int $mode
+ * @param mixed $arg3
+ * @param array $ctorargs
+ *
+ * @return array|bool|false|\PDOStatement
+ */
+ public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, $arg3 = null, array $ctorargs = [])
{
- $result = $this->client->query($statement, $timeout);
+ $result = $this->client->query($statement, Arr::get(self::$options, 'timeout'));
if ($result === false) {
$exception = new Exception($this->client->error, $this->client->errno);
@@ -172,39 +240,42 @@ public function query(string $statement, float $timeout = -1)
return $result;
}
- private function rewriteToPosition(string $statement)
+ /**
+ * @param string $statement
+ * @param array $options
+ *
+ * @return bool|\PDOStatement|\SwooleTW\Http\Coroutine\PDOStatement
+ */
+ public function prepare($statement, $options = null)
{
- //
- }
-
- public function prepare($statement, $driverOptions = null)
- {
- $driverOptions = is_null($driverOptions) ? [] : $driverOptions;
+ $options = is_null($options) ? [] : $options;
if (strpos($statement, ':') !== false) {
$i = 0;
$bindKeyMap = [];
- $statement = preg_replace_callback(
- '/:(\w+)\b/',
- function ($matches) use (&$i, &$bindKeyMap) {
- $bindKeyMap[$matches[1]] = $i++;
-
- return '?';
- },
- $statement
- );
+ $statement = preg_replace_callback('/:([a-zA-Z_]\w*?)\b/', function ($matches) use (&$i, &$bindKeyMap) {
+ $bindKeyMap[$matches[1]] = $i++;
+
+ return '?';
+ }, $statement);
}
$stmtObj = $this->client->prepare($statement);
if ($stmtObj) {
$stmtObj->bindKeyMap = $bindKeyMap ?? [];
- return new PDOStatement($this, $stmtObj, $driverOptions);
+
+ return new PDOStatement($this, $stmtObj, $options);
} else {
$statementException = new StatementException($this->client->error, $this->client->errno);
throw new QueryException($statement, [], $statementException);
}
}
+ /**
+ * @param int $attribute
+ *
+ * @return bool|mixed|string
+ */
public function getAttribute($attribute)
{
switch ($attribute) {
@@ -221,7 +292,7 @@ public function getAttribute($attribute)
case \PDO::ATTR_PERSISTENT:
case \PDO::ATTR_PREFETCH:
case \PDO::ATTR_SERVER_INFO:
- return $this->serverInfo['timeout'] ?? static::$defaultOptions['timeout'];
+ return self::$options['timeout'];
case \PDO::ATTR_SERVER_VERSION:
return 'Swoole Mysql';
case \PDO::ATTR_TIMEOUT:
@@ -230,9 +301,16 @@ public function getAttribute($attribute)
}
}
+ /**
+ * @param string $string
+ * @param null $paramtype
+ *
+ * @return string|void
+ */
public function quote($string, $paramtype = null)
{
- throw new \BadMethodCallException(<<client->close();
diff --git a/src/Coroutine/PDOStatement.php b/src/Coroutine/PDOStatement.php
index bec3fa1a..fcd605ab 100644
--- a/src/Coroutine/PDOStatement.php
+++ b/src/Coroutine/PDOStatement.php
@@ -10,18 +10,24 @@
namespace SwooleTW\Http\Coroutine;
use PDOStatement as BaseStatement;
-use SwooleTW\Http\Coroutine\PDO;
use Swoole\Coroutine\MySQL\Statement;
class PDOStatement extends BaseStatement
{
private $parent;
+
public $statement;
+
public $timeout;
+
public $bindMap = [];
+
public $cursor = -1;
+
public $cursorOrientation = PDO::FETCH_ORI_NEXT;
+
public $resultSet = [];
+
public $fetchStyle = PDO::FETCH_BOTH;
public function __construct(PDO $parent, Statement $statement, array $driverOptions = [])
@@ -95,7 +101,9 @@ public function execute($inputParameters = null)
$inputParameters = [];
if (! empty($this->statement->bindKeyMap)) {
foreach ($this->statement->bindKeyMap as $nameKey => $numKey) {
- $inputParameters[$numKey] = $this->bindMap[$nameKey];
+ if (isset($this->bindMap[$nameKey])) {
+ $inputParameters[$numKey] = $this->bindMap[$nameKey];
+ }
}
} else {
$inputParameters = $this->bindMap;
@@ -105,6 +113,10 @@ public function execute($inputParameters = null)
$this->resultSet = ($ok = $result !== false) ? $result : [];
$this->afterExecute();
+ if ($result === false) {
+ throw new \PDOException($this->errorInfo(), $this->errorCode());
+ }
+
return $ok;
}
@@ -142,7 +154,8 @@ private function transStyle(
$fetchStyle = null,
$fetchArgument = null,
$ctorArgs = null
- ) {
+ )
+ {
if (! is_array($rawData)) {
return false;
}
@@ -187,7 +200,8 @@ public function fetch(
$cursorOrientation = null,
$cursorOffset = null,
$fetchArgument = null
- ) {
+ )
+ {
$this->__executeWhenStringQueryEmpty();
$cursorOrientation = is_null($cursorOrientation) ? PDO::FETCH_ORI_NEXT : $cursorOrientation;
@@ -222,7 +236,8 @@ public function fetch(
/**
* Returns a single column from the next row of a result set or FALSE if there are no more rows.
*
- * @param int $column_number
+ * @param int|null $columnNumber
+ *
* 0-indexed number of the column you wish to retrieve from the row.
* If no value is supplied, PDOStatement::fetchColumn() fetches the first column.
*
@@ -232,6 +247,7 @@ public function fetchColumn($columnNumber = null)
{
$columnNumber = is_null($columnNumber) ? 0 : $columnNumber;
$this->__executeWhenStringQueryEmpty();
+
return $this->fetch(PDO::FETCH_COLUMN, PDO::FETCH_ORI_NEXT, 0, $columnNumber);
}
diff --git a/src/Exceptions/FrameworkNotSupportException.php b/src/Exceptions/FrameworkNotSupportException.php
new file mode 100644
index 00000000..f393279c
--- /dev/null
+++ b/src/Exceptions/FrameworkNotSupportException.php
@@ -0,0 +1,21 @@
+dump(
+ static::$cloner->cloneVar($arg)
+ );
+ }
+
+ return true;
+ }
+
+ public static function getDumper()
+ {
+ $dumper = defined('IN_PHPUNIT') || ! config('swoole_http.ob_output')
+ ? CliDumper::class
+ : HtmlDumper::class;
+
+ return new $dumper;
+ }
+}
diff --git a/src/Helpers/FW.php b/src/Helpers/FW.php
new file mode 100644
index 00000000..818e5189
--- /dev/null
+++ b/src/Helpers/FW.php
@@ -0,0 +1,102 @@
+version(), $expression)) {
+ return $version;
+ }
+
+ throw new LogicException('No any version found.');
+ }
+
+ /**
+ * Extracts lumen version from $app->version()
+ *
+ * @param string $version
+ * @param string $expression
+ *
+ * @return string|null
+ */
+ protected static function extractVersion(string $version, string $expression): ?string
+ {
+ if (preg_match($expression, $version, $result)) {
+ return Arr::first($result);
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/Helpers/MimeType.php b/src/Helpers/MimeType.php
new file mode 100644
index 00000000..7132b629
--- /dev/null
+++ b/src/Helpers/MimeType.php
@@ -0,0 +1,821 @@
+ 'application/andrew-inset',
+ 'aw' => 'application/applixware',
+ 'atom' => 'application/atom+xml',
+ 'atomcat' => 'application/atomcat+xml',
+ 'atomsvc' => 'application/atomsvc+xml',
+ 'ccxml' => 'application/ccxml+xml',
+ 'cdmia' => 'application/cdmi-capability',
+ 'cdmic' => 'application/cdmi-container',
+ 'cdmid' => 'application/cdmi-domain',
+ 'cdmio' => 'application/cdmi-object',
+ 'cdmiq' => 'application/cdmi-queue',
+ 'cu' => 'application/cu-seeme',
+ 'davmount' => 'application/davmount+xml',
+ 'dbk' => 'application/docbook+xml',
+ 'dssc' => 'application/dssc+der',
+ 'xdssc' => 'application/dssc+xml',
+ 'ecma' => 'application/ecmascript',
+ 'emma' => 'application/emma+xml',
+ 'epub' => 'application/epub+zip',
+ 'exi' => 'application/exi',
+ 'pfr' => 'application/font-tdpfr',
+ 'gml' => 'application/gml+xml',
+ 'gpx' => 'application/gpx+xml',
+ 'gxf' => 'application/gxf',
+ 'stk' => 'application/hyperstudio',
+ 'ink' => 'application/inkml+xml',
+ 'ipfix' => 'application/ipfix',
+ 'jar' => 'application/java-archive',
+ 'ser' => 'application/java-serialized-object',
+ 'class' => 'application/java-vm',
+ 'js' => 'application/javascript',
+ 'json' => 'application/json',
+ 'jsonml' => 'application/jsonml+json',
+ 'lostxml' => 'application/lost+xml',
+ 'hqx' => 'application/mac-binhex40',
+ 'cpt' => 'application/mac-compactpro',
+ 'mads' => 'application/mads+xml',
+ 'mrc' => 'application/marc',
+ 'mrcx' => 'application/marcxml+xml',
+ 'ma' => 'application/mathematica',
+ 'mathml' => 'application/mathml+xml',
+ 'mbox' => 'application/mbox',
+ 'mscml' => 'application/mediaservercontrol+xml',
+ 'metalink' => 'application/metalink+xml',
+ 'meta4' => 'application/metalink4+xml',
+ 'mets' => 'application/mets+xml',
+ 'mods' => 'application/mods+xml',
+ 'm21' => 'application/mp21',
+ 'mp4s' => 'application/mp4',
+ 'doc' => 'application/msword',
+ 'mxf' => 'application/mxf',
+ 'bin' => 'application/octet-stream',
+ 'oda' => 'application/oda',
+ 'opf' => 'application/oebps-package+xml',
+ 'ogx' => 'application/ogg',
+ 'omdoc' => 'application/omdoc+xml',
+ 'onetoc' => 'application/onenote',
+ 'oxps' => 'application/oxps',
+ 'xer' => 'application/patch-ops-error+xml',
+ 'pdf' => 'application/pdf',
+ 'pgp' => 'application/pgp-encrypted',
+ 'asc' => 'application/pgp-signature',
+ 'prf' => 'application/pics-rules',
+ 'p10' => 'application/pkcs10',
+ 'p7m' => 'application/pkcs7-mime',
+ 'p7s' => 'application/pkcs7-signature',
+ 'p8' => 'application/pkcs8',
+ 'ac' => 'application/pkix-attr-cert',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'pkipath' => 'application/pkix-pkipath',
+ 'pki' => 'application/pkixcmp',
+ 'pls' => 'application/pls+xml',
+ 'ai' => 'application/postscript',
+ 'cww' => 'application/prs.cww',
+ 'pskcxml' => 'application/pskc+xml',
+ 'rdf' => 'application/rdf+xml',
+ 'rif' => 'application/reginfo+xml',
+ 'rnc' => 'application/relax-ng-compact-syntax',
+ 'rl' => 'application/resource-lists+xml',
+ 'rld' => 'application/resource-lists-diff+xml',
+ 'rs' => 'application/rls-services+xml',
+ 'gbr' => 'application/rpki-ghostbusters',
+ 'mft' => 'application/rpki-manifest',
+ 'roa' => 'application/rpki-roa',
+ 'rsd' => 'application/rsd+xml',
+ 'rss' => 'application/rss+xml',
+ 'sbml' => 'application/sbml+xml',
+ 'scq' => 'application/scvp-cv-request',
+ 'scs' => 'application/scvp-cv-response',
+ 'spq' => 'application/scvp-vp-request',
+ 'spp' => 'application/scvp-vp-response',
+ 'sdp' => 'application/sdp',
+ 'setpay' => 'application/set-payment-initiation',
+ 'setreg' => 'application/set-registration-initiation',
+ 'shf' => 'application/shf+xml',
+ 'smi' => 'application/smil+xml',
+ 'rq' => 'application/sparql-query',
+ 'srx' => 'application/sparql-results+xml',
+ 'gram' => 'application/srgs',
+ 'grxml' => 'application/srgs+xml',
+ 'sru' => 'application/sru+xml',
+ 'ssdl' => 'application/ssdl+xml',
+ 'ssml' => 'application/ssml+xml',
+ 'tei' => 'application/tei+xml',
+ 'tfi' => 'application/thraud+xml',
+ 'tsd' => 'application/timestamped-data',
+ 'plb' => 'application/vnd.3gpp.pic-bw-large',
+ 'psb' => 'application/vnd.3gpp.pic-bw-small',
+ 'pvb' => 'application/vnd.3gpp.pic-bw-var',
+ 'tcap' => 'application/vnd.3gpp2.tcap',
+ 'pwn' => 'application/vnd.3m.post-it-notes',
+ 'aso' => 'application/vnd.accpac.simply.aso',
+ 'imp' => 'application/vnd.accpac.simply.imp',
+ 'acu' => 'application/vnd.acucobol',
+ 'atc' => 'application/vnd.acucorp',
+ 'air' => 'application/vnd.adobe.air-application-installer-package+zip',
+ 'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
+ 'fxp' => 'application/vnd.adobe.fxp',
+ 'xdp' => 'application/vnd.adobe.xdp+xml',
+ 'xfdf' => 'application/vnd.adobe.xfdf',
+ 'ahead' => 'application/vnd.ahead.space',
+ 'azf' => 'application/vnd.airzip.filesecure.azf',
+ 'azs' => 'application/vnd.airzip.filesecure.azs',
+ 'azw' => 'application/vnd.amazon.ebook',
+ 'acc' => 'application/vnd.americandynamics.acc',
+ 'ami' => 'application/vnd.amiga.ami',
+ 'apk' => 'application/vnd.android.package-archive',
+ 'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
+ 'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
+ 'atx' => 'application/vnd.antix.game-component',
+ 'mpkg' => 'application/vnd.apple.installer+xml',
+ 'm3u8' => 'application/vnd.apple.mpegurl',
+ 'swi' => 'application/vnd.aristanetworks.swi',
+ 'iota' => 'application/vnd.astraea-software.iota',
+ 'aep' => 'application/vnd.audiograph',
+ 'mpm' => 'application/vnd.blueice.multipass',
+ 'bmi' => 'application/vnd.bmi',
+ 'rep' => 'application/vnd.businessobjects',
+ 'cdxml' => 'application/vnd.chemdraw+xml',
+ 'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
+ 'cdy' => 'application/vnd.cinderella',
+ 'cla' => 'application/vnd.claymore',
+ 'rp9' => 'application/vnd.cloanto.rp9',
+ 'c4g' => 'application/vnd.clonk.c4group',
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
+ 'csp' => 'application/vnd.commonspace',
+ 'cdbcmsg' => 'application/vnd.contact.cmsg',
+ 'cmc' => 'application/vnd.cosmocaller',
+ 'clkx' => 'application/vnd.crick.clicker',
+ 'clkk' => 'application/vnd.crick.clicker.keyboard',
+ 'clkp' => 'application/vnd.crick.clicker.palette',
+ 'clkt' => 'application/vnd.crick.clicker.template',
+ 'clkw' => 'application/vnd.crick.clicker.wordbank',
+ 'wbs' => 'application/vnd.criticaltools.wbs+xml',
+ 'pml' => 'application/vnd.ctc-posml',
+ 'ppd' => 'application/vnd.cups-ppd',
+ 'car' => 'application/vnd.curl.car',
+ 'pcurl' => 'application/vnd.curl.pcurl',
+ 'dart' => 'application/vnd.dart',
+ 'rdz' => 'application/vnd.data-vision.rdz',
+ 'uvf' => 'application/vnd.dece.data',
+ 'uvt' => 'application/vnd.dece.ttml+xml',
+ 'uvx' => 'application/vnd.dece.unspecified',
+ 'uvz' => 'application/vnd.dece.zip',
+ 'fe_launch' => 'application/vnd.denovo.fcselayout-link',
+ 'dna' => 'application/vnd.dna',
+ 'mlp' => 'application/vnd.dolby.mlp',
+ 'dpg' => 'application/vnd.dpgraph',
+ 'dfac' => 'application/vnd.dreamfactory',
+ 'kpxx' => 'application/vnd.ds-keypoint',
+ 'ait' => 'application/vnd.dvb.ait',
+ 'svc' => 'application/vnd.dvb.service',
+ 'geo' => 'application/vnd.dynageo',
+ 'mag' => 'application/vnd.ecowin.chart',
+ 'nml' => 'application/vnd.enliven',
+ 'esf' => 'application/vnd.epson.esf',
+ 'msf' => 'application/vnd.epson.msf',
+ 'qam' => 'application/vnd.epson.quickanime',
+ 'slt' => 'application/vnd.epson.salt',
+ 'ssf' => 'application/vnd.epson.ssf',
+ 'es3' => 'application/vnd.eszigno3+xml',
+ 'ez2' => 'application/vnd.ezpix-album',
+ 'ez3' => 'application/vnd.ezpix-package',
+ 'fdf' => 'application/vnd.fdf',
+ 'mseed' => 'application/vnd.fdsn.mseed',
+ 'seed' => 'application/vnd.fdsn.seed',
+ 'gph' => 'application/vnd.flographit',
+ 'ftc' => 'application/vnd.fluxtime.clip',
+ 'fm' => 'application/vnd.framemaker',
+ 'fnc' => 'application/vnd.frogans.fnc',
+ 'ltf' => 'application/vnd.frogans.ltf',
+ 'fsc' => 'application/vnd.fsc.weblaunch',
+ 'oas' => 'application/vnd.fujitsu.oasys',
+ 'oa2' => 'application/vnd.fujitsu.oasys2',
+ 'oa3' => 'application/vnd.fujitsu.oasys3',
+ 'fg5' => 'application/vnd.fujitsu.oasysgp',
+ 'bh2' => 'application/vnd.fujitsu.oasysprs',
+ 'ddd' => 'application/vnd.fujixerox.ddd',
+ 'xdw' => 'application/vnd.fujixerox.docuworks',
+ 'xbd' => 'application/vnd.fujixerox.docuworks.binder',
+ 'fzs' => 'application/vnd.fuzzysheet',
+ 'txd' => 'application/vnd.genomatix.tuxedo',
+ 'ggb' => 'application/vnd.geogebra.file',
+ 'ggt' => 'application/vnd.geogebra.tool',
+ 'gex' => 'application/vnd.geometry-explorer',
+ 'gxt' => 'application/vnd.geonext',
+ 'g2w' => 'application/vnd.geoplan',
+ 'g3w' => 'application/vnd.geospace',
+ 'gmx' => 'application/vnd.gmx',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'gqf' => 'application/vnd.grafeq',
+ 'gac' => 'application/vnd.groove-account',
+ 'ghf' => 'application/vnd.groove-help',
+ 'gim' => 'application/vnd.groove-identity-message',
+ 'grv' => 'application/vnd.groove-injector',
+ 'gtm' => 'application/vnd.groove-tool-message',
+ 'tpl' => 'application/vnd.groove-tool-template',
+ 'vcg' => 'application/vnd.groove-vcard',
+ 'hal' => 'application/vnd.hal+xml',
+ 'zmm' => 'application/vnd.handheld-entertainment+xml',
+ 'hbci' => 'application/vnd.hbci',
+ 'les' => 'application/vnd.hhe.lesson-player',
+ 'hpgl' => 'application/vnd.hp-hpgl',
+ 'hpid' => 'application/vnd.hp-hpid',
+ 'hps' => 'application/vnd.hp-hps',
+ 'jlt' => 'application/vnd.hp-jlyt',
+ 'pcl' => 'application/vnd.hp-pcl',
+ 'pclxl' => 'application/vnd.hp-pclxl',
+ 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
+ 'mpy' => 'application/vnd.ibm.minipay',
+ 'afp' => 'application/vnd.ibm.modcap',
+ 'irm' => 'application/vnd.ibm.rights-management',
+ 'sc' => 'application/vnd.ibm.secure-container',
+ 'icc' => 'application/vnd.iccprofile',
+ 'igl' => 'application/vnd.igloader',
+ 'ivp' => 'application/vnd.immervision-ivp',
+ 'ivu' => 'application/vnd.immervision-ivu',
+ 'igm' => 'application/vnd.insors.igm',
+ 'xpw' => 'application/vnd.intercon.formnet',
+ 'i2g' => 'application/vnd.intergeo',
+ 'qbo' => 'application/vnd.intu.qbo',
+ 'qfx' => 'application/vnd.intu.qfx',
+ 'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
+ 'irp' => 'application/vnd.irepository.package+xml',
+ 'xpr' => 'application/vnd.is-xpr',
+ 'fcs' => 'application/vnd.isac.fcs',
+ 'jam' => 'application/vnd.jam',
+ 'rms' => 'application/vnd.jcp.javame.midlet-rms',
+ 'jisp' => 'application/vnd.jisp',
+ 'joda' => 'application/vnd.joost.joda-archive',
+ 'ktz' => 'application/vnd.kahootz',
+ 'karbon' => 'application/vnd.kde.karbon',
+ 'chrt' => 'application/vnd.kde.kchart',
+ 'kfo' => 'application/vnd.kde.kformula',
+ 'flw' => 'application/vnd.kde.kivio',
+ 'kon' => 'application/vnd.kde.kontour',
+ 'kpr' => 'application/vnd.kde.kpresenter',
+ 'ksp' => 'application/vnd.kde.kspread',
+ 'kwd' => 'application/vnd.kde.kword',
+ 'htke' => 'application/vnd.kenameaapp',
+ 'kia' => 'application/vnd.kidspiration',
+ 'kne' => 'application/vnd.kinar',
+ 'skp' => 'application/vnd.koan',
+ 'sse' => 'application/vnd.kodak-descriptor',
+ 'lasxml' => 'application/vnd.las.las+xml',
+ 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
+ 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
+ '123' => 'application/vnd.lotus-1-2-3',
+ 'apr' => 'application/vnd.lotus-approach',
+ 'pre' => 'application/vnd.lotus-freelance',
+ 'nsf' => 'application/vnd.lotus-notes',
+ 'org' => 'application/vnd.lotus-organizer',
+ 'scm' => 'application/vnd.lotus-screencam',
+ 'lwp' => 'application/vnd.lotus-wordpro',
+ 'portpkg' => 'application/vnd.macports.portpkg',
+ 'mcd' => 'application/vnd.mcd',
+ 'mc1' => 'application/vnd.medcalcdata',
+ 'cdkey' => 'application/vnd.mediastation.cdkey',
+ 'mwf' => 'application/vnd.mfer',
+ 'mfm' => 'application/vnd.mfmp',
+ 'flo' => 'application/vnd.micrografx.flo',
+ 'igx' => 'application/vnd.micrografx.igx',
+ 'mif' => 'application/vnd.mif',
+ 'daf' => 'application/vnd.mobius.daf',
+ 'dis' => 'application/vnd.mobius.dis',
+ 'mbk' => 'application/vnd.mobius.mbk',
+ 'mqy' => 'application/vnd.mobius.mqy',
+ 'msl' => 'application/vnd.mobius.msl',
+ 'plc' => 'application/vnd.mobius.plc',
+ 'txf' => 'application/vnd.mobius.txf',
+ 'mpn' => 'application/vnd.mophun.application',
+ 'mpc' => 'application/vnd.mophun.certificate',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'cil' => 'application/vnd.ms-artgalry',
+ 'cab' => 'application/vnd.ms-cab-compressed',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12',
+ 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'chm' => 'application/vnd.ms-htmlhelp',
+ 'ims' => 'application/vnd.ms-ims',
+ 'lrm' => 'application/vnd.ms-lrm',
+ 'thmx' => 'application/vnd.ms-officetheme',
+ 'cat' => 'application/vnd.ms-pki.seccat',
+ 'stl' => 'application/vnd.ms-pki.stl',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
+ 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
+ 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12',
+ 'mpp' => 'application/vnd.ms-project',
+ 'docm' => 'application/vnd.ms-word.document.macroenabled.12',
+ 'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
+ 'wps' => 'application/vnd.ms-works',
+ 'wpl' => 'application/vnd.ms-wpl',
+ 'xps' => 'application/vnd.ms-xpsdocument',
+ 'mseq' => 'application/vnd.mseq',
+ 'mus' => 'application/vnd.musician',
+ 'msty' => 'application/vnd.muvee.style',
+ 'taglet' => 'application/vnd.mynfc',
+ 'nlu' => 'application/vnd.neurolanguage.nlu',
+ 'ntf' => 'application/vnd.nitf',
+ 'nnd' => 'application/vnd.noblenet-directory',
+ 'nns' => 'application/vnd.noblenet-sealer',
+ 'nnw' => 'application/vnd.noblenet-web',
+ 'ngdat' => 'application/vnd.nokia.n-gage.data',
+ 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
+ 'rpst' => 'application/vnd.nokia.radio-preset',
+ 'rpss' => 'application/vnd.nokia.radio-presets',
+ 'edm' => 'application/vnd.novadigm.edm',
+ 'edx' => 'application/vnd.novadigm.edx',
+ 'ext' => 'application/vnd.novadigm.ext',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'otc' => 'application/vnd.oasis.opendocument.chart-template',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odft' => 'application/vnd.oasis.opendocument.formula-template',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'oti' => 'application/vnd.oasis.opendocument.image-template',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
+ 'xo' => 'application/vnd.olpc-sugar',
+ 'dd2' => 'application/vnd.oma.dd2+xml',
+ 'oxt' => 'application/vnd.openofficeorg.extension',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'mgp' => 'application/vnd.osgeo.mapguide.package',
+ 'dp' => 'application/vnd.osgi.dp',
+ 'esa' => 'application/vnd.osgi.subsystem',
+ 'pdb' => 'application/vnd.palm',
+ 'paw' => 'application/vnd.pawaafile',
+ 'str' => 'application/vnd.pg.format',
+ 'ei6' => 'application/vnd.pg.osasli',
+ 'efif' => 'application/vnd.picsel',
+ 'wg' => 'application/vnd.pmi.widget',
+ 'plf' => 'application/vnd.pocketlearn',
+ 'pbd' => 'application/vnd.powerbuilder6',
+ 'box' => 'application/vnd.previewsystems.box',
+ 'mgz' => 'application/vnd.proteus.magazine',
+ 'qps' => 'application/vnd.publishare-delta-tree',
+ 'ptid' => 'application/vnd.pvi.ptid1',
+ 'qxd' => 'application/vnd.quark.quarkxpress',
+ 'bed' => 'application/vnd.realvnc.bed',
+ 'mxl' => 'application/vnd.recordare.musicxml',
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
+ 'cryptonote' => 'application/vnd.rig.cryptonote',
+ 'cod' => 'application/vnd.rim.cod',
+ 'rm' => 'application/vnd.rn-realmedia',
+ 'rmvb' => 'application/vnd.rn-realmedia-vbr',
+ 'link66' => 'application/vnd.route66.link66+xml',
+ 'st' => 'application/vnd.sailingtracker.track',
+ 'see' => 'application/vnd.seemail',
+ 'sema' => 'application/vnd.sema',
+ 'semd' => 'application/vnd.semd',
+ 'semf' => 'application/vnd.semf',
+ 'ifm' => 'application/vnd.shana.informed.formdata',
+ 'itp' => 'application/vnd.shana.informed.formtemplate',
+ 'iif' => 'application/vnd.shana.informed.interchange',
+ 'ipk' => 'application/vnd.shana.informed.package',
+ 'twd' => 'application/vnd.simtech-mindmapper',
+ 'mmf' => 'application/vnd.smaf',
+ 'teacher' => 'application/vnd.smart.teacher',
+ 'sdkm' => 'application/vnd.solent.sdkm+xml',
+ 'dxp' => 'application/vnd.spotfire.dxp',
+ 'sfs' => 'application/vnd.spotfire.sfs',
+ 'sdc' => 'application/vnd.stardivision.calc',
+ 'sda' => 'application/vnd.stardivision.draw',
+ 'sdd' => 'application/vnd.stardivision.impress',
+ 'smf' => 'application/vnd.stardivision.math',
+ 'sdw' => 'application/vnd.stardivision.writer',
+ 'sgl' => 'application/vnd.stardivision.writer-global',
+ 'smzip' => 'application/vnd.stepmania.package',
+ 'sm' => 'application/vnd.stepmania.stepchart',
+ 'sxc' => 'application/vnd.sun.xml.calc',
+ 'stc' => 'application/vnd.sun.xml.calc.template',
+ 'sxd' => 'application/vnd.sun.xml.draw',
+ 'std' => 'application/vnd.sun.xml.draw.template',
+ 'sxi' => 'application/vnd.sun.xml.impress',
+ 'sti' => 'application/vnd.sun.xml.impress.template',
+ 'sxm' => 'application/vnd.sun.xml.math',
+ 'sxw' => 'application/vnd.sun.xml.writer',
+ 'sxg' => 'application/vnd.sun.xml.writer.global',
+ 'stw' => 'application/vnd.sun.xml.writer.template',
+ 'sus' => 'application/vnd.sus-calendar',
+ 'svd' => 'application/vnd.svd',
+ 'sis' => 'application/vnd.symbian.install',
+ 'xsm' => 'application/vnd.syncml+xml',
+ 'bdm' => 'application/vnd.syncml.dm+wbxml',
+ 'xdm' => 'application/vnd.syncml.dm+xml',
+ 'tao' => 'application/vnd.tao.intent-module-archive',
+ 'pcap' => 'application/vnd.tcpdump.pcap',
+ 'tmo' => 'application/vnd.tmobile-livetv',
+ 'tpt' => 'application/vnd.trid.tpt',
+ 'mxs' => 'application/vnd.triscape.mxs',
+ 'tra' => 'application/vnd.trueapp',
+ 'ufd' => 'application/vnd.ufdl',
+ 'utz' => 'application/vnd.uiq.theme',
+ 'umj' => 'application/vnd.umajin',
+ 'unityweb' => 'application/vnd.unity',
+ 'uoml' => 'application/vnd.uoml+xml',
+ 'vcx' => 'application/vnd.vcx',
+ 'vsd' => 'application/vnd.visio',
+ 'vis' => 'application/vnd.visionary',
+ 'vsf' => 'application/vnd.vsf',
+ 'wbxml' => 'application/vnd.wap.wbxml',
+ 'wmlc' => 'application/vnd.wap.wmlc',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'wtb' => 'application/vnd.webturbo',
+ 'nbp' => 'application/vnd.wolfram.player',
+ 'wpd' => 'application/vnd.wordperfect',
+ 'wqd' => 'application/vnd.wqd',
+ 'stf' => 'application/vnd.wt.stf',
+ 'xar' => 'application/vnd.xara',
+ 'xfdl' => 'application/vnd.xfdl',
+ 'hvd' => 'application/vnd.yamaha.hv-dic',
+ 'hvs' => 'application/vnd.yamaha.hv-script',
+ 'hvp' => 'application/vnd.yamaha.hv-voice',
+ 'osf' => 'application/vnd.yamaha.openscoreformat',
+ 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
+ 'saf' => 'application/vnd.yamaha.smaf-audio',
+ 'spf' => 'application/vnd.yamaha.smaf-phrase',
+ 'cmp' => 'application/vnd.yellowriver-custom-menu',
+ 'zir' => 'application/vnd.zul',
+ 'zaz' => 'application/vnd.zzazz.deck+xml',
+ 'vxml' => 'application/voicexml+xml',
+ 'wgt' => 'application/widget',
+ 'hlp' => 'application/winhlp',
+ 'wsdl' => 'application/wsdl+xml',
+ 'wspolicy' => 'application/wspolicy+xml',
+ '7z' => 'application/x-7z-compressed',
+ 'abw' => 'application/x-abiword',
+ 'ace' => 'application/x-ace-compressed',
+ 'dmg' => 'application/x-apple-diskimage',
+ 'aab' => 'application/x-authorware-bin',
+ 'aam' => 'application/x-authorware-map',
+ 'aas' => 'application/x-authorware-seg',
+ 'bcpio' => 'application/x-bcpio',
+ 'torrent' => 'application/x-bittorrent',
+ 'blb' => 'application/x-blorb',
+ 'bz' => 'application/x-bzip',
+ 'bz2' => 'application/x-bzip2',
+ 'cbr' => 'application/x-cbr',
+ 'vcd' => 'application/x-cdlink',
+ 'cfs' => 'application/x-cfs-compressed',
+ 'chat' => 'application/x-chat',
+ 'pgn' => 'application/x-chess-pgn',
+ 'nsc' => 'application/x-conference',
+ 'cpio' => 'application/x-cpio',
+ 'csh' => 'application/x-csh',
+ 'deb' => 'application/x-debian-package',
+ 'dgc' => 'application/x-dgc-compressed',
+ 'dir' => 'application/x-director',
+ 'wad' => 'application/x-doom',
+ 'ncx' => 'application/x-dtbncx+xml',
+ 'dtb' => 'application/x-dtbook+xml',
+ 'res' => 'application/x-dtbresource+xml',
+ 'dvi' => 'application/x-dvi',
+ 'evy' => 'application/x-envoy',
+ 'eva' => 'application/x-eva',
+ 'bdf' => 'application/x-font-bdf',
+ 'gsf' => 'application/x-font-ghostscript',
+ 'psf' => 'application/x-font-linux-psf',
+ 'otf' => 'application/x-font-otf',
+ 'pcf' => 'application/x-font-pcf',
+ 'snf' => 'application/x-font-snf',
+ 'ttf' => 'application/x-font-ttf',
+ 'pfa' => 'application/x-font-type1',
+ 'woff' => 'application/x-font-woff',
+ 'arc' => 'application/x-freearc',
+ 'spl' => 'application/x-futuresplash',
+ 'gca' => 'application/x-gca-compressed',
+ 'ulx' => 'application/x-glulx',
+ 'gnumeric' => 'application/x-gnumeric',
+ 'gramps' => 'application/x-gramps-xml',
+ 'gtar' => 'application/x-gtar',
+ 'hdf' => 'application/x-hdf',
+ 'install' => 'application/x-install-instructions',
+ 'iso' => 'application/x-iso9660-image',
+ 'jnlp' => 'application/x-java-jnlp-file',
+ 'latex' => 'application/x-latex',
+ 'lzh' => 'application/x-lzh-compressed',
+ 'mie' => 'application/x-mie',
+ 'prc' => 'application/x-mobipocket-ebook',
+ 'application' => 'application/x-ms-application',
+ 'lnk' => 'application/x-ms-shortcut',
+ 'wmd' => 'application/x-ms-wmd',
+ 'wmz' => 'application/x-ms-wmz',
+ 'xbap' => 'application/x-ms-xbap',
+ 'mdb' => 'application/x-msaccess',
+ 'obd' => 'application/x-msbinder',
+ 'crd' => 'application/x-mscardfile',
+ 'clp' => 'application/x-msclip',
+ 'exe' => 'application/x-msdownload',
+ 'mvb' => 'application/x-msmediaview',
+ 'wmf' => 'application/x-msmetafile',
+ 'mny' => 'application/x-msmoney',
+ 'pub' => 'application/x-mspublisher',
+ 'scd' => 'application/x-msschedule',
+ 'trm' => 'application/x-msterminal',
+ 'wri' => 'application/x-mswrite',
+ 'nc' => 'application/x-netcdf',
+ 'nzb' => 'application/x-nzb',
+ 'p12' => 'application/x-pkcs12',
+ 'p7b' => 'application/x-pkcs7-certificates',
+ 'p7r' => 'application/x-pkcs7-certreqresp',
+ 'rar' => 'application/x-rar',
+ 'ris' => 'application/x-research-info-systems',
+ 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar',
+ 'swf' => 'application/x-shockwave-flash',
+ 'xap' => 'application/x-silverlight-app',
+ 'sql' => 'application/x-sql',
+ 'sit' => 'application/x-stuffit',
+ 'sitx' => 'application/x-stuffitx',
+ 'srt' => 'application/x-subrip',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc',
+ 't3' => 'application/x-t3vm-image',
+ 'gam' => 'application/x-tads',
+ 'tar' => 'application/x-tar',
+ 'tcl' => 'application/x-tcl',
+ 'tex' => 'application/x-tex',
+ 'tfm' => 'application/x-tex-tfm',
+ 'texinfo' => 'application/x-texinfo',
+ 'obj' => 'application/x-tgif',
+ 'ustar' => 'application/x-ustar',
+ 'src' => 'application/x-wais-source',
+ 'der' => 'application/x-x509-ca-cert',
+ 'fig' => 'application/x-xfig',
+ 'xlf' => 'application/x-xliff+xml',
+ 'xpi' => 'application/x-xpinstall',
+ 'xz' => 'application/x-xz',
+ 'z1' => 'application/x-zmachine',
+ 'xaml' => 'application/xaml+xml',
+ 'xdf' => 'application/xcap-diff+xml',
+ 'xenc' => 'application/xenc+xml',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xml' => 'application/xml',
+ 'dtd' => 'application/xml-dtd',
+ 'xop' => 'application/xop+xml',
+ 'xpl' => 'application/xproc+xml',
+ 'xslt' => 'application/xslt+xml',
+ 'xspf' => 'application/xspf+xml',
+ 'mxml' => 'application/xv+xml',
+ 'yang' => 'application/yang',
+ 'yin' => 'application/yin+xml',
+ 'zip' => 'application/zip',
+ 'adp' => 'audio/adpcm',
+ 'au' => 'audio/basic',
+ 'mid' => 'audio/midi',
+ 'mp3' => 'audio/mpeg',
+ 'mp4a' => 'audio/mp4',
+ 'mpga' => 'audio/mpeg',
+ 'oga' => 'audio/ogg',
+ 's3m' => 'audio/s3m',
+ 'sil' => 'audio/silk',
+ 'uva' => 'audio/vnd.dece.audio',
+ 'eol' => 'audio/vnd.digital-winds',
+ 'dra' => 'audio/vnd.dra',
+ 'dts' => 'audio/vnd.dts',
+ 'dtshd' => 'audio/vnd.dts.hd',
+ 'lvp' => 'audio/vnd.lucent.voice',
+ 'pya' => 'audio/vnd.ms-playready.media.pya',
+ 'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
+ 'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
+ 'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
+ 'rip' => 'audio/vnd.rip',
+ 'weba' => 'audio/webm',
+ 'aac' => 'audio/x-aac',
+ 'aif' => 'audio/x-aiff',
+ 'caf' => 'audio/x-caf',
+ 'flac' => 'audio/x-flac',
+ 'mka' => 'audio/x-matroska',
+ 'm3u' => 'audio/x-mpegurl',
+ 'wax' => 'audio/x-ms-wax',
+ 'wma' => 'audio/x-ms-wma',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'rmp' => 'audio/x-pn-realaudio-plugin',
+ 'wav' => 'audio/x-wav',
+ 'xm' => 'audio/xm',
+ 'cdx' => 'chemical/x-cdx',
+ 'cif' => 'chemical/x-cif',
+ 'cmdf' => 'chemical/x-cmdf',
+ 'cml' => 'chemical/x-cml',
+ 'csml' => 'chemical/x-csml',
+ 'xyz' => 'chemical/x-xyz',
+ 'bmp' => 'image/bmp',
+ 'cgm' => 'image/cgm',
+ 'g3' => 'image/g3fax',
+ 'gif' => 'image/gif',
+ 'ief' => 'image/ief',
+ 'jpg' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'ktx' => 'image/ktx',
+ 'png' => 'image/png',
+ 'btif' => 'image/prs.btif',
+ 'sgi' => 'image/sgi',
+ 'svg' => 'image/svg+xml',
+ 'tiff' => 'image/tiff',
+ 'psd' => 'image/vnd.adobe.photoshop',
+ 'uvi' => 'image/vnd.dece.graphic',
+ 'djvu' => 'image/vnd.djvu',
+ 'dwg' => 'image/vnd.dwg',
+ 'dxf' => 'image/vnd.dxf',
+ 'fbs' => 'image/vnd.fastbidsheet',
+ 'fpx' => 'image/vnd.fpx',
+ 'fst' => 'image/vnd.fst',
+ 'mmr' => 'image/vnd.fujixerox.edmics-mmr',
+ 'rlc' => 'image/vnd.fujixerox.edmics-rlc',
+ 'mdi' => 'image/vnd.ms-modi',
+ 'wdp' => 'image/vnd.ms-photo',
+ 'npx' => 'image/vnd.net-fpx',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'xif' => 'image/vnd.xiff',
+ 'webp' => 'image/webp',
+ '3ds' => 'image/x-3ds',
+ 'ras' => 'image/x-cmu-raster',
+ 'cmx' => 'image/x-cmx',
+ 'fh' => 'image/x-freehand',
+ 'ico' => 'image/x-icon',
+ 'sid' => 'image/x-mrsid-image',
+ 'pcx' => 'image/x-pcx',
+ 'pic' => 'image/x-pict',
+ 'pnm' => 'image/x-portable-anymap',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pgm' => 'image/x-portable-graymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'rgb' => 'image/x-rgb',
+ 'tga' => 'image/x-tga',
+ 'xbm' => 'image/x-xbitmap',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'eml' => 'message/rfc822',
+ 'igs' => 'model/iges',
+ 'msh' => 'model/mesh',
+ 'dae' => 'model/vnd.collada+xml',
+ 'dwf' => 'model/vnd.dwf',
+ 'gdl' => 'model/vnd.gdl',
+ 'gtw' => 'model/vnd.gtw',
+ 'mts' => 'model/vnd.mts',
+ 'vtu' => 'model/vnd.vtu',
+ 'wrl' => 'model/vrml',
+ 'x3db' => 'model/x3d+binary',
+ 'x3dv' => 'model/x3d+vrml',
+ 'x3d' => 'model/x3d+xml',
+ 'appcache' => 'text/cache-manifest',
+ 'ics' => 'text/calendar',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'html' => 'text/html',
+ 'n3' => 'text/n3',
+ 'txt' => 'text/plain',
+ 'dsc' => 'text/prs.lines.tag',
+ 'rtx' => 'text/richtext',
+ 'rtf' => 'text/rtf',
+ 'sgml' => 'text/sgml',
+ 'tsv' => 'text/tab-separated-values',
+ 't' => 'text/troff',
+ 'ttl' => 'text/turtle',
+ 'uri' => 'text/uri-list',
+ 'vcard' => 'text/vcard',
+ 'curl' => 'text/vnd.curl',
+ 'dcurl' => 'text/vnd.curl.dcurl',
+ 'scurl' => 'text/vnd.curl.scurl',
+ 'mcurl' => 'text/vnd.curl.mcurl',
+ 'sub' => 'text/vnd.dvb.subtitle',
+ 'fly' => 'text/vnd.fly',
+ 'flx' => 'text/vnd.fmi.flexstor',
+ 'gv' => 'text/vnd.graphviz',
+ '3dml' => 'text/vnd.in3d.3dml',
+ 'spot' => 'text/vnd.in3d.spot',
+ 'jad' => 'text/vnd.sun.j2me.app-descriptor',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 's' => 'text/x-asm',
+ 'c' => 'text/x-c',
+ 'f' => 'text/x-fortran',
+ 'p' => 'text/x-pascal',
+ 'java' => 'text/x-java-source',
+ 'opml' => 'text/x-opml',
+ 'nfo' => 'text/x-nfo',
+ 'etx' => 'text/x-setext',
+ 'sfv' => 'text/x-sfv',
+ 'uu' => 'text/x-uuencode',
+ 'vcs' => 'text/x-vcalendar',
+ 'vcf' => 'text/x-vcard',
+ '3gp' => 'video/3gpp',
+ '3g2' => 'video/3gpp2',
+ 'h261' => 'video/h261',
+ 'h263' => 'video/h263',
+ 'h264' => 'video/h264',
+ 'jpgv' => 'video/jpeg',
+ 'jpm' => 'video/jpm',
+ 'mj2' => 'video/mj2',
+ 'mp4' => 'video/mp4',
+ 'mpeg' => 'video/mpeg',
+ 'ogv' => 'video/ogg',
+ 'mov' => 'video/quicktime',
+ 'qt' => 'video/quicktime',
+ 'uvh' => 'video/vnd.dece.hd',
+ 'uvm' => 'video/vnd.dece.mobile',
+ 'uvp' => 'video/vnd.dece.pd',
+ 'uvs' => 'video/vnd.dece.sd',
+ 'uvv' => 'video/vnd.dece.video',
+ 'dvb' => 'video/vnd.dvb.file',
+ 'fvt' => 'video/vnd.fvt',
+ 'mxu' => 'video/vnd.mpegurl',
+ 'pyv' => 'video/vnd.ms-playready.media.pyv',
+ 'uvu' => 'video/vnd.uvvu.mp4',
+ 'viv' => 'video/vnd.vivo',
+ 'webm' => 'video/webm',
+ 'f4v' => 'video/x-f4v',
+ 'fli' => 'video/x-fli',
+ 'flv' => 'video/x-flv',
+ 'm4v' => 'video/x-m4v',
+ 'mkv' => 'video/x-matroska',
+ 'mng' => 'video/x-mng',
+ 'asf' => 'video/x-ms-asf',
+ 'vob' => 'video/x-ms-vob',
+ 'wm' => 'video/x-ms-wm',
+ 'wmv' => 'video/x-ms-wmv',
+ 'wmx' => 'video/x-ms-wmx',
+ 'wvx' => 'video/x-ms-wvx',
+ 'avi' => 'video/x-msvideo',
+ 'movie' => 'video/x-sgi-movie',
+ 'smv' => 'video/x-smv',
+ 'ice' => 'x-conference/x-cooltalk',
+ ];
+
+ /**
+ * Get the MIME type for a file based on the file's extension.
+ *
+ * @param string $filename
+ * @return string
+ */
+ public static function from($filename)
+ {
+ $extension = strtok(pathinfo($filename, PATHINFO_EXTENSION), '?');
+
+ return self::getMimeTypeFromExtension($extension);
+ }
+
+ /**
+ * Get the MIME type for a given extension or return all mimes.
+ *
+ * @param string $extension
+ * @return string|array
+ */
+ public static function get($extension = null)
+ {
+ return isset($extension) ? self::getMimeTypeFromExtension($extension) : self::$mimes;
+ }
+
+ /**
+ * Get the MIME type for a given extension.
+ *
+ * @param string $extension
+ * @return string
+ */
+ protected static function getMimeTypeFromExtension($extension)
+ {
+ return self::$mimes[$extension] ?? 'application/octet-stream';
+ }
+}
diff --git a/src/Helpers/OS.php b/src/Helpers/OS.php
new file mode 100644
index 00000000..7a22a6fb
--- /dev/null
+++ b/src/Helpers/OS.php
@@ -0,0 +1,61 @@
+when = $when;
+ $this->path = $path;
+ $this->types = $types;
+ }
+
+ /**
+ * @return \Carbon\Carbon
+ */
+ public function getWhen(): Carbon
+ {
+ return $this->when;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ /**
+ * @return array
+ */
+ public function getTypes(): array
+ {
+ return $this->types;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType(): string
+ {
+ return Arr::first($this->types);
+ }
+
+ /**
+ * Checks if event types has needed type(s).
+ *
+ * @param string ...$types
+ *
+ * @return bool
+ */
+ public function isType(string ...$types): bool
+ {
+ return count(array_intersect($this->types, $types)) > 0;
+ }
+
+ /**
+ * Get possible event types.
+ *
+ * @return array
+ */
+ public static function getPossibleTypes(): array
+ {
+ return self::$possibleTypes;
+ }
+}
\ No newline at end of file
diff --git a/src/HotReload/FSEventParser.php b/src/HotReload/FSEventParser.php
new file mode 100644
index 00000000..b37fbc8c
--- /dev/null
+++ b/src/HotReload/FSEventParser.php
@@ -0,0 +1,37 @@
+getPath()) ? 'Directory' : 'File';
+ $events = implode(', ', $event->getTypes());
+ $time = $event->getWhen()->format('Y.m.d H:i:s');
+
+ return sprintf('%s: %s %s at %s', $item, $event->getPath(), $events, $time);
+ }
+}
\ No newline at end of file
diff --git a/src/HotReload/FSProcess.php b/src/HotReload/FSProcess.php
new file mode 100644
index 00000000..4747d07b
--- /dev/null
+++ b/src/HotReload/FSProcess.php
@@ -0,0 +1,96 @@
+filter = $filter;
+ $this->recursively = $recursively;
+ $this->directory = $directory;
+ $this->locked = false;
+ }
+
+ /**
+ * Make swoole process.
+ *
+ * @param callable|null $callback
+ *
+ * @return \Swoole\Process
+ */
+ public function make(?callable $callback = null)
+ {
+ $mcb = function ($type, $buffer) use ($callback) {
+ if (! $this->locked && AppProcess::OUT === $type && $event = FSEventParser::toEvent($buffer)) {
+ $this->locked = true;
+ ($callback) ? $callback($event) : null;
+ $this->locked = false;
+ unset($event);
+ }
+ };
+
+ return new SwooleProcess(function () use ($mcb) {
+ (new AppProcess($this->configure()))->setTimeout(0)->run($mcb);
+ }, false, false);
+ }
+
+ /**
+ * Configure process.
+ *
+ * @return array
+ */
+ protected function configure(): array
+ {
+ return [
+ 'fswatch',
+ $this->recursively ? '-rtx' : '-tx',
+ '-e',
+ '.*',
+ '-i',
+ "\\{$this->filter}$",
+ $this->directory,
+ ];
+ }
+}
diff --git a/src/HttpServiceProvider.php b/src/HttpServiceProvider.php
index 6f6c0b2a..fdc7460a 100644
--- a/src/HttpServiceProvider.php
+++ b/src/HttpServiceProvider.php
@@ -2,14 +2,18 @@
namespace SwooleTW\Http;
+use SwooleTW\Http\Helpers\FW;
+use Illuminate\Queue\QueueManager;
+use SwooleTW\Http\Server\PidManager;
use Swoole\Http\Server as HttpServer;
use Illuminate\Support\ServiceProvider;
+use Illuminate\Database\DatabaseManager;
use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Coroutine\MySqlConnection;
use SwooleTW\Http\Commands\HttpServerCommand;
use Swoole\Websocket\Server as WebsocketServer;
-use SwooleTW\Http\Coroutine\Connectors\MySqlConnector;
use SwooleTW\Http\Task\Connectors\SwooleTaskConnector;
+use SwooleTW\Http\Coroutine\Connectors\ConnectorFactory;
/**
* @codeCoverageIgnore
@@ -34,17 +38,39 @@ abstract class HttpServiceProvider extends ServiceProvider
protected static $server;
/**
- * Register the service provider.
+ * Boot the service provider.
*
* @return void
*/
- public function register()
+ public function boot()
{
+ $this->publishFiles();
+ $this->loadConfigs();
$this->mergeConfigs();
$this->setIsWebsocket();
+
+ $config = $this->app->make('config');
+
+ if ($config->get('swoole_http.websocket.enabled')) {
+ $this->bootWebsocketRoutes();
+ }
+
+ if ($config->get('swoole_http.server.access_log')) {
+ $this->pushAccessLogMiddleware();
+ }
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
$this->registerServer();
$this->registerManager();
$this->registerCommands();
+ $this->registerPidManager();
$this->registerDatabaseDriver();
$this->registerSwooleQueueDriver();
}
@@ -57,28 +83,37 @@ public function register()
abstract protected function registerManager();
/**
- * Boot routes.
+ * Boot websocket routes.
*
* @return void
*/
- abstract protected function bootRoutes();
+ abstract protected function bootWebsocketRoutes();
/**
- * Boot the service provider.
+ * Register access log middleware to container.
*
* @return void
*/
- public function boot()
+ abstract protected function pushAccessLogMiddleware();
+
+ /**
+ * Publish files of this package.
+ */
+ protected function publishFiles()
{
$this->publishes([
__DIR__ . '/../config/swoole_http.php' => base_path('config/swoole_http.php'),
__DIR__ . '/../config/swoole_websocket.php' => base_path('config/swoole_websocket.php'),
- __DIR__ . '/../routes/websocket.php' => base_path('routes/websocket.php')
+ __DIR__ . '/../routes/websocket.php' => base_path('routes/websocket.php'),
], 'laravel-swoole');
+ }
- if ($this->app['config']->get('swoole_http.websocket.enabled')) {
- $this->bootRoutes();
- }
+ /**
+ * Load configurations.
+ */
+ protected function loadConfigs()
+ {
+ // do nothing
}
/**
@@ -90,12 +125,27 @@ protected function mergeConfigs()
$this->mergeConfigFrom(__DIR__ . '/../config/swoole_websocket.php', 'swoole_websocket');
}
+ /**
+ * Register pid manager.
+ *
+ * @return void
+ */
+ protected function registerPidManager(): void
+ {
+ $this->app->singleton(PidManager::class, function() {
+ return new PidManager(
+ $this->app->make('config')->get('swoole_http.server.options.pid_file')
+ );
+ });
+ }
+
/**
* Set isWebsocket.
*/
protected function setIsWebsocket()
{
- $this->isWebsocket = $this->app['config']->get('swoole_http.websocket.enabled');
+ $this->isWebsocket = $this->app->make('config')
+ ->get('swoole_http.websocket.enabled');
}
/**
@@ -114,11 +164,13 @@ protected function registerCommands()
protected function createSwooleServer()
{
$server = $this->isWebsocket ? WebsocketServer::class : HttpServer::class;
- $host = $this->app['config']->get('swoole_http.server.host');
- $port = $this->app['config']->get('swoole_http.server.port');
- $socketType = $this->app['config']->get('swoole_http.server.socket_type', SWOOLE_SOCK_TCP);
+ $config = $this->app->make('config');
+ $host = $config->get('swoole_http.server.host');
+ $port = $config->get('swoole_http.server.port');
+ $socketType = $config->get('swoole_http.server.socket_type', SWOOLE_SOCK_TCP);
+ $processType = $config->get('swoole_http.server.process_type', SWOOLE_PROCESS);
- static::$server = new $server($host, $port, SWOOLE_PROCESS, $socketType);
+ static::$server = new $server($host, $port, $processType, $socketType);
}
/**
@@ -126,12 +178,12 @@ protected function createSwooleServer()
*/
protected function configureSwooleServer()
{
- $config = $this->app['config'];
+ $config = $this->app->make('config');
$options = $config->get('swoole_http.server.options');
// only enable task worker in websocket mode and for queue driver
if ($config->get('queue.default') !== 'swoole' && ! $this->isWebsocket) {
- unset($config['task_worker_num']);
+ unset($options['task_worker_num']);
}
static::$server->set($options);
@@ -149,6 +201,7 @@ protected function registerServer()
$this->createSwooleServer();
$this->configureSwooleServer();
}
+
return static::$server;
});
$this->app->alias(Server::class, 'swoole.server');
@@ -159,29 +212,51 @@ protected function registerServer()
*/
protected function registerDatabaseDriver()
{
- $this->app->resolving('db', function ($db) {
+ $this->app->extend(DatabaseManager::class, function (DatabaseManager $db) {
$db->extend('mysql-coroutine', function ($config, $name) {
$config['name'] = $name;
- $connection = function () use ($config) {
- return (new MySqlConnector())->connect($config);
- };
- return new MySqlConnection(
- $connection,
+ $connection = new MySqlConnection(
+ $this->getNewMySqlConnection($config, 'write'),
$config['database'],
$config['prefix'],
$config
);
+
+ if (isset($config['read'])) {
+ $connection->setReadPdo($this->getNewMySqlConnection($config, 'read'));
+ }
+
+ return $connection;
});
+
+ return $db;
});
}
+ /**
+ * Get a new mysql connection.
+ *
+ * @param array $config
+ * @param string $connection
+ *
+ * @return \PDO
+ */
+ protected function getNewMySqlConnection(array $config, string $connection = null)
+ {
+ if ($connection && isset($config[$connection])) {
+ $config = array_merge($config, $config[$connection]);
+ }
+
+ return ConnectorFactory::make(FW::version())->connect($config);
+ }
+
/**
* Register queue driver for swoole async task.
*/
protected function registerSwooleQueueDriver()
{
- $this->app->afterResolving('queue', function ($manager) {
+ $this->app->afterResolving('queue', function (QueueManager $manager) {
$manager->addConnector('swoole', function () {
return new SwooleTaskConnector($this->app->make(Server::class));
});
diff --git a/src/LaravelServiceProvider.php b/src/LaravelServiceProvider.php
index 5cf86a30..3f58e2ca 100644
--- a/src/LaravelServiceProvider.php
+++ b/src/LaravelServiceProvider.php
@@ -3,6 +3,8 @@
namespace SwooleTW\Http;
use SwooleTW\Http\Server\Manager;
+use Illuminate\Contracts\Http\Kernel;
+use SwooleTW\Http\Middleware\AccessLog;
/**
* @codeCoverageIgnore
@@ -16,18 +18,30 @@ class LaravelServiceProvider extends HttpServiceProvider
*/
protected function registerManager()
{
- $this->app->singleton('swoole.manager', function ($app) {
+ $this->app->singleton(Manager::class, function ($app) {
return new Manager($app, 'laravel');
});
+
+ $this->app->alias(Manager::class, 'swoole.manager');
+ }
+
+ /**
+ * Boot websocket routes.
+ *
+ * @return void
+ */
+ protected function bootWebsocketRoutes()
+ {
+ require __DIR__ . '/../routes/laravel_routes.php';
}
/**
- * Boot routes.
+ * Register access log middleware to container.
*
* @return void
*/
- protected function bootRoutes()
+ protected function pushAccessLogMiddleware()
{
- require __DIR__.'/../routes/laravel_routes.php';
+ $this->app->make(Kernel::class)->pushMiddleware(AccessLog::class);
}
}
diff --git a/src/LumenServiceProvider.php b/src/LumenServiceProvider.php
index f7ae8414..3270ded9 100644
--- a/src/LumenServiceProvider.php
+++ b/src/LumenServiceProvider.php
@@ -3,12 +3,22 @@
namespace SwooleTW\Http;
use SwooleTW\Http\Server\Manager;
+use SwooleTW\Http\Middleware\AccessLog;
/**
* @codeCoverageIgnore
*/
class LumenServiceProvider extends HttpServiceProvider
{
+ /**
+ * Load configurations.
+ */
+ protected function loadConfigs()
+ {
+ $this->app->configure('swoole_http');
+ $this->app->configure('swoole_websocket');
+ }
+
/**
* Register manager.
*
@@ -16,28 +26,33 @@ class LumenServiceProvider extends HttpServiceProvider
*/
protected function registerManager()
{
- $this->app->singleton('swoole.manager', function ($app) {
+ $this->app->singleton(Manager::class, function ($app) {
return new Manager($app, 'lumen');
});
+
+ $this->app->alias(Manager::class, 'swoole.manager');
}
/**
- * Boot routes.
+ * Boot websocket routes.
*
* @return void
*/
- protected function bootRoutes()
+ protected function bootWebsocketRoutes()
{
- $app = $this->app;
-
- if (property_exists($app, 'router')) {
- $app->router->group(['namespace' => 'SwooleTW\Http\Controllers'], function ($app) {
- require __DIR__ . '/../routes/lumen_routes.php';
- });
- } else {
- $app->group(['namespace' => 'App\Http\Controllers'], function ($app) {
+ $this->app->router
+ ->group(['namespace' => 'SwooleTW\Http\Controllers'], function ($router) {
require __DIR__ . '/../routes/lumen_routes.php';
});
- }
+ }
+
+ /**
+ * Register access log middleware to container.
+ *
+ * @return void
+ */
+ protected function pushAccessLogMiddleware()
+ {
+ $this->app->middleware(AccessLog::class);
}
}
diff --git a/src/Middleware/AccessLog.php b/src/Middleware/AccessLog.php
new file mode 100644
index 00000000..2041b8cf
--- /dev/null
+++ b/src/Middleware/AccessLog.php
@@ -0,0 +1,55 @@
+output = $output;
+ }
+
+ /**
+ * Handle the incoming request.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
+ * @return mixed
+ */
+ public function handle($request, Closure $next)
+ {
+ return $next($request);
+ }
+
+ /**
+ * Handle the outgoing request and response.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Symfony\Component\HttpFoundation\Response $response
+ */
+ public function terminate(Request $request, Response $response)
+ {
+ $this->output->log($request, $response);
+ }
+}
\ No newline at end of file
diff --git a/src/Server/AccessOutput.php b/src/Server/AccessOutput.php
new file mode 100644
index 00000000..08de5046
--- /dev/null
+++ b/src/Server/AccessOutput.php
@@ -0,0 +1,71 @@
+output = $output;
+ }
+
+ /**
+ * Access log.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \Symfony\Component\HttpFoundation\Response $response
+ */
+ public function log(Request $request, Response $response): void
+ {
+ $host = $request->url();
+ $method = $request->method();
+ $agent = $request->userAgent();
+ $date = $this->date($response->getDate());
+ $status = $response->getStatusCode();
+ $style = $this->style($status);
+
+ $this->output->writeln(
+ sprintf("%s %s %s <$style>%d$style> %s", $host, $date, $method, $status, $agent)
+ );
+ }
+
+ /**
+ * @param \DateTimeInterface $date
+ *
+ * @return string
+ */
+ protected function date(DateTimeInterface $date): string
+ {
+ return $date->format('Y-m-d H:i:s');
+ }
+
+ /**
+ * @param int $status
+ *
+ * @return string
+ */
+ protected function style(int $status): string
+ {
+ return $status !== Response::HTTP_OK ? 'error' : 'info';
+ }
+}
diff --git a/src/Server/Facades/Sandbox.php b/src/Server/Facades/Sandbox.php
index 8c468254..6dd2bd4f 100644
--- a/src/Server/Facades/Sandbox.php
+++ b/src/Server/Facades/Sandbox.php
@@ -4,6 +4,26 @@
use Illuminate\Support\Facades\Facade;
+/**
+ * @method static this setFramework($framework)
+ * @method static string getFramework()
+ * @method static this setBaseApp($app)
+ * @method static \Illuminate\Container\Container getBaseApp()
+ * @method static \Illuminate\Container\Container getApplication()
+ * @method static this setRequest($request)
+ * @method static \Illuminate\Http\Request getRequest($request)
+ * @method static \Illuminate\Http\Response run()
+ * @method static this setSnapshot($snapshot)
+ * @method static \Illuminate\Container\Container getSnapshot()
+ * @method static this initialize()
+ * @method static boolean isLaravel()
+ * @method static void terminate($request, $response)
+ * @method static void enable()
+ * @method static void disable()
+ * @method static void setInstance($app)
+ *
+ * @see \SwooleTW\Http\Server\Sandbox
+ */
class Sandbox extends Facade
{
/**
diff --git a/src/Server/Facades/Server.php b/src/Server/Facades/Server.php
index 7a34102a..eb422aa3 100644
--- a/src/Server/Facades/Server.php
+++ b/src/Server/Facades/Server.php
@@ -4,6 +4,11 @@
use Illuminate\Support\Facades\Facade;
+/**
+ * Class Server
+ *
+ * @mixin \Swoole\Http\Server
+ */
class Server extends Facade
{
/**
diff --git a/src/Server/Manager.php b/src/Server/Manager.php
index b5715d81..0495aba1 100644
--- a/src/Server/Manager.php
+++ b/src/Server/Manager.php
@@ -2,24 +2,37 @@
namespace SwooleTW\Http\Server;
+use Exception;
use Throwable;
-use Swoole\Http\Server;
+use Swoole\Process;
+use Swoole\Server\Task;
+use Illuminate\Support\Str;
+use SwooleTW\Http\Helpers\OS;
use SwooleTW\Http\Server\Sandbox;
+use SwooleTW\Http\Server\PidManager;
use SwooleTW\Http\Task\SwooleTaskJob;
use Illuminate\Support\Facades\Facade;
use SwooleTW\Http\Websocket\Websocket;
use SwooleTW\Http\Transformers\Request;
+use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Transformers\Response;
use SwooleTW\Http\Concerns\WithApplication;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Debug\ExceptionHandler;
use SwooleTW\Http\Concerns\InteractsWithWebsocket;
+use Symfony\Component\Console\Output\ConsoleOutput;
+use SwooleTW\Http\Concerns\InteractsWithSwooleQueue;
use SwooleTW\Http\Concerns\InteractsWithSwooleTable;
+use Symfony\Component\Debug\Exception\FatalThrowableError;
+/**
+ * Class Manager
+ */
class Manager
{
use InteractsWithWebsocket,
InteractsWithSwooleTable,
+ InteractsWithSwooleQueue,
WithApplication;
/**
@@ -45,18 +58,30 @@ class Manager
* @var array
*/
protected $events = [
- 'start', 'shutDown', 'workerStart', 'workerStop', 'packet',
- 'bufferFull', 'bufferEmpty', 'task', 'finish', 'pipeMessage',
- 'workerError', 'managerStart', 'managerStop', 'request',
+ 'start',
+ 'shutDown',
+ 'workerStart',
+ 'workerStop',
+ 'packet',
+ 'bufferFull',
+ 'bufferEmpty',
+ 'task',
+ 'finish',
+ 'pipeMessage',
+ 'workerError',
+ 'managerStart',
+ 'managerStop',
+ 'request',
];
/**
* HTTP server manager constructor.
*
- * @param \Swoole\Http\Server $server
* @param \Illuminate\Contracts\Container\Container $container
* @param string $framework
* @param string $basePath
+ *
+ * @throws \Exception
*/
public function __construct(Container $container, $framework, $basePath = null)
{
@@ -71,7 +96,7 @@ public function __construct(Container $container, $framework, $basePath = null)
*/
public function run()
{
- $this->container['swoole.server']->start();
+ $this->container->make(Server::class)->start();
}
/**
@@ -79,7 +104,7 @@ public function run()
*/
public function stop()
{
- $this->container['swoole.server']->shutdown();
+ $this->container->make(Server::class)->shutdown();
}
/**
@@ -89,7 +114,10 @@ protected function initialize()
{
$this->createTables();
$this->prepareWebsocket();
- $this->setSwooleServerListeners();
+
+ if (! $this->container->make(Server::class)->taskworker) {
+ $this->setSwooleServerListeners();
+ }
}
/**
@@ -97,18 +125,14 @@ protected function initialize()
*/
protected function setSwooleServerListeners()
{
+ $server = $this->container->make(Server::class);
foreach ($this->events as $event) {
- $listener = 'on' . ucfirst($event);
-
- if (method_exists($this, $listener)) {
- $this->container['swoole.server']->on($event, [$this, $listener]);
- } else {
- $this->container['swoole.server']->on($event, function () use ($event) {
- $event = sprintf('swoole.%s', $event);
+ $listener = Str::camel("on_$event");
+ $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) {
+ $this->container->make('events')->dispatch("swoole.$event", func_get_args());
+ };
- $this->container['events']->fire($event, func_get_args());
- });
- }
+ $server->on($event, $callback);
}
}
@@ -118,9 +142,11 @@ protected function setSwooleServerListeners()
public function onStart()
{
$this->setProcessName('master process');
- $this->createPidFile();
- $this->container['events']->fire('swoole.start', func_get_args());
+ $server = $this->container->make(Server::class);
+ $this->container->make(PidManager::class)->write($server->master_pid, $server->manager_pid ?? 0);
+
+ $this->container->make('events')->dispatch('swoole.start', func_get_args());
}
/**
@@ -131,23 +157,24 @@ public function onStart()
public function onManagerStart()
{
$this->setProcessName('manager process');
- $this->container['events']->fire('swoole.managerStart', func_get_args());
+
+ $this->container->make('events')->dispatch('swoole.managerStart', func_get_args());
}
/**
* "onWorkerStart" listener.
+ *
+ * @param \Swoole\Http\Server|mixed $server
+ *
+ * @throws \Exception
*/
public function onWorkerStart($server)
{
$this->clearCache();
- $this->setProcessName('worker process');
- $this->container['events']->fire('swoole.workerStart', func_get_args());
+ $this->container->make('events')->dispatch('swoole.workerStart', func_get_args());
- // don't init laravel app in task workers
- if ($server->taskworker) {
- return;
- }
+ $this->setProcessName($server->taskworker ? 'task process' : 'worker process');
// clear events instance in case of repeated listeners in worker process
Facade::clearResolvedInstance('events');
@@ -159,7 +186,7 @@ public function onWorkerStart($server)
$this->bindToLaravelApp();
// prepare websocket handler and routes
- if ($this->isWebsocket) {
+ if ($this->isServerWebsocket) {
$this->prepareWebsocketHandler();
$this->loadWebsocketRoutes();
}
@@ -173,11 +200,12 @@ public function onWorkerStart($server)
*/
public function onRequest($swooleRequest, $swooleResponse)
{
- $this->app['events']->fire('swoole.request');
+ $this->app->make('events')->dispatch('swoole.request');
$this->resetOnRequest();
- $handleStatic = $this->container['config']->get('swoole_http.handle_static_files', true);
- $publicPath = $this->container['config']->get('swoole_http.server.public_path', base_path('public'));
+ $sandbox = $this->app->make(Sandbox::class);
+ $handleStatic = $this->container->make('config')->get('swoole_http.server.handle_static_files', true);
+ $publicPath = $this->container->make('config')->get('swoole_http.server.public_path', base_path('public'));
try {
// handle static file request first
@@ -188,25 +216,31 @@ public function onRequest($swooleRequest, $swooleResponse)
$illuminateRequest = Request::make($swooleRequest)->toIlluminate();
// set current request to sandbox
- $this->app['swoole.sandbox']->setRequest($illuminateRequest);
+ $sandbox->setRequest($illuminateRequest);
+
// enable sandbox
- $this->app['swoole.sandbox']->enable();
+ $sandbox->enable();
// handle request via laravel/lumen's dispatcher
- $illuminateResponse = $this->app['swoole.sandbox']->run($illuminateRequest);
- $response = Response::make($illuminateResponse, $swooleResponse);
- $response->send();
+ $illuminateResponse = $sandbox->run($illuminateRequest);
+
+ // send response
+ Response::make($illuminateResponse, $swooleResponse)->send();
} catch (Throwable $e) {
try {
- $exceptionResponse = $this->app[ExceptionHandler::class]->render($illuminateRequest, $e);
- $response = Response::make($exceptionResponse, $swooleResponse);
- $response->send();
+ $exceptionResponse = $this->app
+ ->make(ExceptionHandler::class)
+ ->render(
+ $illuminateRequest,
+ $this->normalizeException($e)
+ );
+ Response::make($exceptionResponse, $swooleResponse)->send();
} catch (Throwable $e) {
$this->logServerError($e);
}
} finally {
// disable and recycle sandbox resource
- $this->app['swoole.sandbox']->disable();
+ $sandbox->disable();
}
}
@@ -216,33 +250,30 @@ public function onRequest($swooleRequest, $swooleResponse)
protected function resetOnRequest()
{
// Reset websocket data
- if ($this->isWebsocket) {
- $this->app['swoole.websocket']->reset(true);
+ if ($this->isServerWebsocket) {
+ $this->app->make(Websocket::class)->reset(true);
}
}
/**
* Set onTask listener.
+ *
+ * @param mixed $server
+ * @param string|\Swoole\Server\Task $taskId or $task
+ * @param string $srcWorkerId
+ * @param mixed $data
*/
public function onTask($server, $taskId, $srcWorkerId, $data)
{
- $this->container['events']->fire('swoole.task', func_get_args());
+ $this->container->make('events')->dispatch('swoole.task', func_get_args());
try {
// push websocket message
- if (is_array($data)) {
- if ($this->isWebsocket
- && array_key_exists('action', $data)
- && $data['action'] === Websocket::PUSH_ACTION) {
- $this->pushMessage($server, $data['data'] ?? []);
- }
+ if ($this->isWebsocketPushPayload($data)) {
+ $this->pushMessage($server, $data['data']);
// push async task to queue
- } elseif (is_string($data)) {
- $decoded = json_decode($data, true);
-
- if (JSON_ERROR_NONE === json_last_error() && isset($decoded['job'])) {
- (new SwooleTaskJob($this->container, $server, $data, $taskId, $srcWorkerId))->fire();
- }
+ } elseif ($this->isAsyncTaskPayload($data)) {
+ (new SwooleTaskJob($this->container, $server, $data, $taskId, $srcWorkerId))->fire();
}
} catch (Throwable $e) {
$this->logServerError($e);
@@ -251,10 +282,16 @@ public function onTask($server, $taskId, $srcWorkerId, $data)
/**
* Set onFinish listener.
+ *
+ * @param mixed $server
+ * @param string $taskId
+ * @param mixed $data
*/
public function onFinish($server, $taskId, $data)
{
// task worker callback
+ $this->container->make('events')->dispatch('swoole.finish', func_get_args());
+
return;
}
@@ -263,7 +300,7 @@ public function onFinish($server, $taskId, $data)
*/
public function onShutdown()
{
- $this->removePidFile();
+ $this->container->make(PidManager::class)->delete();
}
/**
@@ -274,7 +311,7 @@ protected function bindToLaravelApp()
$this->bindSandbox();
$this->bindSwooleTable();
- if ($this->isWebsocket) {
+ if ($this->isServerWebsocket) {
$this->bindRoom();
$this->bindWebsocket();
}
@@ -288,40 +325,8 @@ protected function bindSandbox()
$this->app->singleton(Sandbox::class, function ($app) {
return new Sandbox($app, $this->framework);
});
- $this->app->alias(Sandbox::class, 'swoole.sandbox');
- }
-
- /**
- * Gets pid file path.
- *
- * @return string
- */
- protected function getPidFile()
- {
- return $this->container['config']->get('swoole_http.server.options.pid_file');
- }
-
- /**
- * Create pid file.
- */
- protected function createPidFile()
- {
- $pidFile = $this->getPidFile();
- $pid = $this->container['swoole.server']->master_pid;
-
- file_put_contents($pidFile, $pid);
- }
- /**
- * Remove pid file.
- */
- protected function removePidFile()
- {
- $pidFile = $this->getPidFile();
-
- if (file_exists($pidFile)) {
- unlink($pidFile);
- }
+ $this->app->alias(Sandbox::class, 'swoole.sandbox');
}
/**
@@ -329,11 +334,11 @@ protected function removePidFile()
*/
protected function clearCache()
{
- if (function_exists('apc_clear_cache')) {
+ if (extension_loaded('apc')) {
apc_clear_cache();
}
- if (function_exists('opcache_reset')) {
+ if (extension_loaded('Zend OPcache')) {
opcache_reset();
}
}
@@ -342,16 +347,17 @@ protected function clearCache()
* Set process name.
*
* @codeCoverageIgnore
+ *
* @param $process
*/
protected function setProcessName($process)
{
// MacOS doesn't support modifying process name.
- if ($this->isMacOS() || $this->isInTesting()) {
+ if (OS::is(OS::MAC_OS, OS::CYGWIN) || $this->isInTesting()) {
return;
}
$serverName = 'swoole_http_server';
- $appName = $this->container['config']->get('app.name', 'Laravel');
+ $appName = $this->container->make('config')->get('app.name', 'Laravel');
$name = sprintf('%s: %s for %s', $serverName, $process, $appName);
@@ -359,13 +365,13 @@ protected function setProcessName($process)
}
/**
- * Indicates if the process is running in macOS.
+ * Add process to http server
*
- * @return bool
+ * @param \Swoole\Process $process
*/
- protected function isMacOS()
+ public function addProcess(Process $process): void
{
- return PHP_OS === 'Darwin';
+ $this->container->make(Server::class)->addProcess($process);
}
/**
@@ -381,10 +387,51 @@ protected function isInTesting()
/**
* Log server error.
*
- * @param Throwable
+ * @param \Throwable|\Exception $e
*/
public function logServerError(Throwable $e)
{
- $this->container[ExceptionHandler::class]->report($e);
+ if ($this->isInTesting()) {
+ return;
+ }
+
+ $exception = $this->normalizeException($e);
+ $this->container->make(ConsoleOutput::class)
+ ->writeln(sprintf("%s", $exception));
+
+ $this->container->make(ExceptionHandler::class)
+ ->report($exception);
+ }
+
+ /**
+ * Normalize a throwable/exception to exception.
+ *
+ * @param \Throwable|\Exception $e
+ */
+ protected function normalizeException(Throwable $e)
+ {
+ if (! $e instanceof Exception) {
+ $e = new FatalThrowableError($e);
+ }
+
+ return $e;
+ }
+
+ /**
+ * Indicates if the payload is async task.
+ *
+ * @param mixed $payload
+ *
+ * @return boolean
+ */
+ protected function isAsyncTaskPayload($payload): bool
+ {
+ $data = json_decode($payload, true);
+
+ if (JSON_ERROR_NONE !== json_last_error()) {
+ return false;
+ }
+
+ return isset($data['job']);
}
}
diff --git a/src/Server/PidManager.php b/src/Server/PidManager.php
new file mode 100644
index 00000000..1e17babd
--- /dev/null
+++ b/src/Server/PidManager.php
@@ -0,0 +1,88 @@
+setPidFile(
+ $pidFile ?: sys_get_temp_dir() . '/swoole.pid'
+ );
+ }
+
+ /**
+ * Set pid file path
+ */
+ public function setPidFile(string $pidFile): self
+ {
+ $this->pidFile = $pidFile;
+
+ return $this;
+ }
+
+ /**
+ * Write master pid and manager pid to pid file
+ *
+ * @throws \RuntimeException when $pidFile is not writable
+ */
+ public function write(int $masterPid, int $managerPid): void
+ {
+ if (! is_writable($this->pidFile)
+ && ! is_writable(dirname($this->pidFile))
+ ) {
+ throw new \RuntimeException(
+ sprintf('Pid file "%s" is not writable', $this->pidFile)
+ );
+ }
+
+ file_put_contents($this->pidFile, $masterPid . ',' . $managerPid);
+ }
+
+ /**
+ * Read master pid and manager pid from pid file
+ *
+ * @return string[] {
+ * @var string $masterPid
+ * @var string $managerPid
+ * }
+ */
+ public function read(): array
+ {
+ $pids = [];
+
+ if (is_readable($this->pidFile)) {
+ $content = file_get_contents($this->pidFile);
+ $pids = explode(',', $content);
+ }
+
+ return [
+ 'masterPid' => $pids[0] ?? null,
+ 'managerPid' => $pids[1] ?? null
+ ];
+ }
+
+ /**
+ * Gets pid file path.
+ *
+ * @return string
+ */
+ public function file()
+ {
+ return $this->pidFile;
+ }
+
+ /**
+ * Delete pid file
+ */
+ public function delete(): bool
+ {
+ if (is_writable($this->pidFile)) {
+ return unlink($this->pidFile);
+ }
+
+ return false;
+ }
+}
diff --git a/src/Server/Resetters/BindRequest.php b/src/Server/Resetters/BindRequest.php
index 35df56ce..b0787829 100644
--- a/src/Server/Resetters/BindRequest.php
+++ b/src/Server/Resetters/BindRequest.php
@@ -2,11 +2,13 @@
namespace SwooleTW\Http\Server\Resetters;
+use Illuminate\Contracts\Container\Container;
use Illuminate\Http\Request;
use SwooleTW\Http\Server\Sandbox;
-use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+/**
+ * Class BindRequest
+ */
class BindRequest implements ResetterContract
{
/**
@@ -14,6 +16,8 @@ class BindRequest implements ResetterContract
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
diff --git a/src/Server/Resetters/ClearInstances.php b/src/Server/Resetters/ClearInstances.php
index 24fc6963..865516f1 100644
--- a/src/Server/Resetters/ClearInstances.php
+++ b/src/Server/Resetters/ClearInstances.php
@@ -2,9 +2,8 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use SwooleTW\Http\Server\Sandbox;
class ClearInstances implements ResetterContract
{
@@ -13,6 +12,8 @@ class ClearInstances implements ResetterContract
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
diff --git a/src/Server/Resetters/RebindKernelContainer.php b/src/Server/Resetters/RebindKernelContainer.php
index f97a07ac..f2d8b9b4 100644
--- a/src/Server/Resetters/RebindKernelContainer.php
+++ b/src/Server/Resetters/RebindKernelContainer.php
@@ -2,18 +2,24 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
-use Illuminate\Contracts\Http\Kernel;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use Illuminate\Contracts\Http\Kernel;
+use SwooleTW\Http\Server\Sandbox;
class RebindKernelContainer implements ResetterContract
{
+ /**
+ * @var \Illuminate\Contracts\Container\Container
+ */
+ protected $app;
+
/**
* "handle" function for resetting app.
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
diff --git a/src/Server/Resetters/RebindRouterContainer.php b/src/Server/Resetters/RebindRouterContainer.php
index c51398ce..15274195 100644
--- a/src/Server/Resetters/RebindRouterContainer.php
+++ b/src/Server/Resetters/RebindRouterContainer.php
@@ -2,18 +2,29 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use SwooleTW\Http\Server\Sandbox;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class RebindRouterContainer implements ResetterContract
{
+ /**
+ * @var \Illuminate\Contracts\Container\Container
+ */
+ protected $container;
+
+ /**
+ * @var mixed
+ */
+ protected $routes;
+
/**
* "handle" function for resetting app.
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
@@ -26,6 +37,7 @@ public function handle(Container $app, Sandbox $sandbox)
return;
}
try {
+ /** @var mixed $route */
$route = $this->routes->match($request);
// clear resolved controller
if (property_exists($route, 'container')) {
diff --git a/src/Server/Resetters/RebindViewContainer.php b/src/Server/Resetters/RebindViewContainer.php
index 52bd5ab1..525f9daa 100644
--- a/src/Server/Resetters/RebindViewContainer.php
+++ b/src/Server/Resetters/RebindViewContainer.php
@@ -2,17 +2,28 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use SwooleTW\Http\Server\Sandbox;
class RebindViewContainer implements ResetterContract
{
+ /**
+ * @var Container
+ */
+ protected $container;
+
+ /**
+ * @var array
+ */
+ protected $shared;
+
/**
* "handle" function for resetting app.
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
diff --git a/src/Server/Resetters/ResetConfig.php b/src/Server/Resetters/ResetConfig.php
index 5fbfd042..beb44b7d 100644
--- a/src/Server/Resetters/ResetConfig.php
+++ b/src/Server/Resetters/ResetConfig.php
@@ -4,7 +4,6 @@
use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
class ResetConfig implements ResetterContract
{
@@ -13,6 +12,8 @@ class ResetConfig implements ResetterContract
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
diff --git a/src/Server/Resetters/ResetCookie.php b/src/Server/Resetters/ResetCookie.php
index c9b76da9..28d26662 100644
--- a/src/Server/Resetters/ResetCookie.php
+++ b/src/Server/Resetters/ResetCookie.php
@@ -2,9 +2,8 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use SwooleTW\Http\Server\Sandbox;
class ResetCookie implements ResetterContract
{
@@ -13,13 +12,15 @@ class ResetCookie implements ResetterContract
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
if (isset($app['cookie'])) {
$cookies = $app->make('cookie');
foreach ($cookies->getQueuedCookies() as $key => $value) {
- $cookies->unqueue($key);
+ $cookies->unqueue($value->getName());
}
}
diff --git a/src/Server/Resetters/ResetProviders.php b/src/Server/Resetters/ResetProviders.php
index 0b11648e..0193106b 100644
--- a/src/Server/Resetters/ResetProviders.php
+++ b/src/Server/Resetters/ResetProviders.php
@@ -2,17 +2,23 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use SwooleTW\Http\Server\Sandbox;
class ResetProviders implements ResetterContract
{
+ /**
+ * @var \Illuminate\Contracts\Container\Container
+ */
+ protected $app;
+
/**
* "handle" function for resetting app.
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
@@ -31,6 +37,9 @@ public function handle(Container $app, Sandbox $sandbox)
/**
* Rebind service provider's container.
+ *
+ * @param $app
+ * @param $provider
*/
protected function rebindProviderContainer($app, $provider)
{
diff --git a/src/Server/Resetters/ResetSession.php b/src/Server/Resetters/ResetSession.php
index 84db650c..9a97ac53 100644
--- a/src/Server/Resetters/ResetSession.php
+++ b/src/Server/Resetters/ResetSession.php
@@ -2,9 +2,8 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
-use SwooleTW\Http\Server\Resetters\ResetterContract;
+use SwooleTW\Http\Server\Sandbox;
class ResetSession implements ResetterContract
{
@@ -13,6 +12,8 @@ class ResetSession implements ResetterContract
*
* @param \Illuminate\Contracts\Container\Container $app
* @param \SwooleTW\Http\Server\Sandbox $sandbox
+ *
+ * @return \Illuminate\Contracts\Container\Container
*/
public function handle(Container $app, Sandbox $sandbox)
{
diff --git a/src/Server/Resetters/ResetterContract.php b/src/Server/Resetters/ResetterContract.php
index 5f63611f..27a5d85c 100644
--- a/src/Server/Resetters/ResetterContract.php
+++ b/src/Server/Resetters/ResetterContract.php
@@ -2,8 +2,8 @@
namespace SwooleTW\Http\Server\Resetters;
-use SwooleTW\Http\Server\Sandbox;
use Illuminate\Contracts\Container\Container;
+use SwooleTW\Http\Server\Sandbox;
interface ResetterContract
{
diff --git a/src/Server/Sandbox.php b/src/Server/Sandbox.php
index 0dbf367f..1fcad3bd 100644
--- a/src/Server/Sandbox.php
+++ b/src/Server/Sandbox.php
@@ -31,6 +31,11 @@ class Sandbox
/**
* Constructor
+ *
+ * @param null $app
+ * @param null $framework
+ *
+ * @throws \SwooleTW\Http\Exceptions\SandboxException
*/
public function __construct($app = null, $framework = null)
{
@@ -45,6 +50,10 @@ public function __construct($app = null, $framework = null)
/**
* Set framework type.
+ *
+ * @param string $framework
+ *
+ * @return \SwooleTW\Http\Server\Sandbox
*/
public function setFramework(string $framework)
{
@@ -65,6 +74,8 @@ public function getFramework()
* Set a base application.
*
* @param \Illuminate\Container\Container
+ *
+ * @return \SwooleTW\Http\Server\Sandbox
*/
public function setBaseApp(Container $app)
{
@@ -77,6 +88,8 @@ public function setBaseApp(Container $app)
* Set current request.
*
* @param \Illuminate\Http\Request
+ *
+ * @return \SwooleTW\Http\Server\Sandbox
*/
public function setRequest(Request $request)
{
@@ -89,6 +102,8 @@ public function setRequest(Request $request)
* Set current snapshot.
*
* @param \Illuminate\Container\Container
+ *
+ * @return \SwooleTW\Http\Server\Sandbox
*/
public function setSnapshot(Container $snapshot)
{
@@ -99,6 +114,8 @@ public function setSnapshot(Container $snapshot)
/**
* Initialize based on base app.
+ *
+ * @throws \SwooleTW\Http\Exceptions\SandboxException
*/
public function initialize()
{
@@ -145,7 +162,10 @@ public function getApplication()
* Run framework.
*
* @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Http\Response
+ * @throws \SwooleTW\Http\Exceptions\SandboxException
+ * @throws \ReflectionException
*/
public function run(Request $request)
{
@@ -166,7 +186,9 @@ public function run(Request $request)
* Handle request for non-ob case.
*
* @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Http\Response
+ * @throws \ReflectionException
*/
protected function prepareResponse(Request $request)
{
@@ -183,7 +205,9 @@ protected function prepareResponse(Request $request)
* Handle request for ob output.
*
* @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Http\Response
+ * @throws \ReflectionException
*/
protected function prepareObResponse(Request $request)
{
@@ -224,6 +248,7 @@ protected function prepareObResponse(Request $request)
* Handle request through Laravel or Lumen.
*
* @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Http\Response
*/
protected function handleRequest(Request $request)
@@ -254,29 +279,35 @@ public function isLaravel()
/**
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
+ *
+ * @throws \ReflectionException
*/
public function terminate(Request $request, $response)
{
if ($this->isLaravel()) {
$this->getKernel()->terminate($request, $response);
- } else {
- $app = $this->getApplication();
- $reflection = new \ReflectionObject($app);
- $middleware = $reflection->getProperty('middleware');
- $middleware->setAccessible(true);
+ return;
+ }
- $callTerminableMiddleware = $reflection->getMethod('callTerminableMiddleware');
- $callTerminableMiddleware->setAccessible(true);
+ $app = $this->getApplication();
+ $reflection = new \ReflectionObject($app);
- if (count($middleware->getValue($app)) > 0) {
- $callTerminableMiddleware->invoke($app, $response);
- }
+ $middleware = $reflection->getProperty('middleware');
+ $middleware->setAccessible(true);
+
+ $callTerminableMiddleware = $reflection->getMethod('callTerminableMiddleware');
+ $callTerminableMiddleware->setAccessible(true);
+
+ if (count($middleware->getValue($app)) > 0) {
+ $callTerminableMiddleware->invoke($app, $response);
}
}
/**
* Set laravel snapshot to container and facade.
+ *
+ * @throws \SwooleTW\Http\Exceptions\SandboxException
*/
public function enable()
{
@@ -293,12 +324,14 @@ public function enable()
*/
public function disable()
{
- Context::clear();
$this->setInstance($this->getBaseApp());
+ Context::clear();
}
/**
* Replace app's self bindings.
+ *
+ * @param \Illuminate\Container\Container $app
*/
public function setInstance(Container $app)
{
@@ -323,14 +356,6 @@ public function getSnapshot()
return Context::getApp();
}
- /**
- * Remove current request.
- */
- protected function removeRequest()
- {
- return Context::removeData('_request');
- }
-
/**
* Get current request.
*/
diff --git a/src/Server/helpers.php b/src/Server/helpers.php
index 7fb15785..f9c43ba8 100644
--- a/src/Server/helpers.php
+++ b/src/Server/helpers.php
@@ -4,9 +4,9 @@
* This is only for `function not exists` in config/swoole_http.php.
*/
if (! function_exists('swoole_cpu_num')) {
- function swoole_cpu_num()
+ function swoole_cpu_num(): int
{
- return;
+ return 1;
}
}
@@ -16,3 +16,7 @@ function swoole_cpu_num()
if (! defined('SWOOLE_SOCK_TCP')) {
define('SWOOLE_SOCK_TCP', 1);
}
+
+if (! defined('SWOOLE_PROCESS')) {
+ define('SWOOLE_PROCESS', 2);
+}
diff --git a/src/Table/Facades/SwooleTable.php b/src/Table/Facades/SwooleTable.php
index f5441690..67510a2a 100644
--- a/src/Table/Facades/SwooleTable.php
+++ b/src/Table/Facades/SwooleTable.php
@@ -4,6 +4,13 @@
use Illuminate\Support\Facades\Facade;
+/**
+ * @method static \SwooleTW\Http\Table\SwooleTable add($name, $table)
+ * @method static \Swoole\Table get($name)
+ * @method static array getAll()
+ *
+ * @see \SwooleTW\Http\Table\SwooleTable
+ */
class SwooleTable extends Facade
{
/**
diff --git a/src/Table/SwooleTable.php b/src/Table/SwooleTable.php
index 9dfe8947..3c154775 100644
--- a/src/Table/SwooleTable.php
+++ b/src/Table/SwooleTable.php
@@ -18,6 +18,7 @@ class SwooleTable
*
* @param string $name
* @param \Swoole\Table $table
+ *
* @return \SwooleTW\Http\Table\SwooleTable
*/
public function add(string $name, Table $table)
@@ -31,6 +32,7 @@ public function add(string $name, Table $table)
* Get a swoole table by its name from existing tables.
*
* @param string $name
+ *
* @return \Swoole\Table $table
*/
public function get(string $name)
@@ -51,7 +53,8 @@ public function getAll()
/**
* Dynamically access table.
*
- * @param string $key
+ * @param string $key
+ *
* @return table
*/
public function __get($key)
diff --git a/src/Task/Connectors/SwooleTaskConnector.php b/src/Task/Connectors/SwooleTaskConnector.php
index 3b690aac..6e3c0664 100644
--- a/src/Task/Connectors/SwooleTaskConnector.php
+++ b/src/Task/Connectors/SwooleTaskConnector.php
@@ -2,9 +2,13 @@
namespace SwooleTW\Http\Task\Connectors;
-use SwooleTW\Http\Task\SwooleTaskQueue;
use Illuminate\Queue\Connectors\ConnectorInterface;
+use SwooleTW\Http\Helpers\FW;
+use SwooleTW\Http\Task\QueueFactory;
+/**
+ * Class SwooleTaskConnector
+ */
class SwooleTaskConnector implements ConnectorInterface
{
/**
@@ -14,10 +18,11 @@ class SwooleTaskConnector implements ConnectorInterface
*/
protected $swoole;
- /**
+ /**
* Create a new Swoole Async task connector instance.
*
* @param \Swoole\Http\Server $swoole
+ *
* @return void
*/
public function __construct($swoole)
@@ -28,11 +33,12 @@ public function __construct($swoole)
/**
* Establish a queue connection.
*
- * @param array $config
+ * @param array $config
+ *
* @return \Illuminate\Contracts\Queue\Queue
*/
public function connect(array $config)
{
- return new SwooleTaskQueue($this->swoole);
+ return QueueFactory::make($this->swoole, FW::version());
}
}
diff --git a/src/Task/QueueFactory.php b/src/Task/QueueFactory.php
new file mode 100644
index 00000000..99ddc73d
--- /dev/null
+++ b/src/Task/QueueFactory.php
@@ -0,0 +1,107 @@
+getDocComment(), $result)) {
+ $fileVersion = Arr::first($result);
+ }
+ }
+
+ return version_compare($fileVersion, $version, '>=');
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param string $version
+ *
+ * @return bool
+ */
+ protected static function hasBreakingChanges(string $version): bool
+ {
+ return version_compare($version, self::CHANGE_VERSION, '>=');
+ }
+}
diff --git a/src/Task/SwooleTaskJob.php b/src/Task/SwooleTaskJob.php
index 5a70b7a9..5aa0fc64 100644
--- a/src/Task/SwooleTaskJob.php
+++ b/src/Task/SwooleTaskJob.php
@@ -6,6 +6,9 @@
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Queue\Job as JobContract;
+/**
+ * Class SwooleTaskJob
+ */
class SwooleTaskJob extends Job implements JobContract
{
/**
@@ -36,15 +39,19 @@ class SwooleTaskJob extends Job implements JobContract
*/
protected $srcWrokerId;
+ /**
+ * @var int
+ */
+ protected $srcWorkderId;
+
/**
* Create a new job instance.
*
- * @param \Illuminate\Container\Container $container
- * @param \Swoole\Http\Server $swoole
- * @param string $job
- * @param int $taskId
- * @param int $srcWorkerId
- * @return void
+ * @param \Illuminate\Contracts\Container\Container $container
+ * @param \Swoole\Http\Server $swoole
+ * @param string $job
+ * @param int $taskId
+ * @param $srcWrokerId
*/
public function __construct(Container $container, $swoole, $job, $taskId, $srcWrokerId)
{
@@ -71,6 +78,7 @@ public function fire()
/**
* Get the number of times the job has been attempted.
+ *
* @return int
*/
public function attempts()
@@ -80,6 +88,7 @@ public function attempts()
/**
* Get the raw body string for the job.
+ *
* @return string
*/
public function getRawBody()
@@ -87,9 +96,9 @@ public function getRawBody()
return $this->job;
}
-
/**
* Get the job identifier.
+ *
* @return string
*/
public function getJobId()
@@ -100,7 +109,7 @@ public function getJobId()
/**
* Get the service container instance.
*
- * @return \Illuminate\Container\Container
+ * @return \Illuminate\Contracts\Container\Container
*/
public function getContainer()
{
diff --git a/src/Task/SwooleTaskQueue.php b/src/Task/SwooleTaskQueue.php
index 734fbc4a..5eb0e73d 100644
--- a/src/Task/SwooleTaskQueue.php
+++ b/src/Task/SwooleTaskQueue.php
@@ -3,10 +3,13 @@
namespace SwooleTW\Http\Task;
use Exception;
-use Swoole\Timer;
-use Illuminate\Queue\Queue;
use Illuminate\Contracts\Queue\Queue as QueueContract;
+use Illuminate\Queue\Queue;
+use Swoole\Timer;
+/**
+ * Class SwooleTaskQueue (5.7)
+ */
class SwooleTaskQueue extends Queue implements QueueContract
{
/**
@@ -19,7 +22,7 @@ class SwooleTaskQueue extends Queue implements QueueContract
/**
* Create Async Task instance.
*
- * @param \Swoole\Http\Server $swoole
+ * @param \Swoole\Http\Server $swoole
*/
public function __construct($swoole)
{
@@ -29,9 +32,10 @@ public function __construct($swoole)
/**
* Push a new job onto the queue.
*
- * @param string|object $job
- * @param mixed $data
- * @param string $queue
+ * @param string|object $job
+ * @param mixed $data
+ * @param string $queue
+ *
* @return mixed
*/
public function push($job, $data = '', $queue = null)
@@ -42,23 +46,25 @@ public function push($job, $data = '', $queue = null)
/**
* Push a raw payload onto the queue.
*
- * @param string $payload
- * @param string $queue
- * @param array $options
+ * @param string $payload
+ * @param string $queue
+ * @param array $options
+ *
* @return mixed
*/
public function pushRaw($payload, $queue = null, array $options = [])
{
- return $this->swoole->task($payload, ! is_numeric($queue) ? 1 : (int) $queue);
+ return $this->swoole->task($payload, ! is_numeric($queue) ? -1 : (int)$queue);
}
/**
* Push a new job onto the queue after a delay.
*
- * @param \DateTimeInterface|\DateInterval|int $delay
- * @param string|object $job
- * @param mixed $data
- * @param string $queue
+ * @param \DateTimeInterface|\DateInterval|int $delay
+ * @param string|object $job
+ * @param mixed $data
+ * @param string $queue
+ *
* @return mixed
*/
public function later($delay, $job, $data = '', $queue = null)
@@ -71,12 +77,13 @@ public function later($delay, $job, $data = '', $queue = null)
/**
* Create a typical, string based queue payload array.
*
- * @param string $job
- * @param mixed $data
+ * @param string $job
+ * @param string $queue
+ * @param mixed $data
*
- * @throws Expcetion
+ * @throws \Exception
*/
- protected function createStringPayload($job, $data)
+ protected function createStringPayload($job, $queue, $data)
{
throw new Exception('Unsupported empty data');
}
@@ -84,7 +91,8 @@ protected function createStringPayload($job, $data)
/**
* Get the size of the queue.
*
- * @param string $queue
+ * @param string $queue
+ *
* @return int
*/
public function size($queue = null)
@@ -95,7 +103,8 @@ public function size($queue = null)
/**
* Pop the next job off of the queue.
*
- * @param string $queue
+ * @param string $queue
+ *
* @return \Illuminate\Contracts\Queue\Job|null
*/
public function pop($queue = null)
diff --git a/src/Transformers/Request.php b/src/Transformers/Request.php
index 704ab911..1dbd41db 100644
--- a/src/Transformers/Request.php
+++ b/src/Transformers/Request.php
@@ -3,14 +3,29 @@
namespace SwooleTW\Http\Transformers;
use Illuminate\Http\Request as IlluminateRequest;
+use Illuminate\Http\Response as IlluminateResponse;
use Swoole\Http\Request as SwooleRequest;
-use Swoole\Http\Response as SwooleResponse;
+use SwooleTW\Http\Helpers\FW;
+use SwooleTW\Http\Helpers\MimeType;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
+/**
+ * Class Request
+ */
class Request
{
- const BLACK_LIST = ['php', 'htaccess', 'config'];
+ /**
+ * Blacklisted extensions
+ *
+ * @const array
+ */
+ protected const EXTENSION_BLACKLIST = ['php', 'htaccess', 'config'];
+
+ /**
+ * Extension mime types
+ */
+ protected const EXTENSION_MIMES = ['js' => 'text/javascript', 'css' => 'text/css'];
/**
* @var \Illuminate\Http\Request
@@ -21,30 +36,22 @@ class Request
* Make a request.
*
* @param \Swoole\Http\Request $swooleRequest
- * @return \SwooleTW\Http\Server\Request
+ *
+ * @return \SwooleTW\Http\Transformers\Request
*/
public static function make(SwooleRequest $swooleRequest)
{
- list($get, $post, $cookie, $files, $server, $content)
- = static::toIlluminateParameters($swooleRequest);
-
- return new static($get, $post, $cookie, $files, $server, $content);
+ return new static(...static::toIlluminateParameters($swooleRequest));
}
/**
* Request constructor.
*
- * @param array $get
- * @param array $post
- * @param array $cookie
- * @param array $files
- * @param array $server
- * @param string $content
- * @throws \LogicException
+ * @param array $params provides GET, POST, COOKIE, FILES, SERVER, CONTENT
*/
- public function __construct(array $get, array $post, array $cookie, array $files, array $server, $content = null)
+ public function __construct(...$params)
{
- $this->createIlluminateRequest($get, $post, $cookie, $files, $server, $content);
+ $this->createIlluminateRequest(...$params);
}
/**
@@ -56,8 +63,8 @@ public function __construct(array $get, array $post, array $cookie, array $files
* @param array $files
* @param array $server
* @param string $content
- * @return \Illuminate\Http\Request
- * @throws \LogicException
+ *
+ * @return void
*/
protected function createIlluminateRequest($get, $post, $cookie, $files, $server, $content = null)
{
@@ -86,7 +93,7 @@ protected function createIlluminateRequest($get, $post, $cookie, $files, $server
$request = new SymfonyRequest($get, $post, [], $cookie, $files, $server, $content);
if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
- && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
+ && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
) {
parse_str($request->getContent(), $data);
$request->request = new ParameterBag($data);
@@ -115,16 +122,17 @@ public function getIlluminateRequest()
* Transforms request parameters.
*
* @param \Swoole\Http\Request $request
+ *
* @return array
*/
protected static function toIlluminateParameters(SwooleRequest $request)
{
- $get = isset($request->get) ? $request->get : [];
- $post = isset($request->post) ? $request->post : [];
- $cookie = isset($request->cookie) ? $request->cookie : [];
- $files = isset($request->files) ? $request->files : [];
- $header = isset($request->header) ? $request->header : [];
- $server = isset($request->server) ? $request->server : [];
+ $get = $request->get ?? [];
+ $post = $request->post ?? [];
+ $cookie = $request->cookie ?? [];
+ $files = $request->files ?? [];
+ $header = $request->header ?? [];
+ $server = $request->server ?? [];
$server = static::transformServerParameters($server, $header);
$content = $request->rawContent();
@@ -136,6 +144,7 @@ protected static function toIlluminateParameters(SwooleRequest $request)
*
* @param array $server
* @param array $header
+ *
* @return array
*/
protected static function transformServerParameters(array $server, array $header)
@@ -166,31 +175,27 @@ protected static function transformServerParameters(array $server, array $header
*
* @param \Swoole\Http\Request $swooleRequest
* @param \Swoole\Http\Response $swooleResponse
- * @param string $path
+ * @param string $publicPath
+ *
* @return boolean
*/
public static function handleStatic($swooleRequest, $swooleResponse, string $publicPath)
{
$uri = $swooleRequest->server['request_uri'] ?? '';
- $extension = substr(strrchr($uri, '.'), 1);
- if ($extension && in_array($extension, static::BLACK_LIST)) {
- return;
- }
+ $extension = strtok(pathinfo($uri, PATHINFO_EXTENSION), '?');
+ $fileName = $publicPath . $uri;
- $filename = $publicPath . $uri;
- if (! is_file($filename) || filesize($filename) === 0) {
- return;
+ if ($extension && in_array($extension, static::EXTENSION_BLACKLIST)) {
+ return false;
}
- $swooleResponse->status(200);
- $mime = mime_content_type($filename);
- if ($extension === 'js') {
- $mime = 'text/javascript';
- } elseif ($extension === 'css') {
- $mime = 'text/css';
+ if (! is_file($fileName) || ! filesize($fileName)) {
+ return false;
}
- $swooleResponse->header('Content-Type', $mime);
- $swooleResponse->sendfile($filename);
+
+ $swooleResponse->status(IlluminateResponse::HTTP_OK);
+ $swooleResponse->header('Content-Type', MimeType::get($extension));
+ $swooleResponse->sendfile($fileName);
return true;
}
diff --git a/src/Transformers/Response.php b/src/Transformers/Response.php
index 5efd0239..416a26fa 100644
--- a/src/Transformers/Response.php
+++ b/src/Transformers/Response.php
@@ -10,6 +10,8 @@
class Response
{
+ const CHUNK_SIZE = 8192;
+
/**
* @var \Swoole\Http\Response
*/
@@ -25,7 +27,8 @@ class Response
*
* @param $illuminateResponse
* @param \Swoole\Http\Response $swooleResponse
- * @return \SwooleTW\Http\Server\Response
+ *
+ * @return \SwooleTW\Http\Transformers\Response
*/
public static function make($illuminateResponse, SwooleResponse $swooleResponse)
{
@@ -45,7 +48,7 @@ public function __construct($illuminateResponse, SwooleResponse $swooleResponse)
}
/**
- * Sends HTTP headers and content.
+ * Send HTTP headers and content.
*
* @throws \InvalidArgumentException
*/
@@ -56,7 +59,7 @@ public function send()
}
/**
- * Sends HTTP headers.
+ * Send HTTP headers.
*
* @throws \InvalidArgumentException
*/
@@ -85,38 +88,62 @@ protected function sendHeaders()
$this->swooleResponse->status($illuminateResponse->getStatusCode());
// cookies
+ // $cookie->isRaw() is supported after symfony/http-foundation 3.1
+ // and Laravel 5.3, so we can add it back now
foreach ($illuminateResponse->headers->getCookies() as $cookie) {
- // may need to consider rawcookie
- $this->swooleResponse->cookie(
- $cookie->getName(), $cookie->getValue(),
- $cookie->getExpiresTime(), $cookie->getPath(),
- $cookie->getDomain(), $cookie->isSecure(),
+ $method = $cookie->isRaw() ? 'rawcookie' : 'cookie';
+ $this->swooleResponse->$method(
+ $cookie->getName(),
+ $cookie->getValue(),
+ $cookie->getExpiresTime(),
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $cookie->isSecure(),
$cookie->isHttpOnly()
);
}
}
/**
- * Sends HTTP content.
+ * Send HTTP content.
*/
protected function sendContent()
{
$illuminateResponse = $this->getIlluminateResponse();
- if ($illuminateResponse instanceof StreamedResponse &&
- property_exists($illuminateResponse, 'output')
- ) {
+ if ($illuminateResponse instanceof StreamedResponse && property_exists($illuminateResponse, 'output')) {
+ // TODO Add Streamed Response with output
$this->swooleResponse->end($illuminateResponse->output);
} elseif ($illuminateResponse instanceof BinaryFileResponse) {
$this->swooleResponse->sendfile($illuminateResponse->getFile()->getPathname());
} else {
- $this->swooleResponse->end($illuminateResponse->getContent());
+ $this->sendInChunk($illuminateResponse->getContent());
+ }
+ }
+
+ /**
+ * Send content in chunk
+ *
+ * @param string $content
+ */
+ protected function sendInChunk($content)
+ {
+ if (strlen($content) <= static::CHUNK_SIZE) {
+ $this->swooleResponse->end($content);
+ return;
+ }
+
+ foreach (str_split($content, static::CHUNK_SIZE) as $chunk) {
+ $this->swooleResponse->write($chunk);
}
+
+ $this->swooleResponse->end();
}
/**
* @param \Swoole\Http\Response $swooleResponse
- * @return \SwooleTW\Http\Server\Response
+ *
+ * @return \SwooleTW\Http\Transformers\Response
*/
protected function setSwooleResponse(SwooleResponse $swooleResponse)
{
@@ -135,7 +162,8 @@ public function getSwooleResponse()
/**
* @param mixed illuminateResponse
- * @return \SwooleTW\Http\Server\Response
+ *
+ * @return \SwooleTW\Http\Transformers\Response
*/
protected function setIlluminateResponse($illuminateResponse)
{
diff --git a/src/Transformers/StreamedResponse.php b/src/Transformers/StreamedResponse.php
new file mode 100644
index 00000000..31a5e59b
--- /dev/null
+++ b/src/Transformers/StreamedResponse.php
@@ -0,0 +1,38 @@
+output;
+ }
+
+ /**
+ * Set output buffer
+ *
+ * @param string $output
+ */
+ public function setOutput(?string $output = null): void
+ {
+ $this->output = $output;
+ }
+}
\ No newline at end of file
diff --git a/src/Websocket/Authenticatable.php b/src/Websocket/Authenticatable.php
index ae9d1c3d..92276511 100644
--- a/src/Websocket/Authenticatable.php
+++ b/src/Websocket/Authenticatable.php
@@ -2,15 +2,24 @@
namespace SwooleTW\Http\Websocket;
-use InvalidArgumentException;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
+use InvalidArgumentException;
+/**
+ * Trait Authenticatable
+ *
+ * @property-read \SwooleTW\Http\Websocket\Rooms\RoomContract $room
+ */
trait Authenticatable
{
protected $userId;
/**
* Login using current user.
+ *
+ * @param \Illuminate\Contracts\Auth\Authenticatable $user
+ *
+ * @return mixed
*/
public function loginUsing(AuthenticatableContract $user)
{
@@ -19,6 +28,10 @@ public function loginUsing(AuthenticatableContract $user)
/**
* Login using current userId.
+ *
+ * @param $userId
+ *
+ * @return mixed
*/
public function loginUsingId($userId)
{
@@ -27,11 +40,13 @@ public function loginUsingId($userId)
/**
* Logout with current sender's fd.
+ *
+ * @return mixed
*/
public function logout()
{
if (is_null($userId = $this->getUserId())) {
- return;
+ return null;
}
return $this->leave(static::USER_PREFIX . $userId);
@@ -39,13 +54,18 @@ public function logout()
/**
* Set multiple recepients' fds by users.
+ *
+ * @param $users
+ *
+ * @return \SwooleTW\Http\Websocket\Authenticatable
*/
public function toUser($users)
{
$users = is_object($users) ? func_get_args() : $users;
- $userIds = array_map(function ($user) {
+ $userIds = array_map(function (AuthenticatableContract $user) {
$this->checkUser($user);
+
return $user->getAuthIdentifier();
}, $users);
@@ -54,6 +74,10 @@ public function toUser($users)
/**
* Set multiple recepients' fds by userIds.
+ *
+ * @param $userIds
+ *
+ * @return \SwooleTW\Http\Websocket\Authenticatable
*/
public function toUserId($userIds)
{
@@ -89,6 +113,10 @@ public function getUserId()
/**
* Check if a user is online by given userId.
+ *
+ * @param $userId
+ *
+ * @return bool
*/
public function isUserIdOnline($userId)
{
@@ -97,6 +125,8 @@ public function isUserIdOnline($userId)
/**
* Check if user object implements AuthenticatableContract.
+ *
+ * @param $user
*/
protected function checkUser($user)
{
diff --git a/src/Websocket/Facades/Room.php b/src/Websocket/Facades/Room.php
index e0a26fd4..5050e4f6 100644
--- a/src/Websocket/Facades/Room.php
+++ b/src/Websocket/Facades/Room.php
@@ -4,6 +4,15 @@
use Illuminate\Support\Facades\Facade;
+/**
+ * @method static $this prepare()
+ * @method static $this add($fd, $rooms)
+ * @method static $this delete($fd, $rooms)
+ * @method static array getClients($room)
+ * @method static array getRooms($fd)
+ *
+ * @see \SwooleTW\Http\Websocket\Rooms\RoomContract
+ */
class Room extends Facade
{
/**
@@ -15,4 +24,4 @@ protected static function getFacadeAccessor()
{
return 'swoole.room';
}
-}
\ No newline at end of file
+}
diff --git a/src/Websocket/Facades/Websocket.php b/src/Websocket/Facades/Websocket.php
index 8c24866b..6fedbcb4 100644
--- a/src/Websocket/Facades/Websocket.php
+++ b/src/Websocket/Facades/Websocket.php
@@ -4,6 +4,36 @@
use Illuminate\Support\Facades\Facade;
+/**
+ * @method static $this broadcast()
+ * @method static $this to($values)
+ * @method static $this join($rooms)
+ * @method static $this leave($rooms)
+ * @method static boolean emit($event, $data)
+ * @method static $this in($room)
+ * @method static $this on($event, $callback)
+ * @method static boolean eventExists($event)
+ * @method static mixed call($event, $data)
+ * @method static boolean close($fd)
+ * @method static $this setSender($fd)
+ * @method static int getSender()
+ * @method static boolean getIsBroadcast()
+ * @method static array getTo()
+ * @method static $this reset()
+ * @method static $this middleware($middleware)
+ * @method static $this setContainer($container)
+ * @method static $this setPipeline($pipeline)
+ * @method static \Illuminate\Contracts\Pipeline\Pipeline getPipeline()
+ * @method static mixed loginUsing($user)
+ * @method static $this loginUsingId($userId)
+ * @method static $this logout()
+ * @method static $this toUser($users)
+ * @method static $this toUserId($userIds)
+ * @method static string getUserId()
+ * @method static boolean isUserIdOnline($userId)
+ *
+ * @see \SwooleTW\Http\Websocket\Websocket
+ */
class Websocket extends Facade
{
/**
@@ -15,4 +45,4 @@ protected static function getFacadeAccessor()
{
return 'swoole.websocket';
}
-}
\ No newline at end of file
+}
diff --git a/src/Websocket/HandShakeHandler.php b/src/Websocket/HandShakeHandler.php
new file mode 100644
index 00000000..291fe17a
--- /dev/null
+++ b/src/Websocket/HandShakeHandler.php
@@ -0,0 +1,48 @@
+header['sec-websocket-key'];
+
+ if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $socketkey) || 16 !== strlen(base64_decode($socketkey))) {
+ $response->end();
+
+ return false;
+ }
+
+ $headers = [
+ 'Upgrade' => 'websocket',
+ 'Connection' => 'Upgrade',
+ 'Sec-WebSocket-Accept' => base64_encode(sha1($socketkey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)),
+ 'Sec-WebSocket-Version' => '13',
+ ];
+
+ if (isset($request->header['sec-websocket-protocol'])) {
+ $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
+ }
+
+ foreach ($headers as $header => $val) {
+ $response->header($header, $val);
+ }
+
+ $response->status(101);
+ $response->end();
+
+ return true;
+ }
+}
diff --git a/src/Websocket/Middleware/Authenticate.php b/src/Websocket/Middleware/Authenticate.php
index 27315d7b..caf1c675 100644
--- a/src/Websocket/Middleware/Authenticate.php
+++ b/src/Websocket/Middleware/Authenticate.php
@@ -6,19 +6,23 @@
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
+/**
+ * Class Authenticate
+ */
class Authenticate
{
/**
* The authentication factory instance.
*
- * @var \Illuminate\Contracts\Auth\Factory
+ * @var \Illuminate\Contracts\Auth\Factory|mixed
*/
protected $auth;
/**
* Create a new middleware instance.
*
- * @param \Illuminate\Contracts\Auth\Factory $auth
+ * @param \Illuminate\Contracts\Auth\Factory $auth
+ *
* @return void
*/
public function __construct(Auth $auth)
@@ -29,15 +33,16 @@ public function __construct(Auth $auth)
/**
* Handle an incoming request.
*
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
* @return mixed
*
- * @throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next)
{
try {
+ $this->auth->setRequest($request);
if ($user = $this->auth->authenticate()) {
$request->setUserResolver(function () use ($user) {
return $user;
diff --git a/src/Websocket/Middleware/DecryptCookies.php b/src/Websocket/Middleware/DecryptCookies.php
index 6a76c65e..2807ea1b 100644
--- a/src/Websocket/Middleware/DecryptCookies.php
+++ b/src/Websocket/Middleware/DecryptCookies.php
@@ -3,11 +3,13 @@
namespace SwooleTW\Http\Websocket\Middleware;
use Closure;
-use Symfony\Component\HttpFoundation\Cookie;
-use Symfony\Component\HttpFoundation\Request;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
+use Symfony\Component\HttpFoundation\Request;
+/**
+ * Class DecryptCookies
+ */
class DecryptCookies
{
/**
@@ -35,7 +37,8 @@ class DecryptCookies
/**
* Create a new CookieGuard instance.
*
- * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
+ * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
+ *
* @return void
*/
public function __construct(EncrypterContract $encrypter)
@@ -46,7 +49,8 @@ public function __construct(EncrypterContract $encrypter)
/**
* Disable encryption for the given cookie name(s).
*
- * @param string|array $name
+ * @param string|array $name
+ *
* @return void
*/
public function disableFor($name)
@@ -57,8 +61,9 @@ public function disableFor($name)
/**
* Handle an incoming request.
*
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle($request, Closure $next)
@@ -69,7 +74,8 @@ public function handle($request, Closure $next)
/**
* Decrypt the cookies on the request.
*
- * @param \Symfony\Component\HttpFoundation\Request $request
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ *
* @return \Symfony\Component\HttpFoundation\Request
*/
protected function decrypt(Request $request)
@@ -92,21 +98,23 @@ protected function decrypt(Request $request)
/**
* Decrypt the given cookie and return the value.
*
- * @param string $name
- * @param string|array $cookie
+ * @param string $name
+ * @param string|array $cookie
+ *
* @return string|array
*/
protected function decryptCookie($name, $cookie)
{
return is_array($cookie)
- ? $this->decryptArray($cookie)
- : $this->encrypter->decrypt($cookie, static::serialized($name));
+ ? $this->decryptArray($cookie)
+ : $this->encrypter->decrypt($cookie, static::serialized($name));
}
/**
* Decrypt an array based cookie.
*
- * @param array $cookie
+ * @param array $cookie
+ *
* @return array
*/
protected function decryptArray(array $cookie)
@@ -114,7 +122,7 @@ protected function decryptArray(array $cookie)
$decrypted = [];
foreach ($cookie as $key => $value) {
- if (is_string($value)) {
+ if (\is_string($value)) {
$decrypted[$key] = $this->encrypter->decrypt($value, static::serialized($key));
}
}
@@ -126,20 +134,22 @@ protected function decryptArray(array $cookie)
* Determine whether encryption has been disabled for the given cookie.
*
* @param string $name
+ *
* @return bool
*/
- public function isDisabled($name)
+ public function isDisabled(string $name)
{
- return in_array($name, $this->except);
+ return \in_array($name, $this->except);
}
/**
* Determine if the cookie contents should be serialized.
*
- * @param string $name
+ * @param string $name
+ *
* @return bool
*/
- public static function serialized($name)
+ public static function serialized(string $name)
{
return static::$serialize;
}
diff --git a/src/Websocket/Middleware/StartSession.php b/src/Websocket/Middleware/StartSession.php
index bb5de3eb..6be32c6d 100644
--- a/src/Websocket/Middleware/StartSession.php
+++ b/src/Websocket/Middleware/StartSession.php
@@ -3,10 +3,14 @@
namespace SwooleTW\Http\Websocket\Middleware;
use Closure;
+use Illuminate\Contracts\Session\Session;
use Illuminate\Http\Request;
use Illuminate\Session\SessionManager;
-use Illuminate\Contracts\Session\Session;
+use Illuminate\Support\Arr;
+/**
+ * Class StartSession
+ */
class StartSession
{
/**
@@ -19,7 +23,8 @@ class StartSession
/**
* Create a new session middleware.
*
- * @param \Illuminate\Session\SessionManager $manager
+ * @param \Illuminate\Session\SessionManager $manager
+ *
* @return void
*/
public function __construct(SessionManager $manager)
@@ -30,16 +35,15 @@ public function __construct(SessionManager $manager)
/**
* Handle an incoming request.
*
- * @param \Illuminate\Http\Request $request
- * @param \Closure $next
+ * @param \Illuminate\Http\Request $request
+ * @param \Closure $next
+ *
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($this->sessionConfigured()) {
- $request->setLaravelSession(
- $session = $this->startSession($request)
- );
+ $request->setLaravelSession($this->startSession($request));
}
return $next($request);
@@ -48,12 +52,13 @@ public function handle($request, Closure $next)
/**
* Start the session for the given request.
*
- * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Contracts\Session\Session
*/
protected function startSession(Request $request)
{
- return tap($this->getSession($request), function ($session) use ($request) {
+ return tap($this->getSession($request), function (Session $session) use ($request) {
$session->setRequestOnHandler($request);
$session->start();
@@ -63,12 +68,13 @@ protected function startSession(Request $request)
/**
* Get the session implementation from the manager.
*
- * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Contracts\Session\Session
*/
public function getSession(Request $request)
{
- return tap($this->manager->driver(), function ($session) use ($request) {
+ return tap($this->manager->driver(), function (Session $session) use ($request) {
$session->setId($request->cookies->get($session->getName()));
});
}
diff --git a/src/Websocket/Parser.php b/src/Websocket/Parser.php
index c8a22543..b61c93c6 100644
--- a/src/Websocket/Parser.php
+++ b/src/Websocket/Parser.php
@@ -15,6 +15,9 @@ abstract class Parser
* Execute strategies before decoding payload.
* If return value is true will skip decoding.
*
+ * @param \Swoole\WebSocket\Server $server
+ * @param \Swoole\WebSocket\Frame $frame
+ *
* @return boolean
*/
public function execute($server, $frame)
@@ -22,10 +25,13 @@ public function execute($server, $frame)
$skip = false;
foreach ($this->strategies as $strategy) {
- $result = App::call($strategy . '@handle', [
- 'server' => $server,
- 'frame' => $frame
- ]);
+ $result = App::call(
+ $strategy . '@handle',
+ [
+ 'server' => $server,
+ 'frame' => $frame,
+ ]
+ );
if ($result === true) {
$skip = true;
break;
@@ -40,6 +46,7 @@ public function execute($server, $frame)
*
* @param string $event
* @param mixed $data
+ *
* @return mixed
*/
abstract public function encode(string $event, $data);
@@ -49,6 +56,7 @@ abstract public function encode(string $event, $data);
* Define and return event name and payload data here.
*
* @param \Swoole\Websocket\Frame $frame
+ *
* @return array
*/
abstract public function decode($frame);
diff --git a/src/Websocket/Pusher.php b/src/Websocket/Pusher.php
new file mode 100644
index 00000000..32b8e91f
--- /dev/null
+++ b/src/Websocket/Pusher.php
@@ -0,0 +1,269 @@
+opcode = $opcode;
+ $this->sender = $sender;
+ $this->descriptors = $descriptors;
+ $this->broadcast = $broadcast;
+ $this->assigned = $assigned;
+ $this->event = $event;
+ $this->message = $message;
+ $this->server = $server;
+ }
+
+ /**
+ * Static constructor
+ *
+ * @param array $data
+ * @param \Swoole\Websocket\Server $server
+ *
+ * @return \SwooleTW\Http\Websocket\Pusher
+ */
+ public static function make(array $data, $server)
+ {
+ return new static(
+ $data['opcode'] ?? 1,
+ $data['sender'] ?? 0,
+ $data['fds'] ?? [],
+ $data['broadcast'] ?? false,
+ $data['assigned'] ?? false,
+ $data['event'] ?? null,
+ $data['message'] ?? null,
+ $server
+ );
+ }
+
+ /**
+ * @return int
+ */
+ public function getOpcode(): int
+ {
+ return $this->opcode;
+ }
+
+ /**
+ * @return int
+ */
+ public function getSender(): int
+ {
+ return $this->sender;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDescriptors(): array
+ {
+ return $this->descriptors;
+ }
+
+ /**
+ * @param int $descriptor
+ *
+ * @return self
+ */
+ public function addDescriptor($descriptor): self
+ {
+ return $this->addDescriptors([$descriptor]);
+ }
+
+ /**
+ * @param array $descriptors
+ *
+ * @return self
+ */
+ public function addDescriptors(array $descriptors): self
+ {
+ $this->descriptors = array_values(
+ array_unique(
+ array_merge($this->descriptors, $descriptors)
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @param int $descriptor
+ *
+ * @return bool
+ */
+ public function hasDescriptor(int $descriptor): bool
+ {
+ return in_array($descriptor, $this->descriptors);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isBroadcast(): bool
+ {
+ return $this->broadcast;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isAssigned(): bool
+ {
+ return $this->assigned;
+ }
+
+ /**
+ * @return string
+ */
+ public function getEvent(): string
+ {
+ return $this->event;
+ }
+
+ /**
+ * @return mixed|null
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * @return \Swoole\Websocket\Server
+ */
+ public function getServer()
+ {
+ return $this->server;
+ }
+
+ /**
+ * @return bool
+ */
+ public function shouldBroadcast(): bool
+ {
+ return $this->broadcast && empty($this->descriptors) && ! $this->assigned;
+ }
+
+ /**
+ * Returns all descriptors that are websocket
+ *
+ * @param \Swoole\Connection\Iterator $descriptors
+ *
+ * @return array
+ */
+ protected function getWebsocketConnections(): array
+ {
+ return array_filter(iterator_to_array($this->server->connections), function ($fd) {
+ return $this->server->isEstablished($fd);
+ });
+ }
+
+ /**
+ * @param int $fd
+ *
+ * @return bool
+ */
+ public function shouldPushToDescriptor(int $fd): bool
+ {
+ if (! $this->server->isEstablished($fd)) {
+ return false;
+ }
+
+ return $this->broadcast ? $this->sender !== (int) $fd : true;
+ }
+
+ /**
+ * Push message to related descriptors
+ *
+ * @param mixed $payload
+ *
+ * @return void
+ */
+ public function push($payload): void
+ {
+ // attach sender if not broadcast
+ if (! $this->broadcast && $this->sender && ! $this->hasDescriptor($this->sender)) {
+ $this->addDescriptor($this->sender);
+ }
+
+ // check if to broadcast to other clients
+ if ($this->shouldBroadcast()) {
+ $this->addDescriptors($this->getWebsocketConnections());
+ }
+
+ // push message to designated fds
+ foreach ($this->descriptors as $descriptor) {
+ if ($this->shouldPushToDescriptor($descriptor)) {
+ $this->server->push($descriptor, $payload, $this->opcode);
+ }
+ }
+ }
+}
diff --git a/src/Websocket/Rooms/RedisRoom.php b/src/Websocket/Rooms/RedisRoom.php
index 410ac391..46ef7e9b 100644
--- a/src/Websocket/Rooms/RedisRoom.php
+++ b/src/Websocket/Rooms/RedisRoom.php
@@ -2,47 +2,74 @@
namespace SwooleTW\Http\Websocket\Rooms;
+use Illuminate\Support\Arr;
use Predis\Client as RedisClient;
-use SwooleTW\Http\Websocket\Rooms\RoomContract;
+use Predis\Pipeline\Pipeline;
+/**
+ * Class RedisRoom
+ */
class RedisRoom implements RoomContract
{
+ /**
+ * @var \Predis\Client
+ */
protected $redis;
+ /**
+ * @var array
+ */
protected $config;
+ /**
+ * @var string
+ */
protected $prefix = 'swoole:';
+ /**
+ * RedisRoom constructor.
+ *
+ * @param array $config
+ */
public function __construct(array $config)
{
$this->config = $config;
}
- public function prepare(RedisClient $redis = null)
+ /**
+ * @param \Predis\Client|null $redis
+ *
+ * @return \SwooleTW\Http\Websocket\Rooms\RoomContract
+ */
+ public function prepare(RedisClient $redis = null): RoomContract
{
$this->setRedis($redis);
$this->setPrefix();
$this->cleanRooms();
+
+ return $this;
}
/**
* Set redis client.
+ *
+ * @param \Predis\Client|null $redis
*/
- public function setRedis(RedisClient $redis = null)
+ public function setRedis(?RedisClient $redis = null)
{
- $server = $this->config['server'] ?? [];
- $options = $this->config['options'] ?? [];
+ if (! $redis) {
+ $server = Arr::get($this->config, 'server', []);
+ $options = Arr::get($this->config, 'options', []);
- // forbid setting prefix from options
- if (array_key_exists('prefix', $options)) {
- unset($options['prefix']);
- }
+ // forbid setting prefix from options
+ if (Arr::has($options, 'prefix')) {
+ $options = Arr::except($options, 'prefix');
+ }
- if ($redis) {
- $this->redis = $redis;
- } else {
- $this->redis = new RedisClient($server, $options);
+ $redis = new RedisClient($server, $options);
}
+
+ $this->redis = $redis;
}
/**
@@ -50,8 +77,8 @@ public function setRedis(RedisClient $redis = null)
*/
protected function setPrefix()
{
- if (array_key_exists('prefix', $this->config)) {
- $this->prefix = $this->config['prefix'];
+ if ($prefix = Arr::get($this->config, 'prefix')) {
+ $this->prefix = $prefix;
}
}
@@ -63,35 +90,56 @@ public function getRedis()
return $this->redis;
}
- public function add(int $fd, $roomNames)
+ /**
+ * Add multiple socket fds to a room.
+ *
+ * @param int fd
+ * @param array|string rooms
+ */
+ public function add(int $fd, $rooms)
{
- $roomNames = is_array($roomNames) ? $roomNames : [$roomNames];
+ $rooms = is_array($rooms) ? $rooms : [$rooms];
- $this->addValue($fd, $roomNames, 'fds');
+ $this->addValue($fd, $rooms, RoomContract::DESCRIPTORS_KEY);
- foreach ($roomNames as $room) {
- $this->addValue($room, [$fd], 'rooms');
+ foreach ($rooms as $room) {
+ $this->addValue($room, [$fd], RoomContract::ROOMS_KEY);
}
}
- public function delete(int $fd, $roomNames = [])
+ /**
+ * Delete multiple socket fds from a room.
+ *
+ * @param int fd
+ * @param array|string rooms
+ */
+ public function delete(int $fd, $rooms)
{
- $roomNames = is_array($roomNames) ? $roomNames : [$roomNames];
- $roomNames = count($roomNames) ? $roomNames : $this->getRooms($fd);
+ $rooms = is_array($rooms) ? $rooms : [$rooms];
+ $rooms = count($rooms) ? $rooms : $this->getRooms($fd);
- $this->removeValue($fd, $roomNames, 'fds');
+ $this->removeValue($fd, $rooms, RoomContract::DESCRIPTORS_KEY);
- foreach ($roomNames as $room) {
- $this->removeValue($room, [$fd], 'rooms');
+ foreach ($rooms as $room) {
+ $this->removeValue($room, [$fd], RoomContract::ROOMS_KEY);
}
}
+ /**
+ * Add value to redis.
+ *
+ * @param $key
+ * @param array $values
+ * @param string $table
+ *
+ * @return $this
+ */
public function addValue($key, array $values, string $table)
{
$this->checkTable($table);
$redisKey = $this->getKey($key, $table);
- $this->redis->pipeline(function ($pipe) use ($redisKey, $values) {
+ $this->redis->pipeline(function (Pipeline $pipe) use ($redisKey, $values) {
foreach ($values as $value) {
$pipe->sadd($redisKey, $value);
}
@@ -100,12 +148,21 @@ public function addValue($key, array $values, string $table)
return $this;
}
+ /**
+ * Remove value from redis.
+ *
+ * @param $key
+ * @param array $values
+ * @param string $table
+ *
+ * @return $this
+ */
public function removeValue($key, array $values, string $table)
{
$this->checkTable($table);
$redisKey = $this->getKey($key, $table);
- $this->redis->pipeline(function ($pipe) use ($redisKey, $values) {
+ $this->redis->pipeline(function (Pipeline $pipe) use ($redisKey, $values) {
foreach ($values as $value) {
$pipe->srem($redisKey, $value);
}
@@ -114,39 +171,79 @@ public function removeValue($key, array $values, string $table)
return $this;
}
+ /**
+ * Get all sockets by a room key.
+ *
+ * @param string room
+ *
+ * @return array
+ */
public function getClients(string $room)
{
- return $this->getValue($room, 'rooms');
+ return $this->getValue($room, RoomContract::ROOMS_KEY) ?? [];
}
+ /**
+ * Get all rooms by a fd.
+ *
+ * @param int fd
+ *
+ * @return array
+ */
public function getRooms(int $fd)
{
- return $this->getValue($fd, 'fds');
+ return $this->getValue($fd, RoomContract::DESCRIPTORS_KEY) ?? [];
}
+ /**
+ * Check table for rooms and descriptors.
+ *
+ * @param string $table
+ */
protected function checkTable(string $table)
{
- if (! in_array($table, ['rooms', 'fds'])) {
- throw new \InvalidArgumentException('Invalid table name.');
+ if (! in_array($table, [RoomContract::ROOMS_KEY, RoomContract::DESCRIPTORS_KEY])) {
+ throw new \InvalidArgumentException("Invalid table name: `{$table}`.");
}
}
+ /**
+ * Get value.
+ *
+ * @param string $key
+ * @param string $table
+ *
+ * @return array
+ */
public function getValue(string $key, string $table)
{
$this->checkTable($table);
- return $this->redis->smembers($this->getKey($key, $table));
+ $result = $this->redis->smembers($this->getKey($key, $table));
+
+ // Try to fix occasional non-array returned result
+ return is_array($result) ? $result : [];
}
+ /**
+ * Get key.
+ *
+ * @param string $key
+ * @param string $table
+ *
+ * @return string
+ */
public function getKey(string $key, string $table)
{
return "{$this->prefix}{$table}:{$key}";
}
- protected function cleanRooms()
+ /**
+ * Clean all rooms.
+ */
+ protected function cleanRooms(): void
{
- $keys = $this->redis->keys("{$this->prefix}*");
- if (count($keys)) {
+ if (count($keys = $this->redis->keys("{$this->prefix}*"))) {
$this->redis->del($keys);
}
}
diff --git a/src/Websocket/Rooms/RoomContract.php b/src/Websocket/Rooms/RoomContract.php
index 8a1252a7..28aaefee 100644
--- a/src/Websocket/Rooms/RoomContract.php
+++ b/src/Websocket/Rooms/RoomContract.php
@@ -4,38 +4,58 @@
interface RoomContract
{
+ /**
+ * Rooms key
+ *
+ * @const string
+ */
+ public const ROOMS_KEY = 'rooms';
+
+ /**
+ * Descriptors key
+ *
+ * @const string
+ */
+ public const DESCRIPTORS_KEY = 'fds';
+
/**
* Do some init stuffs before workers started.
+ *
+ * @return \SwooleTW\Http\Websocket\Rooms\RoomContract
*/
- public function prepare();
+ public function prepare(): RoomContract;
/**
* Add multiple socket fds to a room.
*
- * @int fd
- * @string|array rooms
+ * @param int fd
+ * @param array|string rooms
*/
public function add(int $fd, $rooms);
/**
* Delete multiple socket fds from a room.
*
- * @int fd
- * @string|array rooms
+ * @param int fd
+ * @param array|string rooms
*/
public function delete(int $fd, $rooms);
/**
* Get all sockets by a room key.
*
- * @string room
+ * @param string room
+ *
+ * @return array
*/
public function getClients(string $room);
/**
* Get all rooms by a fd.
*
- * @int fd
+ * @param int fd
+ *
+ * @return array
*/
public function getRooms(int $fd);
}
diff --git a/src/Websocket/Rooms/TableRoom.php b/src/Websocket/Rooms/TableRoom.php
index ad3e9146..ae9b0bfd 100644
--- a/src/Websocket/Rooms/TableRoom.php
+++ b/src/Websocket/Rooms/TableRoom.php
@@ -3,27 +3,53 @@
namespace SwooleTW\Http\Websocket\Rooms;
use Swoole\Table;
-use SwooleTW\Http\Websocket\Rooms\RoomContract;
class TableRoom implements RoomContract
{
+ /**
+ * @var array
+ */
protected $config;
+ /**
+ * @var \Swoole\Table
+ */
protected $rooms;
+ /**
+ * @var \Swoole\Table
+ */
protected $fds;
+ /**
+ * TableRoom constructor.
+ *
+ * @param array $config
+ */
public function __construct(array $config)
{
$this->config = $config;
}
- public function prepare()
+ /**
+ * Do some init stuffs before workers started.
+ *
+ * @return \SwooleTW\Http\Websocket\Rooms\RoomContract
+ */
+ public function prepare(): RoomContract
{
$this->initRoomsTable();
$this->initFdsTable();
+
+ return $this;
}
+ /**
+ * Add a socket fd to multiple rooms.
+ *
+ * @param int fd
+ * @param array|string rooms
+ */
public function add(int $fd, $roomNames)
{
$rooms = $this->getRooms($fd);
@@ -45,6 +71,12 @@ public function add(int $fd, $roomNames)
$this->setRooms($fd, $rooms);
}
+ /**
+ * Delete a socket fd from multiple rooms.
+ *
+ * @param int fd
+ * @param array|string rooms
+ */
public function delete(int $fd, $roomNames = [])
{
$allRooms = $this->getRooms($fd);
@@ -59,40 +91,72 @@ public function delete(int $fd, $roomNames = [])
continue;
}
- $this->setClients($room, array_values(array_diff($fds, [$fd])), 'rooms');
+ $this->setClients($room, array_values(array_diff($fds, [$fd])));
$removeRooms[] = $room;
}
- $this->setRooms($fd, array_values(array_diff($allRooms, $removeRooms)), 'fds');
+ $this->setRooms($fd, array_values(array_diff($allRooms, $removeRooms)));
}
+ /**
+ * Get all sockets by a room key.
+ *
+ * @param string room
+ *
+ * @return array
+ */
public function getClients(string $room)
{
- return $this->getValue($room, 'rooms');
+ return $this->getValue($room, RoomContract::ROOMS_KEY) ?? [];
}
+ /**
+ * Get all rooms by a fd.
+ *
+ * @param int fd
+ *
+ * @return array
+ */
public function getRooms(int $fd)
{
- return $this->getValue($fd, 'fds');
+ return $this->getValue($fd, RoomContract::DESCRIPTORS_KEY) ?? [];
}
- protected function setClients(string $room, array $fds)
+ /**
+ * @param string $room
+ * @param array $fds
+ *
+ * @return \SwooleTW\Http\Websocket\Rooms\TableRoom
+ */
+ protected function setClients(string $room, array $fds): TableRoom
{
- return $this->setValue($room, $fds, 'rooms');
+ return $this->setValue($room, $fds, RoomContract::ROOMS_KEY);
}
- protected function setRooms(int $fd, array $rooms)
+ /**
+ * @param int $fd
+ * @param array $rooms
+ *
+ * @return \SwooleTW\Http\Websocket\Rooms\TableRoom
+ */
+ protected function setRooms(int $fd, array $rooms): TableRoom
{
- return $this->setValue($fd, $rooms, 'fds');
+ return $this->setValue($fd, $rooms, RoomContract::DESCRIPTORS_KEY);
}
- protected function initRoomsTable()
+ /**
+ * Init rooms table
+ */
+ protected function initRoomsTable(): void
{
$this->rooms = new Table($this->config['room_rows']);
$this->rooms->column('value', Table::TYPE_STRING, $this->config['room_size']);
$this->rooms->create();
}
+ /**
+ * Init descriptors table
+ */
protected function initFdsTable()
{
$this->fds = new Table($this->config['client_rows']);
@@ -100,17 +164,32 @@ protected function initFdsTable()
$this->fds->create();
}
+ /**
+ * Set value to table
+ *
+ * @param $key
+ * @param array $value
+ * @param string $table
+ *
+ * @return $this
+ */
public function setValue($key, array $value, string $table)
{
$this->checkTable($table);
- $this->$table->set($key, [
- 'value' => json_encode($value)
- ]);
+ $this->$table->set($key, ['value' => json_encode($value)]);
return $this;
}
+ /**
+ * Get value from table
+ *
+ * @param string $key
+ * @param string $table
+ *
+ * @return array|mixed
+ */
public function getValue(string $key, string $table)
{
$this->checkTable($table);
@@ -120,10 +199,15 @@ public function getValue(string $key, string $table)
return $value ? json_decode($value['value'], true) : [];
}
+ /**
+ * Check table for exists
+ *
+ * @param string $table
+ */
protected function checkTable(string $table)
{
if (! property_exists($this, $table) || ! $this->$table instanceof Table) {
- throw new \InvalidArgumentException('Invalid table name.');
+ throw new \InvalidArgumentException("Invalid table name: `{$table}`.");
}
}
}
diff --git a/src/Websocket/SimpleParser.php b/src/Websocket/SimpleParser.php
index a052dafc..fd3758ae 100644
--- a/src/Websocket/SimpleParser.php
+++ b/src/Websocket/SimpleParser.php
@@ -2,9 +2,6 @@
namespace SwooleTW\Http\Websocket;
-use Swoole\Websocket\Server;
-use SwooleTW\Http\Websocket\Parser;
-
class SimpleParser extends Parser
{
/**
@@ -17,14 +14,17 @@ class SimpleParser extends Parser
*
* @param string $event
* @param mixed $data
+ *
* @return mixed
*/
public function encode(string $event, $data)
{
- return json_encode([
- 'event' => $event,
- 'data' => $data
- ]);
+ return json_encode(
+ [
+ 'event' => $event,
+ 'data' => $data,
+ ]
+ );
}
/**
@@ -32,6 +32,7 @@ public function encode(string $event, $data)
* Define and return event name and payload data here.
*
* @param \Swoole\Websocket\Frame $frame
+ *
* @return array
*/
public function decode($frame)
@@ -40,7 +41,7 @@ public function decode($frame)
return [
'event' => $data['event'] ?? null,
- 'data' => $data['data'] ?? null
+ 'data' => $data['data'] ?? null,
];
}
}
diff --git a/src/Websocket/SocketIO/Packet.php b/src/Websocket/SocketIO/Packet.php
index 07a4188a..f1f34ae4 100644
--- a/src/Websocket/SocketIO/Packet.php
+++ b/src/Websocket/SocketIO/Packet.php
@@ -2,6 +2,9 @@
namespace SwooleTW\Http\Websocket\SocketIO;
+/**
+ * Class Packet
+ */
class Packet
{
/**
@@ -84,7 +87,7 @@ class Packet
3 => 'PONG',
4 => 'MESSAGE',
5 => 'UPGRADE',
- 6 => 'NOOP'
+ 6 => 'NOOP',
];
/**
@@ -97,11 +100,15 @@ class Packet
3 => 'ACK',
4 => 'ERROR',
5 => 'BINARY_EVENT',
- 6 => 'BINARY_ACK'
+ 6 => 'BINARY_ACK',
];
/**
* Get socket packet type of a raw payload.
+ *
+ * @param string $packet
+ *
+ * @return int|null
*/
public static function getSocketType(string $packet)
{
@@ -116,6 +123,10 @@ public static function getSocketType(string $packet)
/**
* Get data packet from a raw payload.
+ *
+ * @param string $packet
+ *
+ * @return array|null
*/
public static function getPayload(string $packet)
{
@@ -135,12 +146,17 @@ public static function getPayload(string $packet)
return [
'event' => $data[0],
- 'data' => $data[1] ?? null
+ 'data' => $data[1] ?? null,
];
}
/**
* Return if a socket packet belongs to specific type.
+ *
+ * @param $packet
+ * @param string $typeName
+ *
+ * @return bool
*/
public static function isSocketType($packet, string $typeName)
{
diff --git a/src/Websocket/SocketIO/SocketIOParser.php b/src/Websocket/SocketIO/SocketIOParser.php
index 8c3e50d6..53483fb4 100644
--- a/src/Websocket/SocketIO/SocketIOParser.php
+++ b/src/Websocket/SocketIO/SocketIOParser.php
@@ -3,16 +3,18 @@
namespace SwooleTW\Http\Websocket\SocketIO;
use SwooleTW\Http\Websocket\Parser;
-use SwooleTW\Http\Websocket\SocketIO\Packet;
use SwooleTW\Http\Websocket\SocketIO\Strategies\HeartbeatStrategy;
+/**
+ * Class SocketIOParser
+ */
class SocketIOParser extends Parser
{
/**
* Strategy classes need to implement handle method.
*/
protected $strategies = [
- HeartbeatStrategy::class
+ HeartbeatStrategy::class,
];
/**
@@ -20,6 +22,7 @@ class SocketIOParser extends Parser
*
* @param string $event
* @param mixed $data
+ *
* @return mixed
*/
public function encode(string $event, $data)
@@ -37,6 +40,7 @@ public function encode(string $event, $data)
* Define and return payload here.
*
* @param \Swoole\Websocket\Frame $frame
+ *
* @return array
*/
public function decode($frame)
@@ -45,7 +49,7 @@ public function decode($frame)
return [
'event' => $payload['event'] ?? null,
- 'data' => $payload['data'] ?? null
+ 'data' => $payload['data'] ?? null,
];
}
}
diff --git a/src/Websocket/SocketIO/Strategies/HeartbeatStrategy.php b/src/Websocket/SocketIO/Strategies/HeartbeatStrategy.php
index e1fe7c64..5d65bc09 100644
--- a/src/Websocket/SocketIO/Strategies/HeartbeatStrategy.php
+++ b/src/Websocket/SocketIO/Strategies/HeartbeatStrategy.php
@@ -9,6 +9,9 @@ class HeartbeatStrategy
/**
* If return value is true will skip decoding.
*
+ * @param \Swoole\WebSocket\Server $server
+ * @param \Swoole\WebSocket\Frame $frame
+ *
* @return boolean
*/
public function handle($server, $frame)
diff --git a/src/Websocket/SocketIO/WebsocketHandler.php b/src/Websocket/SocketIO/WebsocketHandler.php
index c78f6498..69319692 100644
--- a/src/Websocket/SocketIO/WebsocketHandler.php
+++ b/src/Websocket/SocketIO/WebsocketHandler.php
@@ -2,12 +2,12 @@
namespace SwooleTW\Http\Websocket\SocketIO;
-use Swoole\Websocket\Frame;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
+use Swoole\Websocket\Frame;
+use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Websocket\HandlerContract;
-use SwooleTW\Http\Websocket\SocketIO\Packet;
class WebsocketHandler implements HandlerContract
{
@@ -16,21 +16,25 @@ class WebsocketHandler implements HandlerContract
*
* @param int $fd
* @param \Illuminate\Http\Request $request
+ *
+ * @return bool
*/
public function onOpen($fd, Request $request)
{
if (! $request->input('sid')) {
- $payload = json_encode([
- 'sid' => base64_encode(uniqid()),
- 'upgrades' => [],
- 'pingInterval' => Config::get('swoole_websocket.ping_interval'),
- 'pingTimeout' => Config::get('swoole_websocket.ping_timeout')
- ]);
+ $payload = json_encode(
+ [
+ 'sid' => base64_encode(uniqid()),
+ 'upgrades' => [],
+ 'pingInterval' => Config::get('swoole_websocket.ping_interval'),
+ 'pingTimeout' => Config::get('swoole_websocket.ping_timeout'),
+ ]
+ );
$initPayload = Packet::OPEN . $payload;
$connectPayload = Packet::MESSAGE . Packet::CONNECT;
- App::make('swoole.server')->push($fd, $initPayload);
- App::make('swoole.server')->push($fd, $connectPayload);
+ App::make(Server::class)->push($fd, $initPayload);
+ App::make(Server::class)->push($fd, $connectPayload);
return true;
}
diff --git a/src/Websocket/Websocket.php b/src/Websocket/Websocket.php
index d0da2d59..41f05494 100644
--- a/src/Websocket/Websocket.php
+++ b/src/Websocket/Websocket.php
@@ -2,14 +2,18 @@
namespace SwooleTW\Http\Websocket;
-use InvalidArgumentException;
+use Illuminate\Contracts\Container\Container;
+use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
-use SwooleTW\Http\Websocket\Authenticatable;
-use Illuminate\Contracts\Container\Container;
+use InvalidArgumentException;
+use SwooleTW\Http\Server\Facades\Server;
+use SwooleTW\Http\Server\Manager;
use SwooleTW\Http\Websocket\Rooms\RoomContract;
-use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
+/**
+ * Class Websocket
+ */
class Websocket
{
use Authenticatable;
@@ -56,22 +60,29 @@ class Websocket
/**
* Pipeline instance.
*
- * @var PipelineContract
+ * @var \Illuminate\Contracts\Pipeline\Pipeline
*/
protected $pipeline;
/**
* Room adapter.
*
- * @var RoomContract
+ * @var \SwooleTW\Http\Websocket\Rooms\RoomContract
*/
protected $room;
+ /**
+ * DI Container.
+ *
+ * @var \Illuminate\Contracts\Container\Container
+ */
+ protected $container;
+
/**
* Websocket constructor.
*
- * @var RoomContract $room
- * @var PipelineContract $pipeline
+ * @param \SwooleTW\Http\Websocket\Rooms\RoomContract $room
+ * @param \Illuminate\Contracts\Pipeline\Pipeline $pipeline
*/
public function __construct(RoomContract $room, PipelineContract $pipeline)
{
@@ -83,7 +94,7 @@ public function __construct(RoomContract $room, PipelineContract $pipeline)
/**
* Set broadcast to true.
*/
- public function broadcast()
+ public function broadcast(): self
{
$this->isBroadcast = true;
@@ -94,9 +105,10 @@ public function broadcast()
* Set multiple recipients fd or room names.
*
* @param integer, string, array
+ *
* @return $this
*/
- public function to($values)
+ public function to($values): self
{
$values = is_string($values) || is_integer($values) ? func_get_args() : $values;
@@ -113,9 +125,10 @@ public function to($values)
* Join sender to multiple rooms.
*
* @param string, array $rooms
+ *
* @return $this
*/
- public function join($rooms)
+ public function join($rooms): self
{
$rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms;
@@ -127,10 +140,11 @@ public function join($rooms)
/**
* Make sender leave multiple rooms.
*
- * @param string, array
+ * @param array $rooms
+ *
* @return $this
*/
- public function leave($rooms = [])
+ public function leave($rooms = []): self
{
$rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms;
@@ -144,9 +158,10 @@ public function leave($rooms = [])
*
* @param string
* @param mixed
+ *
* @return boolean
*/
- public function emit(string $event, $data)
+ public function emit(string $event, $data): bool
{
$fds = $this->getFds();
$assigned = ! empty($this->to);
@@ -155,30 +170,40 @@ public function emit(string $event, $data)
// that means trying to emit to a non-existing room
// skip it directly instead of pushing to a task queue
if (empty($fds) && $assigned) {
+ $this->reset();
return false;
}
- $result = App::make('swoole.server')->task([
- 'action' => static::PUSH_ACTION,
- 'data' => [
- 'sender' => $this->sender,
- 'fds' => $fds,
- 'broadcast' => $this->isBroadcast,
- 'assigned' => $assigned,
- 'event' => $event,
- 'message' => $data
- ]
- ]);
+ $payload = [
+ 'sender' => $this->sender,
+ 'fds' => $fds,
+ 'broadcast' => $this->isBroadcast,
+ 'assigned' => $assigned,
+ 'event' => $event,
+ 'message' => $data,
+ ];
+
+ $result = true;
+ $server = App::make(Server::class);
+ if ($server->taskworker) {
+ App::make(Manager::class)->pushMessage($server, $payload);
+ } else {
+ $result = $server->task([
+ 'action' => static::PUSH_ACTION,
+ 'data' => $payload
+ ]);
+ }
$this->reset();
- return $result === false ? false : true;
+ return $result !== false;
}
/**
* An alias of `join` function.
*
* @param string
+ *
* @return $this
*/
public function in($room)
@@ -193,6 +218,7 @@ public function in($room)
*
* @param string
* @param callback
+ *
* @return $this
*/
public function on(string $event, $callback)
@@ -212,6 +238,7 @@ public function on(string $event, $callback)
* Check if this event name exists.
*
* @param string
+ *
* @return boolean
*/
public function eventExists(string $event)
@@ -224,6 +251,7 @@ public function eventExists(string $event)
*
* @param string
* @param mixed
+ *
* @return mixed
*/
public function call(string $event, $data = null)
@@ -243,7 +271,7 @@ public function call(string $event, $data = null)
return App::call($this->callbacks[$event], [
'websocket' => $this,
- $dataKey => $data
+ $dataKey => $data,
]);
}
@@ -251,17 +279,19 @@ public function call(string $event, $data = null)
* Close current connection.
*
* @param integer
+ *
* @return boolean
*/
public function close(int $fd = null)
{
- return App::make('swoole.server')->close($fd ?: $this->sender);
+ return App::make(Server::class)->close($fd ?: $this->sender);
}
/**
* Set sender fd.
*
* @param integer
+ *
* @return $this
*/
public function setSender(int $fd)
@@ -320,6 +350,10 @@ protected function getFds()
/**
* Reset some data status.
+ *
+ * @param bool $force
+ *
+ * @return $this
*/
public function reset($force = false)
{
@@ -336,6 +370,10 @@ public function reset($force = false)
/**
* Get or set middleware.
+ *
+ * @param array|string|null $middleware
+ *
+ * @return array|\SwooleTW\Http\Websocket\Websocket
*/
public function middleware($middleware = null)
{
@@ -362,6 +400,10 @@ protected function setDefaultMiddleware()
/**
* Set container to pipeline.
+ *
+ * @param \Illuminate\Contracts\Container\Container $container
+ *
+ * @return $this
*/
public function setContainer(Container $container)
{
@@ -373,10 +415,16 @@ public function setContainer(Container $container)
$resetPipeline = $closure->bindTo($pipeline, $pipeline);
$resetPipeline();
+
+ return $this;
}
/**
* Set pipeline.
+ *
+ * @param \Illuminate\Contracts\Pipeline\Pipeline $pipeline
+ *
+ * @return $this
*/
public function setPipeline(PipelineContract $pipeline)
{
@@ -396,7 +444,8 @@ public function getPipeline()
/**
* Set the given request through the middleware.
*
- * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Http\Request $request
+ *
* @return \Illuminate\Http\Request
*/
protected function setRequestThroughMiddleware($request)
diff --git a/stubs/5.5/MySqlConnector.stub b/stubs/5.5/MySqlConnector.stub
new file mode 100644
index 00000000..13315b10
--- /dev/null
+++ b/stubs/5.5/MySqlConnector.stub
@@ -0,0 +1,52 @@
+causedByLostConnection($e) || Str::contains($e->getMessage(), 'is closed')) {
+ return $this->createPdoConnection($dsn, $username, $password, $options);
+ }
+
+ throw $e;
+ }
+}
diff --git a/stubs/5.6/MySqlConnector.stub b/stubs/5.6/MySqlConnector.stub
new file mode 100644
index 00000000..2efa720a
--- /dev/null
+++ b/stubs/5.6/MySqlConnector.stub
@@ -0,0 +1,52 @@
+causedByLostConnection($e) || Str::contains($e->getMessage(), 'is closed')) {
+ return $this->createPdoConnection($dsn, $username, $password, $options);
+ }
+
+ throw $e;
+ }
+}
diff --git a/stubs/5.6/SwooleTaskQueue.stub b/stubs/5.6/SwooleTaskQueue.stub
new file mode 100644
index 00000000..2e331e8d
--- /dev/null
+++ b/stubs/5.6/SwooleTaskQueue.stub
@@ -0,0 +1,113 @@
+swoole = $swoole;
+ }
+
+ /**
+ * Push a new job onto the queue.
+ *
+ * @param string|object $job
+ * @param mixed $data
+ * @param string $queue
+ *
+ * @return mixed
+ */
+ public function push($job, $data = '', $queue = null)
+ {
+ return $this->pushRaw($this->createPayload($job, $data), $queue);
+ }
+
+ /**
+ * Push a raw payload onto the queue.
+ *
+ * @param string $payload
+ * @param string $queue
+ * @param array $options
+ *
+ * @return mixed
+ */
+ public function pushRaw($payload, $queue = null, array $options = [])
+ {
+ return $this->swoole->task($payload, ! is_numeric($queue) ? -1 : (int)$queue);
+ }
+
+ /**
+ * Push a new job onto the queue after a delay.
+ *
+ * @param \DateTimeInterface|\DateInterval|int $delay
+ * @param string|object $job
+ * @param mixed $data
+ * @param string $queue
+ *
+ * @return mixed
+ */
+ public function later($delay, $job, $data = '', $queue = null)
+ {
+ return Timer::after($this->secondsUntil($delay) * 1000, function () use ($job, $data, $queue) {
+ return $this->push($job, $data, $queue);
+ });
+ }
+
+ /**
+ * Create a typical, string based queue payload array.
+ *
+ * @param string $job
+ * @param mixed $data
+ *
+ * @throws \Exception
+ */
+ protected function createStringPayload($job, $data)
+ {
+ throw new Exception('Unsupported empty data');
+ }
+
+ /**
+ * Get the size of the queue.
+ *
+ * @param string $queue
+ *
+ * @return int
+ */
+ public function size($queue = null)
+ {
+ return -1;
+ }
+
+ /**
+ * Pop the next job off of the queue.
+ *
+ * @param string $queue
+ *
+ * @return \Illuminate\Contracts\Queue\Job|null
+ */
+ public function pop($queue = null)
+ {
+ return null;
+ }
+}
diff --git a/stubs/5.7/SwooleTaskQueue.stub b/stubs/5.7/SwooleTaskQueue.stub
new file mode 100644
index 00000000..5eb0e73d
--- /dev/null
+++ b/stubs/5.7/SwooleTaskQueue.stub
@@ -0,0 +1,114 @@
+swoole = $swoole;
+ }
+
+ /**
+ * Push a new job onto the queue.
+ *
+ * @param string|object $job
+ * @param mixed $data
+ * @param string $queue
+ *
+ * @return mixed
+ */
+ public function push($job, $data = '', $queue = null)
+ {
+ return $this->pushRaw($this->createPayload($job, $data), $queue);
+ }
+
+ /**
+ * Push a raw payload onto the queue.
+ *
+ * @param string $payload
+ * @param string $queue
+ * @param array $options
+ *
+ * @return mixed
+ */
+ public function pushRaw($payload, $queue = null, array $options = [])
+ {
+ return $this->swoole->task($payload, ! is_numeric($queue) ? -1 : (int)$queue);
+ }
+
+ /**
+ * Push a new job onto the queue after a delay.
+ *
+ * @param \DateTimeInterface|\DateInterval|int $delay
+ * @param string|object $job
+ * @param mixed $data
+ * @param string $queue
+ *
+ * @return mixed
+ */
+ public function later($delay, $job, $data = '', $queue = null)
+ {
+ return Timer::after($this->secondsUntil($delay) * 1000, function () use ($job, $data, $queue) {
+ return $this->push($job, $data, $queue);
+ });
+ }
+
+ /**
+ * Create a typical, string based queue payload array.
+ *
+ * @param string $job
+ * @param string $queue
+ * @param mixed $data
+ *
+ * @throws \Exception
+ */
+ protected function createStringPayload($job, $queue, $data)
+ {
+ throw new Exception('Unsupported empty data');
+ }
+
+ /**
+ * Get the size of the queue.
+ *
+ * @param string $queue
+ *
+ * @return int
+ */
+ public function size($queue = null)
+ {
+ return -1;
+ }
+
+ /**
+ * Pop the next job off of the queue.
+ *
+ * @param string $queue
+ *
+ * @return \Illuminate\Contracts\Queue\Job|null
+ */
+ public function pop($queue = null)
+ {
+ return null;
+ }
+}
diff --git a/tests/Coroutine/ConnectorFactoryTest.php b/tests/Coroutine/ConnectorFactoryTest.php
new file mode 100644
index 00000000..27e70a4a
--- /dev/null
+++ b/tests/Coroutine/ConnectorFactoryTest.php
@@ -0,0 +1,50 @@
+mockEnv('Laravel\Lumen');
+ }
+
+ public function testItHasNeededStubByVersion()
+ {
+ $version = FW::version();
+
+ $search = version_compare($version, '5.6', '>=') ? '5.6' : '5.5';
+ $stub = ConnectorFactory::stub($version);
+
+ $this->assertTrue(Str::contains($stub, $search));
+ }
+
+ public function testItCanCompareNeededStubByVersion()
+ {
+ $version = '5.5';
+ $search = '5.6';
+
+ $stub = ConnectorFactory::stub($version);
+
+ $this->assertNotTrue(Str::contains($stub, $search));
+ }
+
+ public function testItCanMakeNeededQueueByVersion()
+ {
+ $version = FW::version();
+ $queue = ConnectorFactory::make($version);
+
+ $this->assertInstanceOf(ConnectorFactory::CONNECTOR_CLASS, $queue);
+ }
+}
\ No newline at end of file
diff --git a/tests/Coroutine/ContextTest.php b/tests/Coroutine/ContextTest.php
index fb5c6c36..a9ef6800 100644
--- a/tests/Coroutine/ContextTest.php
+++ b/tests/Coroutine/ContextTest.php
@@ -2,11 +2,11 @@
namespace SwooleTW\Http\Tests\Coroutine;
-use TypeError;
-use Mockery as m;
-use SwooleTW\Http\Tests\TestCase;
use Illuminate\Container\Container;
+use Mockery as m;
use SwooleTW\Http\Coroutine\Context;
+use SwooleTW\Http\Tests\TestCase;
+use TypeError;
class ContextTest extends TestCase
{
diff --git a/tests/Helpers/MimeTypeTest.php b/tests/Helpers/MimeTypeTest.php
new file mode 100644
index 00000000..1dd9e7dd
--- /dev/null
+++ b/tests/Helpers/MimeTypeTest.php
@@ -0,0 +1,33 @@
+assertEquals($mimetype, 'text/css');
+ }
+
+ public function testGetWithEmptyString()
+ {
+ $extension = '';
+ $mimetype = MimeType::get($extension);
+
+ $this->assertEquals($mimetype, 'application/octet-stream');
+ }
+
+ public function testFrom()
+ {
+ $filename = 'test.css?id=12d123fadf';
+ $mimetype = MimeType::from($filename);
+
+ $this->assertEquals($mimetype, 'text/css');
+ }
+}
diff --git a/tests/HotReload/FSEventParserTest.php b/tests/HotReload/FSEventParserTest.php
new file mode 100644
index 00000000..33deea92
--- /dev/null
+++ b/tests/HotReload/FSEventParserTest.php
@@ -0,0 +1,28 @@
+assertInstanceOf(FSEvent::class, $event);
+
+ $this->assertTrue(array_diff($event->getTypes(), [FSEvent::Renamed, FSEvent::OwnerModified]) === []);
+ $this->assertTrue((new Carbon('Mon Dec 31 01:18:34 2018'))->eq($event->getWhen()));
+ $this->assertEquals('/Some/Path/To/File/File.php', $event->getPath());
+ $this->assertTrue($event->isType(FSEvent::Renamed));
+ $this->assertTrue($event->isType(FSEvent::OwnerModified));
+ }
+}
\ No newline at end of file
diff --git a/tests/HotReload/FSEventTest.php b/tests/HotReload/FSEventTest.php
new file mode 100644
index 00000000..65fef901
--- /dev/null
+++ b/tests/HotReload/FSEventTest.php
@@ -0,0 +1,29 @@
+assertTrue(array_diff($event->getTypes(), [FSEvent::Renamed, FSEvent::OwnerModified]) === []);
+ $this->assertTrue((new Carbon('Mon Dec 31 01:18:34 2018'))->eq($event->getWhen()));
+ $this->assertEquals('/Some/Path/To/File/File.php', $event->getPath());
+ $this->assertTrue($event->isType(FSEvent::Renamed));
+ $this->assertTrue($event->isType(FSEvent::OwnerModified));
+ }
+}
\ No newline at end of file
diff --git a/tests/HotReload/FSOutputTest.php b/tests/HotReload/FSOutputTest.php
new file mode 100644
index 00000000..f89cb4bc
--- /dev/null
+++ b/tests/HotReload/FSOutputTest.php
@@ -0,0 +1,24 @@
+assertEquals(
+ 'File: /Some/Path/To/File/File.php OwnerModified, Renamed at 2018.12.31 01:18:34',
+ FSOutput::format($event)
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/HotReload/FSProcessTest.php b/tests/HotReload/FSProcessTest.php
new file mode 100644
index 00000000..c4c7b858
--- /dev/null
+++ b/tests/HotReload/FSProcessTest.php
@@ -0,0 +1,45 @@
+assertInstanceOf(FSProcess::class, $process);
+ $this->assertInstanceOf(Process::class, $process->make());
+ }
+
+ public function testItCanCreateHotReloadProcessWithNeededConfiguration()
+ {
+ $process = new FSProcess('.php', true, __DIR__);
+ $ref = new \ReflectionClass($process);
+ $configure = $ref->getMethod('configure');
+ $configure->setAccessible(true);
+ $configuration = $configure->invoke($process);
+ $sampleConfiguration = [
+ 'fswatch',
+ '-rtx',
+ '-e',
+ '.*',
+ '-i',
+ "\\.php$",
+ __DIR__,
+ ];
+
+ $this->assertInstanceOf(FSProcess::class, $process);
+ $this->assertInstanceOf(Process::class, $process->make());
+ $this->assertTrue(
+ array_diff($sampleConfiguration, $configuration) === []
+ );
+ }
+}
\ No newline at end of file
diff --git a/tests/Server/ManagerTest.php b/tests/Server/ManagerTest.php
index d0b80fc7..2f56f526 100644
--- a/tests/Server/ManagerTest.php
+++ b/tests/Server/ManagerTest.php
@@ -10,17 +10,21 @@
use SwooleTW\Http\Server\Sandbox;
use SwooleTW\Http\Tests\TestCase;
use Illuminate\Container\Container;
+use SwooleTW\Http\Websocket\HandShakeHandler;
use SwooleTW\Http\Websocket\Parser;
+use SwooleTW\Http\Server\PidManager;
use SwooleTW\Http\Table\SwooleTable;
-use Swoole\Http\Server as HttpServer;
+use Laravel\Lumen\Exceptions\Handler;
use Illuminate\Support\Facades\Config;
use SwooleTW\Http\Websocket\Websocket;
+use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Websocket\HandlerContract;
use SwooleTW\Http\Websocket\Rooms\TableRoom;
use SwooleTW\Http\Websocket\Rooms\RoomContract;
use Illuminate\Contracts\Debug\ExceptionHandler;
use SwooleTW\Http\Websocket\SocketIO\SocketIOParser;
use SwooleTW\Http\Websocket\SocketIO\WebsocketHandler;
+use Illuminate\Contracts\Config\Repository as ConfigContract;
use SwooleTW\Http\Websocket\Facades\Websocket as WebsocketFacade;
class ManagerTest extends TestCase
@@ -31,31 +35,33 @@ class ManagerTest extends TestCase
'table_name' => [
'size' => 1024,
'columns' => [
- ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024]
- ]
- ]
+ ['name' => 'column_name', 'type' => Table::TYPE_STRING, 'size' => 1024],
+ ],
+ ],
],
'swoole_http.providers' => [],
'swoole_http.resetters' => [],
- 'swoole_http.pre_resolved' => ['foo']
+ 'swoole_http.pre_resolved' => ['foo'],
];
protected $websocketConfig = [
'swoole_http.websocket.enabled' => true,
'swoole_websocket.parser' => SocketIOParser::class,
'swoole_websocket.handler' => WebsocketHandler::class,
+ 'swoole_websocket.handshake.enabled' => true,
+ 'swoole_websocket.handshake.handler' => HandShakeHandler::class,
'swoole_websocket.default' => 'table',
'swoole_websocket.settings.table' => [
'room_rows' => 10,
'room_size' => 10,
'client_rows' => 10,
- 'client_size' => 10
+ 'client_size' => 10,
],
'swoole_websocket.drivers.table' => TableRoom::class,
'swoole_http.tables' => [],
'swoole_http.providers' => [],
'swoole_http.resetters' => [],
- 'swoole_http.server.public_path' => '/'
+ 'swoole_http.server.public_path' => '/',
];
public function testGetFramework()
@@ -74,7 +80,7 @@ public function testGetWebsocketParser()
{
$manager = $this->getWebsocketManager();
- $this->assertTrue($manager->getParser() instanceof SocketIOParser);
+ $this->assertTrue($manager->getPayloadParser() instanceof SocketIOParser);
}
public function testRun()
@@ -99,19 +105,18 @@ public function testStop()
public function testOnStart()
{
- $filePutContents = false;
- $this->mockMethod('file_put_contents', function () use (&$filePutContents) {
- $filePutContents = true;
- });
+ $pidManager = m::mock(PidManager::class);
+ $pidManager->shouldReceive('write')->once();
$container = $this->getContainer();
$container->singleton('events', function () {
return $this->getEvent('swoole.start');
});
+ $container->singleton(PidManager::class, function () use ($pidManager) {
+ return $pidManager;
+ });
$manager = $this->getManager($container);
$manager->onStart();
-
- $this->assertTrue($filePutContents);
}
public function testOnManagerStart()
@@ -127,7 +132,7 @@ public function testOnManagerStart()
public function testOnWorkerStart()
{
$server = $this->getServer();
- $manager = $this->getManager();
+ $this->getManager();
$container = $this->getContainer($this->getServer(), $this->getConfig(true));
$container->singleton('events', function () {
@@ -135,8 +140,8 @@ public function testOnWorkerStart()
});
Config::shouldReceive('get')
- ->with('swoole_websocket.middleware', [])
- ->once();
+ ->with('swoole_websocket.middleware', [])
+ ->once();
WebsocketFacade::shouldReceive('on')->times(3);
$manager = $this->getWebsocketManager($container);
@@ -144,18 +149,16 @@ public function testOnWorkerStart()
$manager->onWorkerStart($server);
$app = $manager->getApplication();
- $this->assertTrue($app->make('swoole.sandbox') instanceof Sandbox);
+ $this->assertTrue($app->make(Sandbox::class) instanceof Sandbox);
$this->assertTrue($app->make('swoole.table') instanceof SwooleTable);
$this->assertTrue($app->make('swoole.room') instanceof RoomContract);
- $this->assertTrue($app->make('swoole.websocket') instanceof Websocket);
+ $this->assertTrue($app->make(Websocket::class) instanceof Websocket);
}
public function testLoadApplication()
{
$server = $this->getServer();
- $manager = $this->getManager();
-
- $container = $this->getContainer($this->getServer(), $this->getConfig());
+ $container = $this->getContainer($server, $this->getConfig());
$container->singleton('events', function () {
return $this->getEvent('swoole.workerStart');
});
@@ -163,8 +166,6 @@ public function testLoadApplication()
$path = __DIR__ . '/../fixtures';
$manager = $this->getManager($container, $framework = 'laravel', $path);
$manager->onWorkerStart($server);
-
- $app = $manager->getApplication();
}
public function testOnTaskWorkerStart()
@@ -177,61 +178,68 @@ public function testOnTaskWorkerStart()
return $this->getEvent('swoole.workerStart');
});
- $manager = $this->getManager($container);
+ $path = __DIR__ . '/../fixtures';
+ $manager = $this->getManager($container, $framework = 'laravel', $path);
$this->assertNull($manager->onWorkerStart($server));
}
public function testOnRequest()
{
- $server = $this->getServer();
- $manager = $this->getManager();
+ $this->getServer();
+ $this->getManager();
$container = $this->getContainer($this->getServer(), $this->getConfig(true));
$container->singleton('events', function () {
return $this->getEvent('swoole.request', false);
});
- $container->singleton('swoole.websocket', function () {
+ $container->singleton(Websocket::class, function () {
$websocket = m::mock(Websocket::class);
$websocket->shouldReceive('reset')
- ->with(true)
- ->once();
+ ->with(true)
+ ->once();
+
return $websocket;
});
- $container->singleton('swoole.sandbox', function () {
+ $container->singleton(Sandbox::class, function () {
$sandbox = m::mock(Sandbox::class);
$sandbox->shouldReceive('setRequest')
- ->with(m::type('Illuminate\Http\Request'))
- ->once();
+ ->with(m::type('Illuminate\Http\Request'))
+ ->once();
$sandbox->shouldReceive('enable')
- ->once();
+ ->once();
$sandbox->shouldReceive('run')
- ->with(m::type('Illuminate\Http\Request'))
- ->once();
+ ->with(m::type('Illuminate\Http\Request'))
+ ->once();
$sandbox->shouldReceive('disable')
- ->once();
+ ->once();
+
return $sandbox;
});
+ $container->alias(Sandbox::class, 'swoole.sandbox');
+
$this->mockMethod('base_path', function () {
return '/';
});
$request = m::mock(Request::class);
$request->shouldReceive('rawcontent')
- ->once()
- ->andReturn([]);
+ ->once()
+ ->andReturn([]);
$response = m::mock(Response::class);
$response->shouldReceive('header')
- ->twice()
- ->andReturnSelf();
+ ->twice()
+ ->andReturnSelf();
$response->shouldReceive('status')
- ->once()
- ->andReturnSelf();
+ ->once()
+ ->andReturnSelf();
+ $response->shouldReceive('write')
+ ->andReturnSelf();
$response->shouldReceive('end')
- ->once()
- ->andReturnSelf();
+ ->once()
+ ->andReturnSelf();
$manager = $this->getWebsocketManager();
$manager->setApplication($container);
@@ -245,16 +253,21 @@ public function testOnRequestException()
$container->singleton('events', function () {
return $this->getEvent('swoole.request', false);
});
- $container->singleton('swoole.sandbox', function () {
+ $container->singleton(Sandbox::class, function () {
$sandbox = m::mock(Sandbox::class);
$sandbox->shouldReceive('disable')
- ->once();
+ ->once();
+
return $sandbox;
});
+
+ $container->alias(Sandbox::class, 'swoole.sandbox');
+
$container->singleton(ExceptionHandler::class, function () {
$handler = m::mock(ExceptionHandler::class);
$handler->shouldReceive('render')
- ->once();
+ ->once();
+
return $handler;
});
@@ -264,19 +277,21 @@ public function testOnRequestException()
$request = m::mock(Request::class);
$request->shouldReceive('rawcontent')
- ->once()
- ->andReturn([]);
+ ->once()
+ ->andReturn([]);
$response = m::mock(Response::class);
$response->shouldReceive('header')
- ->twice()
- ->andReturnSelf();
+ ->twice()
+ ->andReturnSelf();
$response->shouldReceive('status')
- ->once()
- ->andReturnSelf();
+ ->once()
+ ->andReturnSelf();
+ $response->shouldReceive('write')
+ ->andReturnSelf();
$response->shouldReceive('end')
- ->once()
- ->andReturnSelf();
+ ->once()
+ ->andReturnSelf();
$manager = $this->getManager($container);
$manager->setApplication($container);
@@ -295,30 +310,25 @@ public function testOnTask()
public function testOnShutdown()
{
- $fileExists = false;
- $this->mockMethod('file_exists', function () use (&$fileExists) {
- return $fileExists = true;
- });
+ $pidManager = m::mock(PidManager::class);
+ $pidManager->shouldReceive('delete')->once();
- $unlink = false;
- $this->mockMethod('unlink', function () use (&$unlink) {
- return $unlink = true;
+ $container = $this->getContainer();
+ $container->singleton(PidManager::class, function () use ($pidManager) {
+ return $pidManager;
});
- $manager = $this->getManager();
+ $manager = $this->getManager($container);
$manager->onShutdown();
-
- $this->assertTrue($fileExists);
- $this->assertTrue($unlink);
}
public function testSetParser()
{
$parser = m::mock(Parser::class);
$manager = $this->getManager();
- $manager->setParser($parser);
+ $manager->setPayloadParser($parser);
- $this->assertSame($parser, $manager->getParser());
+ $this->assertSame($parser, $manager->getPayloadParser());
}
public function testSetWebsocketHandler()
@@ -337,8 +347,9 @@ public function testLogServerError()
$container->singleton(ExceptionHandler::class, function () use ($exception) {
$handler = m::mock(ExceptionHandler::class);
$handler->shouldReceive('report')
- ->with($exception)
- ->once();
+ ->with($exception)
+ ->once();
+
return $handler;
});
$manager = $this->getManager($container);
@@ -350,56 +361,126 @@ public function testOnOpen()
{
$request = m::mock(Request::class);
$request->shouldReceive('rawcontent')
- ->once()
- ->andReturn([]);
+ ->once()
+ ->andReturn([]);
$request->fd = 1;
$container = $this->getContainer();
- $container->singleton('swoole.websocket', function () {
+ $container->singleton(Websocket::class, function () {
$websocket = m::mock(Websocket::class);
$websocket->shouldReceive('reset')
- ->with(true)
- ->once()
- ->andReturnSelf();
+ ->with(true)
+ ->once()
+ ->andReturnSelf();
$websocket->shouldReceive('setSender')
- ->with(1)
- ->once();
+ ->with(1)
+ ->once();
$websocket->shouldReceive('eventExists')
- ->with('connect')
- ->once()
- ->andReturn(true);
+ ->with('connect')
+ ->once()
+ ->andReturn(true);
$websocket->shouldReceive('setContainer')
- ->with(m::type(Container::class))
- ->once();
- $websocket->shouldReceive('call')
- ->with('connect', m::type('Illuminate\Http\Request'))
- ->once();
+ ->with(m::type(Container::class))
+ ->once();
+ $websocket->shouldReceive('call')
+ ->with('connect', m::type('Illuminate\Http\Request'))
+ ->once();
+
return $websocket;
});
- $container->singleton('swoole.sandbox', function () {
+ $container->singleton(Sandbox::class, function () {
$sandbox = m::mock(Sandbox::class);
$sandbox->shouldReceive('setRequest')
- ->with(m::type('Illuminate\Http\Request'))
- ->once();
+ ->with(m::type('Illuminate\Http\Request'))
+ ->once();
$sandbox->shouldReceive('enable')
- ->once();
+ ->once();
$sandbox->shouldReceive('disable')
- ->once();
+ ->once();
$sandbox->shouldReceive('getApplication')
+ ->once()
+ ->andReturn(m::mock(Container::class));
+
+ return $sandbox;
+ });
+
+ $container->alias(Sandbox::class, 'swoole.sandbox');
+
+ $handler = m::mock(HandlerContract::class);
+ $handler->shouldReceive('onOpen')
+ ->with(1, m::type('Illuminate\Http\Request'))
+ ->andReturn(true);
+
+ $manager = $this->getWebsocketManager();
+ $manager->setApplication($container);
+ $manager->setWebsocketHandler($handler);
+ $manager->onOpen(m::mock('server'), $request);
+ }
+
+ public function testOnHandShake()
+ {
+ $request = m::mock(Request::class);
+ $request->shouldReceive('rawcontent')
->once()
- ->andReturn(m::mock(Container::class));
+ ->andReturn([]);
+ $request->fd = 1;
+ $request->header['sec-websocket-key'] = 'Bet8DkPFq9ZxvIBvPcNy1A==';
+
+ $response = m::mock(Response::class);
+ $response->shouldReceive('header')->withAnyArgs()->times(4)->andReturnSelf();
+ $response->shouldReceive('status')->with(101)->once()->andReturnSelf();
+ $response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
+
+ $container = $this->getContainer($this->getServer(), $this->getConfig(true));
+ $container->singleton(Websocket::class, function () {
+ $websocket = m::mock(Websocket::class);
+ $websocket->shouldReceive('reset')
+ ->with(true)
+ ->once()
+ ->andReturnSelf();
+ $websocket->shouldReceive('setSender')
+ ->with(1)
+ ->once();
+ $websocket->shouldReceive('eventExists')
+ ->with('connect')
+ ->once()
+ ->andReturn(true);
+ $websocket->shouldReceive('setContainer')
+ ->with(m::type(Container::class))
+ ->once();
+ $websocket->shouldReceive('call')
+ ->with('connect', m::type('Illuminate\Http\Request'))
+ ->once();
+
+ return $websocket;
+ });
+ $container->singleton(Sandbox::class, function () {
+ $sandbox = m::mock(Sandbox::class);
+ $sandbox->shouldReceive('setRequest')
+ ->with(m::type('Illuminate\Http\Request'))
+ ->once();
+ $sandbox->shouldReceive('enable')
+ ->once();
+ $sandbox->shouldReceive('disable')
+ ->once();
+ $sandbox->shouldReceive('getApplication')
+ ->once()
+ ->andReturn(m::mock(Container::class));
+
return $sandbox;
});
+ $container->alias(Sandbox::class, 'swoole.sandbox');
+
$handler = m::mock(HandlerContract::class);
$handler->shouldReceive('onOpen')
- ->with(1, m::type('Illuminate\Http\Request'))
- ->andReturn(true);
+ ->with(1, m::type('Illuminate\Http\Request'))
+ ->andReturn(true);
$manager = $this->getWebsocketManager();
$manager->setApplication($container);
$manager->setWebsocketHandler($handler);
- $manager->onOpen('server', $request);
+ $manager->onHandShake($request, $response);
}
public function testOnMessage()
@@ -409,48 +490,52 @@ public function testOnMessage()
$parser = m::mock(Parser::class);
$parser->shouldReceive('execute')
- ->with('server', $frame)
- ->once()
- ->andReturn(false);
+ ->with('server', $frame)
+ ->once()
+ ->andReturn(false);
$parser->shouldReceive('decode')
- ->with($frame)
- ->once()
- ->andReturn($payload = [
- 'event' => 'event',
- 'data' => 'data'
- ]);
+ ->with($frame)
+ ->once()
+ ->andReturn($payload = [
+ 'event' => 'event',
+ 'data' => 'data',
+ ]);
$container = $this->getContainer();
- $container->singleton('swoole.websocket', function () use ($payload) {
+ $container->singleton(Websocket::class, function () use ($payload) {
$websocket = m::mock(Websocket::class);
$websocket->shouldReceive('reset')
- ->with(true)
- ->once()
- ->andReturnSelf();
+ ->with(true)
+ ->once()
+ ->andReturnSelf();
$websocket->shouldReceive('setSender')
- ->with(1)
- ->once();
+ ->with(1)
+ ->once();
$websocket->shouldReceive('eventExists')
- ->with($payload['event'])
- ->once()
- ->andReturn(true);
+ ->with($payload['event'])
+ ->once()
+ ->andReturn(true);
$websocket->shouldReceive('call')
- ->with($payload['event'], $payload['data'])
- ->once();
+ ->with($payload['event'], $payload['data'])
+ ->once();
+
return $websocket;
});
- $container->singleton('swoole.sandbox', function () {
+ $container->singleton(Sandbox::class, function () {
$sandbox = m::mock(Sandbox::class);
$sandbox->shouldReceive('enable')
- ->once();
+ ->once();
$sandbox->shouldReceive('disable')
- ->once();
+ ->once();
+
return $sandbox;
});
+ $container->alias(Sandbox::class, 'swoole.sandbox');
+
$manager = $this->getWebsocketManager();
$manager->setApplication($container);
- $manager->setParser($parser);
+ $manager->setPayloadParser($parser);
$manager->onMessage('server', $frame);
}
@@ -458,24 +543,25 @@ public function testOnClose()
{
$fd = 1;
$app = $this->getContainer();
- $app->singleton('swoole.websocket', function () use ($fd) {
+ $app->singleton(Websocket::class, function () use ($fd) {
$websocket = m::mock(Websocket::class);
$websocket->shouldReceive('reset')
- ->with(true)
- ->once()
- ->andReturnSelf();
+ ->with(true)
+ ->once()
+ ->andReturnSelf();
$websocket->shouldReceive('setSender')
- ->with($fd)
- ->once();
+ ->with($fd)
+ ->once();
$websocket->shouldReceive('eventExists')
- ->with('disconnect')
- ->once()
- ->andReturn(true);
+ ->with('disconnect')
+ ->once()
+ ->andReturn(true);
$websocket->shouldReceive('call')
- ->with('disconnect')
- ->once();
+ ->with('disconnect')
+ ->once();
$websocket->shouldReceive('leave')
- ->once();
+ ->once();
+
return $websocket;
});
@@ -483,72 +569,73 @@ public function testOnClose()
$server->shouldReceive('on');
$container = $this->getContainer($server);
- $container->singleton('swoole.server', function () use ($fd) {
+ $container->singleton(Server::class, function () use ($fd) {
$server = m::mock('server');
$server->shouldReceive('on');
$server->taskworker = false;
$server->master_pid = -1;
$server->shouldReceive('connection_info')
- ->with($fd)
- ->once()
- ->andReturn([
- 'websocket_status' => true
- ]);
+ ->with($fd)
+ ->once()
+ ->andReturn([
+ 'websocket_status' => true,
+ ]);
+
return $server;
});
+ $container->alias(Server::class, 'swoole.server');
+
$manager = $this->getWebsocketManager($container);
$manager->setApplication($app);
$manager->onClose('server', $fd, 'reactorId');
}
- public function testNormalizePushMessage()
+ public function testIsWebsocketPushPayload()
{
- $data = [
- 'opcode' => 'opcode',
- 'sender' => 'sender',
- 'fds' => 'fds',
- 'broadcast' => 'broadcast',
- 'assigned' => 'assigned',
- 'event' => 'event',
- 'message' => 'message'
+ $payload = [
+ 'action' => 'push',
+ 'data' => [
+ 'opcode' => 'opcode',
+ 'sender' => 'sender',
+ 'fds' => 'fds',
+ 'broadcast' => 'broadcast',
+ 'assigned' => 'assigned',
+ 'event' => 'event',
+ 'message' => 'message',
+ ]
];
$manager = $this->getWebsocketManager();
- $result = $manager->normalizePushData($data);
-
- $this->assertSame($data['opcode'], $result[0]);
- $this->assertSame($data['sender'], $result[1]);
- $this->assertSame($data['fds'], $result[2]);
- $this->assertSame($data['broadcast'], $result[3]);
- $this->assertSame($data['assigned'], $result[4]);
- $this->assertSame($data['event'], $result[5]);
- $this->assertSame($data['message'], $result[6]);
+
+ $this->assertTrue($manager->isWebsocketPushPayload($payload));
+ $this->assertFalse($manager->isWebsocketPushPayload(['foo' => 'bar']));
}
public function testPushMessage()
{
$data = [
+ 'sender' => 1,
'fds' => [1, 2, 3],
- 'event' => 'event',
- 'message' => 'message'
+ 'event' => $event = 'event',
+ 'message' => $message = 'message',
+ 'broadcast' => true
];
$parser = m::mock(Parser::class);
$parser->shouldReceive('encode')
- ->with($data['event'], $data['message'])
- ->once()
- ->andReturn(false);
+ ->with($data['event'], $data['message'])
+ ->once()
+ ->andReturn(false);
$server = m::mock('server');
- $server->shouldReceive('exist')
- ->times(count($data['fds']))
+ $server->shouldReceive('isEstablished')
->andReturn(true);
$server->shouldReceive('push')
- ->times(count($data['fds']));
+ ->twice();
$manager = $this->getWebsocketManager();
- $manager->setParser($parser);
+ $manager->setPayloadParser($parser);
$manager->pushMessage($server, $data);
}
@@ -568,12 +655,16 @@ protected function getContainer($server = null, $config = null)
$config = $config ?? $this->getConfig();
$container = new Container;
- $container->singleton('config', function () use ($config) {
+ $container->singleton(ConfigContract::class, function () use ($config) {
return $config;
});
- $container->singleton('swoole.server', function () use ($server) {
+ $container->alias(ConfigContract::class, 'config');
+
+ $container->singleton(Server::class, function () use ($server) {
return $server;
});
+ $container->alias(Server::class, 'swoole.server');
+ $container->singleton(ExceptionHandler::class, Handler::class);
return $container;
}
@@ -590,18 +681,18 @@ protected function getServer()
protected function getConfig($websocket = false)
{
- $config = m::mock('config');
+ $config = m::mock(ConfigContract::class);
$settings = $websocket ? 'websocketConfig' : 'config';
$callback = function ($key) use ($settings) {
return $this->$settings[$key] ?? '';
};
$config->shouldReceive('get')
- ->with(m::type('string'), m::any())
- ->andReturnUsing($callback);
+ ->with(m::type('string'), m::any())
+ ->andReturnUsing($callback);
$config->shouldReceive('get')
- ->with(m::type('string'))
- ->andReturnUsing($callback);
+ ->with(m::type('string'))
+ ->andReturnUsing($callback);
return $config;
}
@@ -609,9 +700,9 @@ protected function getConfig($websocket = false)
protected function getEvent($name, $default = true)
{
$event = m::mock('event')
- ->shouldReceive('fire')
- ->with($name, m::any())
- ->once();
+ ->shouldReceive('dispatch')
+ ->with($name, m::any())
+ ->once();
$event = $default ? $event->with($name, m::any()) : $event->with($name);
diff --git a/tests/Server/PidManagerTest.php b/tests/Server/PidManagerTest.php
new file mode 100644
index 00000000..2977055c
--- /dev/null
+++ b/tests/Server/PidManagerTest.php
@@ -0,0 +1,97 @@
+assertEquals($pidFile, $pidManager->file());
+
+ $pidManager->setPidFile($pidFile = 'laravel-swoole');
+ $this->assertEquals($pidFile, $pidManager->file());
+ }
+
+ public function testWrite()
+ {
+ $pidManager = new PidManager($pidFile = 'foo/bar');
+
+ $this->mockMethod('is_writable', function () {
+ return true;
+ });
+
+ $inputFilePath = '';
+ $inputFileContent = '';
+ $this->mockMethod('file_put_contents', function ($pidPath, $pidContent) use (&$inputFilePath, &$inputFileContent) {
+ $inputFilePath = $pidPath;
+ $inputFileContent = $pidContent;
+ });
+
+ $pidManager->write($masterPid = 1, $managerPid = 2);
+
+ $this->assertEquals($pidFile, $inputFilePath);
+ $this->assertEquals('1,2', $inputFileContent);
+ }
+
+ public function testWriteWithException()
+ {
+ $pidManager = new PidManager($pidFile = 'foo/bar');
+
+ $this->mockMethod('is_writable', function () {
+ return false;
+ });
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Pid file "foo/bar" is not writable');
+
+ $pidManager->write($masterPid = 1, $managerPid = 2);
+ }
+
+ public function testRead()
+ {
+ $pidManager = new PidManager($pidFile = 'foo/bar');
+
+ $this->mockMethod('is_readable', function () {
+ return true;
+ });
+
+ $this->mockMethod('file_get_contents', function () {
+ return '1,2';
+ });
+
+ $this->assertEquals(
+ ['masterPid' => '1', 'managerPid' => '2'],
+ $pidManager->read()
+ );
+ }
+
+ public function testDelete()
+ {
+ $pidManager = new PidManager($pidFile = 'foo/bar');
+
+ $this->mockMethod('is_writable', function () {
+ return true;
+ });
+
+ $unlinkCalled = false;
+ $this->mockMethod('unlink', function () use (&$unlinkCalled) {
+ return $unlinkCalled = true;
+ });
+
+ $pidManager->delete();
+
+ $this->assertTrue($unlinkCalled);
+ }
+
+ protected function mockMethod($name, \Closure $function, $namespace = null)
+ {
+ parent::mockMethod($name, $function, 'SwooleTW\Http\Server');
+ }
+}
diff --git a/tests/Server/Resetters/BindRequestTest.php b/tests/Server/Resetters/BindRequestTest.php
new file mode 100644
index 00000000..68816b6f
--- /dev/null
+++ b/tests/Server/Resetters/BindRequestTest.php
@@ -0,0 +1,30 @@
+shouldReceive('getRequest')
+ ->once()
+ ->andReturn($request);
+
+ $container = new Container;
+
+ $resetter = new BindRequest;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertSame($request, $app->make('request'));
+ }
+}
diff --git a/tests/Server/Resetters/ClearInstanceTest.php b/tests/Server/Resetters/ClearInstanceTest.php
new file mode 100644
index 00000000..2b93c265
--- /dev/null
+++ b/tests/Server/Resetters/ClearInstanceTest.php
@@ -0,0 +1,29 @@
+shouldReceive('getConfig->get')
+ ->with('swoole_http.instances', [])
+ ->once()
+ ->andReturn(['foo']);
+
+ $container = new Container;
+ $container->instance('foo', m::mock('foo'));
+
+ $resetter = new ClearInstances;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertFalse($app->bound('foo'));
+ }
+}
diff --git a/tests/Server/Resetters/RebindKernelContainerTest.php b/tests/Server/Resetters/RebindKernelContainerTest.php
new file mode 100644
index 00000000..c4c66639
--- /dev/null
+++ b/tests/Server/Resetters/RebindKernelContainerTest.php
@@ -0,0 +1,31 @@
+shouldReceive('isLaravel')
+ ->once()
+ ->andReturn(true);
+
+ $kernel = m::mock(Kernel::class);
+
+ $container = new Container;
+ $container->instance(Kernel::class, $kernel);
+
+ $resetter = new RebindKernelContainer;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertSame($app, $app->make(Kernel::class)->app);
+ }
+}
diff --git a/tests/Server/Resetters/RebindRouterContainerTest.php b/tests/Server/Resetters/RebindRouterContainerTest.php
new file mode 100644
index 00000000..1f3834a6
--- /dev/null
+++ b/tests/Server/Resetters/RebindRouterContainerTest.php
@@ -0,0 +1,79 @@
+shouldReceive('isLaravel')
+ ->once()
+ ->andReturn(true);
+ $sandbox->shouldReceive('getRequest')
+ ->once()
+ ->andReturn($request);
+
+ $router = m::mock('router');
+
+ $container = new Container;
+ $container->instance('router', $router);
+
+ $route = m::mock('route');
+ $route->controller = 'controller';
+ $route->shouldReceive('setContainer')
+ ->once()
+ ->with($container);
+
+ $routes = m::mock('routes');
+ $routes->shouldReceive('match')
+ ->once()
+ ->with($request)
+ ->andReturn($route);
+
+ $router->routes = $routes;
+
+ $resetter = new RebindRouterContainer;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertSame($app, $router->container);
+ }
+
+ public function testRebindLumenRouterContainer()
+ {
+ $sandbox = m::mock(Sandbox::class);
+ $sandbox->shouldReceive('isLaravel')
+ ->once()
+ ->andReturn(false);
+
+ $router = m::mock('router');
+
+ $container = m::mock(Container::class);
+ $container->shouldReceive('offsetSet')
+ ->with('router', $router)
+ ->once()
+ ->andReturnSelf();
+ $container->shouldReceive('offsetGet')
+ ->with('router')
+ ->andReturn($router);
+ $container->router = $router;
+
+ $this->mockMethod('property_exists', function () {
+ return true;
+ }, 'SwooleTW\Http\Server\Resetters');
+
+ $resetter = new RebindRouterContainer;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertSame($app, $app->router->app);
+ }
+}
diff --git a/tests/Server/Resetters/RebindViewContainerTest.php b/tests/Server/Resetters/RebindViewContainerTest.php
new file mode 100644
index 00000000..c418ae36
--- /dev/null
+++ b/tests/Server/Resetters/RebindViewContainerTest.php
@@ -0,0 +1,27 @@
+instance('view', $view);
+
+ $resetter = new RebindViewContainer;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertSame($app, $app->make('view')->container);
+ $this->assertSame($app, $app->make('view')->shared['app']);
+ }
+}
diff --git a/tests/Server/Resetters/ResetConfigTest.php b/tests/Server/Resetters/ResetConfigTest.php
new file mode 100644
index 00000000..cb74ffe0
--- /dev/null
+++ b/tests/Server/Resetters/ResetConfigTest.php
@@ -0,0 +1,28 @@
+shouldReceive('getConfig')
+ ->once()
+ ->andReturn($config);
+
+ $container = new Container;
+ $resetter = new ResetConfig;
+ $app = $resetter->handle($container, $sandbox);
+
+ $this->assertSame(get_class($config), get_class($app->make('config')));
+ }
+}
diff --git a/tests/Server/Resetters/ResetCookieTest.php b/tests/Server/Resetters/ResetCookieTest.php
new file mode 100644
index 00000000..1bec5ff3
--- /dev/null
+++ b/tests/Server/Resetters/ResetCookieTest.php
@@ -0,0 +1,37 @@
+shouldReceive('getName')
+ ->once()
+ ->andReturn('foo');
+
+ $cookies = m::mock('cookies');
+ $cookies->shouldReceive('getQueuedCookies')
+ ->once()
+ ->andReturn([$cookie]);
+ $cookies->shouldReceive('unqueue')
+ ->once()
+ ->with('foo');
+
+ $sandbox = m::mock(Sandbox::class);
+
+ $container = new Container;
+ $container->instance('cookie', $cookies);
+
+ $resetter = new ResetCookie;
+ $app = $resetter->handle($container, $sandbox);
+ }
+}
diff --git a/tests/Server/Resetters/ResetProvidersTest.php b/tests/Server/Resetters/ResetProvidersTest.php
new file mode 100644
index 00000000..dcd2bdcc
--- /dev/null
+++ b/tests/Server/Resetters/ResetProvidersTest.php
@@ -0,0 +1,54 @@
+shouldReceive('register')
+ ->once();
+ $provider->shouldReceive('boot')
+ ->once();
+
+ $sandbox = m::mock(Sandbox::class);
+ $sandbox->shouldReceive('getProviders')
+ ->once()
+ ->andReturn([$provider]);
+
+ $this->mockMethod('method_exists', function () {
+ return true;
+ }, 'SwooleTW\Http\Server\Resetters');
+
+ $container = new Container;
+ $resetter = new ResetProviders;
+ $app = $resetter->handle($container, $sandbox);
+
+ $reflector = new \ReflectionProperty(TestProvider::class, 'app');
+ $reflector->setAccessible(true);
+
+ $this->assertSame($app, $reflector->getValue($provider));
+ }
+}
+
+class TestProvider extends ServiceProvider
+{
+ public function register()
+ {
+ //
+ }
+
+ public function boot()
+ {
+ //
+ }
+}
+
diff --git a/tests/Server/Resetters/ResetSessionTest.php b/tests/Server/Resetters/ResetSessionTest.php
new file mode 100644
index 00000000..178be0f4
--- /dev/null
+++ b/tests/Server/Resetters/ResetSessionTest.php
@@ -0,0 +1,29 @@
+shouldReceive('flush')
+ ->once();
+ $session->shouldReceive('regenerate')
+ ->once();
+
+ $sandbox = m::mock(Sandbox::class);
+
+ $container = new Container;
+ $container->instance('session', $session);
+
+ $resetter = new ResetSession;
+ $app = $resetter->handle($container, $sandbox);
+ }
+}
diff --git a/tests/Server/ResettersTest.php b/tests/Server/ResettersTest.php
deleted file mode 100644
index ff416dc2..00000000
--- a/tests/Server/ResettersTest.php
+++ /dev/null
@@ -1,239 +0,0 @@
-shouldReceive('getRequest')
- ->once()
- ->andReturn($request);
-
- $container = new Container;
-
- $resetter = new BindRequest;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertSame($request, $app->make('request'));
- }
-
- public function testClearInstance()
- {
- $sandbox = m::mock(Sandbox::class);
- $sandbox->shouldReceive('getConfig->get')
- ->with('swoole_http.instances', [])
- ->once()
- ->andReturn(['foo']);
-
- $container = new Container;
- $container->instance('foo', m::mock('foo'));
-
- $resetter = new ClearInstances;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertFalse($app->bound('foo'));
- }
-
- public function testRebindKernelContainer()
- {
- $sandbox = m::mock(Sandbox::class);
- $sandbox->shouldReceive('isLaravel')
- ->once()
- ->andReturn(true);
-
- $kernel = m::mock(Kernel::class);
-
- $container = new Container;
- $container->instance(Kernel::class, $kernel);
-
- $resetter = new RebindKernelContainer;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertSame($app, $app->make(Kernel::class)->app);
- }
-
- public function testRebindLaravelRouterContainer()
- {
- $request = m::mock(Request::class);
-
- $sandbox = m::mock(Sandbox::class);
- $sandbox->shouldReceive('isLaravel')
- ->once()
- ->andReturn(true);
- $sandbox->shouldReceive('getRequest')
- ->once()
- ->andReturn($request);
-
- $router = m::mock('router');
-
- $container = new Container;
- $container->instance('router', $router);
-
- $route = m::mock('route');
- $route->controller = 'controller';
- $route->shouldReceive('setContainer')
- ->once()
- ->with($container);
-
- $routes = m::mock('routes');
- $routes->shouldReceive('match')
- ->once()
- ->with($request)
- ->andReturn($route);
-
- $router->routes = $routes;
-
- $resetter = new RebindRouterContainer;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertSame($app, $router->container);
- }
-
- public function testRebindLumenRouterContainer()
- {
- $sandbox = m::mock(Sandbox::class);
- $sandbox->shouldReceive('isLaravel')
- ->once()
- ->andReturn(false);
-
- $router = m::mock('router');
-
- $container = m::mock(Container::class);
- $container->shouldReceive('offsetSet')
- ->with('router', $router)
- ->once()
- ->andReturnSelf();
- $container->shouldReceive('offsetGet')
- ->with('router')
- ->andReturn($router);
- $container->router = $router;
-
- $this->mockMethod('property_exists', function () {
- return true;
- }, 'SwooleTW\Http\Server\Resetters');
-
- $resetter = new RebindRouterContainer;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertSame($app, $app->router->app);
- }
-
- public function testRebindViewContainer()
- {
- $sandbox = m::mock(Sandbox::class);
- $view = m::mock('view');
-
- $container = new Container;
- $container->instance('view', $view);
-
- $resetter = new RebindViewContainer;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertSame($app, $app->make('view')->container);
- $this->assertSame($app, $app->make('view')->shared['app']);
- }
-
- public function testResetConfig()
- {
- $config = m::mock('config');
- $sandbox = m::mock(Sandbox::class);
- $sandbox->shouldReceive('getConfig')
- ->once()
- ->andReturn($config);
-
- $container = new Container;
- $resetter = new ResetConfig;
- $app = $resetter->handle($container, $sandbox);
-
- $this->assertSame(get_class($config), get_class($app->make('config')));
- }
-
- public function testResetCookie()
- {
- $cookies = m::mock('cookies');
- $cookies->shouldReceive('getQueuedCookies')
- ->once()
- ->andReturn(['foo']);
- $cookies->shouldReceive('unqueue')
- ->once()
- ->with('foo');
-
- $sandbox = m::mock(Sandbox::class);
-
- $container = new Container;
- $container->instance('cookie', $cookies);
-
- $resetter = new ResetCookie;
- $app = $resetter->handle($container, $sandbox);
- }
-
- public function testResetSession()
- {
- $session = m::mock('session');
- $session->shouldReceive('flush')
- ->once();
- $session->shouldReceive('regenerate')
- ->once();
-
- $sandbox = m::mock(Sandbox::class);
-
- $container = new Container;
- $container->instance('session', $session);
-
- $resetter = new ResetSession;
- $app = $resetter->handle($container, $sandbox);
- }
-
- public function testResetProviders()
- {
- $provider = m::mock(TestProvider::class);
- $provider->shouldReceive('register')
- ->once();
- $provider->shouldReceive('boot')
- ->once();
-
- $sandbox = m::mock(Sandbox::class);
- $sandbox->shouldReceive('getProviders')
- ->once()
- ->andReturn([$provider]);
-
- $this->mockMethod('method_exists', function () {
- return true;
- }, 'SwooleTW\Http\Server\Resetters');
-
- $container = new Container;
- $resetter = new ResetProviders;
- $app = $resetter->handle($container, $sandbox);
-
- $reflector = new \ReflectionProperty(TestProvider::class, 'app');
- $reflector->setAccessible(true);
-
- $this->assertSame($app, $reflector->getValue($provider));
- }
-}
-
-class TestProvider extends ServiceProvider {
- public function register() {}
- public function boot() {}
-}
diff --git a/tests/Server/SandboxTest.php b/tests/Server/SandboxTest.php
index 9f04a1e6..876d3bc9 100644
--- a/tests/Server/SandboxTest.php
+++ b/tests/Server/SandboxTest.php
@@ -2,21 +2,17 @@
namespace SwooleTW\Http\Tests\Server;
-use Mockery as m;
-use RuntimeException;
-use Swoole\Coroutine;
-use ReflectionProperty;
-use Illuminate\Http\Request;
use Illuminate\Config\Repository;
-use SwooleTW\Http\Server\Sandbox;
-use SwooleTW\Http\Tests\TestCase;
use Illuminate\Container\Container;
+use Illuminate\Contracts\Container\Container as ContainerContract;
+use Illuminate\Http\Request;
+use Mockery as m;
use SwooleTW\Http\Coroutine\Context;
-use SwooleTW\Http\Server\Application;
-use Illuminate\Support\Facades\Facade;
use SwooleTW\Http\Exceptions\SandboxException;
+use SwooleTW\Http\Server\Application;
use SwooleTW\Http\Server\Resetters\ResetterContract;
-use Illuminate\Contracts\Container\Container as ContainerContract;
+use SwooleTW\Http\Server\Sandbox;
+use SwooleTW\Http\Tests\TestCase;
class SandboxTest extends TestCase
{
@@ -56,23 +52,23 @@ public function testInitialize()
$config = m::mock(Repository::class);
$config->shouldReceive('get')
- ->with('swoole_http.providers', [])
- ->once()
- ->andReturn([$providerName]);
+ ->with('swoole_http.providers', [])
+ ->once()
+ ->andReturn([$providerName]);
$config->shouldReceive('get')
- ->with('swoole_http.resetters', [])
- ->once()
- ->andReturn([$resetterName]);
+ ->with('swoole_http.resetters', [])
+ ->once()
+ ->andReturn([$resetterName]);
$container = m::mock(Container::class);
$container->shouldReceive('make')
- ->with('config')
- ->once()
- ->andReturn($config);
+ ->with(\Illuminate\Contracts\Config\Repository::class)
+ ->once()
+ ->andReturn($config);
$container->shouldReceive('make')
- ->with($resetterName)
- ->once()
- ->andReturn($resetter);
+ ->with($resetterName)
+ ->once()
+ ->andReturn($resetter);
$sandbox = new Sandbox;
$sandbox->setBaseApp($container);
@@ -99,6 +95,31 @@ public function testGetApplication()
$this->assertTrue($sandbox->getSnapshot() instanceof Container);
}
+ public function testGetCachedSnapshot()
+ {
+ $container = m::mock(Container::class);
+ $snapshot = m::mock(Container::class);
+ $snapshot->shouldReceive('offsetGet')
+ ->with('foo')
+ ->once()
+ ->andReturn($result = 'bar');
+
+ $sandbox = new Sandbox;
+ $sandbox->setBaseApp($container);
+ $sandbox->setSnapshot($snapshot);
+
+ $this->assertTrue($sandbox->getApplication() instanceof Container);
+ $this->assertEquals($result, $sandbox->getApplication()->foo);
+ }
+
+ public function testRunWithoutSnapshot()
+ {
+ $this->expectException(SandboxException::class);
+
+ $sandbox = new Sandbox;
+ $sandbox->run($request = m::mock(Request::class));
+ }
+
public function testSetRequest()
{
$request = m::mock(Request::class);
@@ -132,18 +153,19 @@ protected function getContainer()
{
$config = m::mock(Illuminate\Config\Repository::class);
$config->shouldReceive('get')
- ->andReturn([]);
+ ->andReturn([]);
$container = m::mock(Container::class);
$container->shouldReceive('offsetGet')
- ->andReturn((object)[]);
+ ->andReturn((object) []);
$container->shouldReceive('make')
- ->andReturn($config);
+ ->andReturn($config);
return $container;
}
}
-class TestResetter implements ResetterContract {
+class TestResetter implements ResetterContract
+{
public function handle(ContainerContract $app, Sandbox $sandbox)
{
return 'foo';
diff --git a/tests/SocketIO/PacketTest.php b/tests/SocketIO/PacketTest.php
index 484b2d9b..b46b1013 100644
--- a/tests/SocketIO/PacketTest.php
+++ b/tests/SocketIO/PacketTest.php
@@ -33,35 +33,35 @@ public function testGetPayload()
$packet = '42["foo","bar"]';
$this->assertSame([
'event' => 'foo',
- 'data' => 'bar'
+ 'data' => 'bar',
], Packet::getPayload($packet));
$packet = '42["foo", "bar"]';
$this->assertSame([
'event' => 'foo',
- 'data' => 'bar'
+ 'data' => 'bar',
], Packet::getPayload($packet));
$packet = '42["foo",{"message":"test"}]';
$this->assertSame([
'event' => 'foo',
'data' => [
- 'message' => 'test'
- ]
+ 'message' => 'test',
+ ],
], Packet::getPayload($packet));
$packet = '42["foo", {"message":"test"}]';
$this->assertSame([
'event' => 'foo',
'data' => [
- 'message' => 'test'
- ]
+ 'message' => 'test',
+ ],
], Packet::getPayload($packet));
$packet = '42["foo"]';
$this->assertSame([
'event' => 'foo',
- 'data' => null
+ 'data' => null,
], Packet::getPayload($packet));
}
diff --git a/tests/SocketIO/ParserTest.php b/tests/SocketIO/ParserTest.php
index ff8c613f..9b952a99 100644
--- a/tests/SocketIO/ParserTest.php
+++ b/tests/SocketIO/ParserTest.php
@@ -2,10 +2,10 @@
namespace SwooleTW\Http\Tests\SocketIO;
+use Illuminate\Support\Facades\App;
use Mockery as m;
use Swoole\Websocket\Frame;
use SwooleTW\Http\Tests\TestCase;
-use Illuminate\Support\Facades\App;
use SwooleTW\Http\Websocket\SocketIO\SocketIOParser;
use SwooleTW\Http\Websocket\SocketIO\Strategies\HeartbeatStrategy;
@@ -38,15 +38,15 @@ public function testDecode()
$this->assertSame([
'event' => 'foo',
'data' => [
- 'message' => 'test'
- ]
+ 'message' => 'test',
+ ],
], $parser->decode($frame));
$payload = '42["foo","bar"]';
$frame->data = $payload;
$this->assertSame([
'event' => 'foo',
- 'data' => 'bar'
+ 'data' => 'bar',
], $parser->decode($frame));
}
diff --git a/tests/SocketIO/SocketIOControllerTest.php b/tests/SocketIO/SocketIOControllerTest.php
index a805a767..e445e3db 100644
--- a/tests/SocketIO/SocketIOControllerTest.php
+++ b/tests/SocketIO/SocketIOControllerTest.php
@@ -2,11 +2,11 @@
namespace SwooleTW\Http\Tests\SocketIO;
-use Mockery as m;
use Illuminate\Http\Request;
-use SwooleTW\Http\Tests\TestCase;
use Illuminate\Support\Facades\Config;
+use Mockery as m;
use SwooleTW\Http\Controllers\SocketIOController;
+use SwooleTW\Http\Tests\TestCase;
class SocketIOControllerTest extends TestCase
{
@@ -14,18 +14,19 @@ public function testUnknownTransport()
{
$request = m::mock(Request::class);
$request->shouldReceive('input')
- ->with('transport')
- ->once()
- ->andReturn('foo');
+ ->with('transport')
+ ->once()
+ ->andReturn('foo');
$this->mockMethod('response', function () {
$response = m::mock('response');
$response->shouldReceive('json')
- ->once()
- ->with([
- 'code' => 0,
- 'message' => 'Transport unknown'
- ], 400);
+ ->once()
+ ->with([
+ 'code' => 0,
+ 'message' => 'Transport unknown',
+ ], 400);
+
return $response;
});
@@ -37,13 +38,13 @@ public function testHasSid()
{
$request = m::mock(Request::class);
$request->shouldReceive('input')
- ->with('transport')
- ->once()
- ->andReturn('websocket');
+ ->with('transport')
+ ->once()
+ ->andReturn('websocket');
$request->shouldReceive('has')
- ->with('sid')
- ->once()
- ->andReturn(true);
+ ->with('sid')
+ ->once()
+ ->andReturn(true);
$controller = new SocketIOController;
$result = $controller->upgrade($request);
@@ -55,34 +56,35 @@ public function testUpgradePayload()
{
$request = m::mock(Request::class);
$request->shouldReceive('input')
- ->with('transport')
- ->once()
- ->andReturn('websocket');
+ ->with('transport')
+ ->once()
+ ->andReturn('websocket');
$request->shouldReceive('has')
- ->with('sid')
- ->once()
- ->andReturn(false);
+ ->with('sid')
+ ->once()
+ ->andReturn(false);
$base64Encode = false;
$this->mockMethod('base64_encode', function () use (&$base64Encode) {
$base64Encode = true;
+
return 'payload';
});
Config::shouldReceive('get')
- ->with('swoole_websocket.ping_interval')
- ->once()
- ->andReturn(1);
+ ->with('swoole_websocket.ping_interval')
+ ->once()
+ ->andReturn(1);
Config::shouldReceive('get')
- ->with('swoole_websocket.ping_timeout')
- ->once()
- ->andReturn(1);
+ ->with('swoole_websocket.ping_timeout')
+ ->once()
+ ->andReturn(1);
$expectedPayload = json_encode([
'sid' => 'payload',
'upgrades' => ['websocket'],
'pingInterval' => 1,
- 'pingTimeout' => 1
+ 'pingTimeout' => 1,
]);
$expectedPayload = '97:0' . $expectedPayload . '2:40';
@@ -95,21 +97,20 @@ public function testUpgradePayload()
public function testReject()
{
- $request = m::mock(Request::class);
-
$this->mockMethod('response', function () {
$response = m::mock('response');
$response->shouldReceive('json')
- ->once()
- ->with([
- 'code' => 3,
- 'message' => 'Bad request'
- ], 400);
+ ->once()
+ ->with([
+ 'code' => 3,
+ 'message' => 'Bad request',
+ ], 400);
+
return $response;
});
$controller = new SocketIOController;
- $controller->reject($request);
+ $controller->reject();
}
protected function mockMethod($name, \Closure $function, $namespace = null)
diff --git a/tests/SocketIO/WebsocketHandlerTest.php b/tests/SocketIO/WebsocketHandlerTest.php
index 09575f6f..6db1e612 100644
--- a/tests/SocketIO/WebsocketHandlerTest.php
+++ b/tests/SocketIO/WebsocketHandlerTest.php
@@ -2,12 +2,13 @@
namespace SwooleTW\Http\Tests\SocketIO;
-use Mockery as m;
-use Swoole\Websocket\Frame;
use Illuminate\Http\Request;
-use SwooleTW\Http\Tests\TestCase;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
+use Mockery as m;
+use Swoole\Websocket\Frame;
+use SwooleTW\Http\Server\Facades\Server;
+use SwooleTW\Http\Tests\TestCase;
use SwooleTW\Http\Websocket\SocketIO\WebsocketHandler;
class WebsocketHandlerTest extends TestCase
@@ -17,33 +18,34 @@ public function testOnOpen()
$fd = 1;
$request = m::mock(Request::class);
$request->shouldReceive('input')
- ->with('sid')
- ->once()
- ->andReturn(false);
+ ->with('sid')
+ ->once()
+ ->andReturn(false);
Config::shouldReceive('get')
- ->with('swoole_websocket.ping_interval')
- ->once();
+ ->with('swoole_websocket.ping_interval')
+ ->once();
Config::shouldReceive('get')
- ->with('swoole_websocket.ping_timeout')
- ->once();
+ ->with('swoole_websocket.ping_timeout')
+ ->once();
$jsonEncode = false;
$this->mockMethod('json_encode', function () use (&$jsonEncode) {
$jsonEncode = true;
+
return '{foo: "bar"}';
}, 'SwooleTW\Http\Websocket\SocketIO');
App::shouldReceive('make')
- ->with('swoole.server')
- ->twice()
- ->andReturnSelf();
+ ->with(Server::class)
+ ->twice()
+ ->andReturnSelf();
App::shouldReceive('push')
- ->with($fd, '0{foo: "bar"}')
- ->once();
+ ->with($fd, '0{foo: "bar"}')
+ ->once();
App::shouldReceive('push')
- ->with($fd, '40')
- ->once();
+ ->with($fd, '40')
+ ->once();
$handler = new WebsocketHandler;
$this->assertTrue($handler->onOpen($fd, $request));
@@ -55,9 +57,9 @@ public function testOnOpenWithFalseReturn()
$fd = 1;
$request = m::mock(Request::class);
$request->shouldReceive('input')
- ->with('sid')
- ->once()
- ->andReturn(true);
+ ->with('sid')
+ ->once()
+ ->andReturn(true);
$handler = new WebsocketHandler;
$this->assertFalse($handler->onOpen($fd, $request));
diff --git a/tests/Table/TableTest.php b/tests/Table/TableTest.php
index 35efc296..fa32dbed 100644
--- a/tests/Table/TableTest.php
+++ b/tests/Table/TableTest.php
@@ -4,8 +4,8 @@
use Mockery as m;
use Swoole\Table;
-use SwooleTW\Http\Tests\TestCase;
use SwooleTW\Http\Table\SwooleTable;
+use SwooleTW\Http\Tests\TestCase;
class TableTest extends TestCase
{
diff --git a/tests/Task/FakeJob.php b/tests/Task/FakeJob.php
index 49b6ab5f..272b658b 100644
--- a/tests/Task/FakeJob.php
+++ b/tests/Task/FakeJob.php
@@ -3,9 +3,9 @@
namespace SwooleTW\Http\Tests\Task;
use Illuminate\Bus\Queueable;
-use Illuminate\Queue\SerializesModels;
-use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
class FakeJob implements ShouldQueue
{
diff --git a/tests/Task/QueueFactoryTest.php b/tests/Task/QueueFactoryTest.php
new file mode 100644
index 00000000..66cae40b
--- /dev/null
+++ b/tests/Task/QueueFactoryTest.php
@@ -0,0 +1,62 @@
+mockEnv('Laravel\Lumen');
+ }
+
+ public function testItHasNeededStubByVersion()
+ {
+ $version = FW::version();
+
+ $search = version_compare($version, '5.7', '>=') ? '5.7' : '5.6';
+ $stub = QueueFactory::stub($version);
+
+ $this->assertTrue(Str::contains($stub, $search));
+ }
+
+ public function testItCanCompareNeededStubByVersion()
+ {
+ $version = '5.6';
+ $search = '5.7';
+
+ $stub = QueueFactory::stub($version);
+
+ $this->assertNotTrue(Str::contains($stub, $search));
+ }
+
+ public function testItCanMakeNeededQueueByVersion()
+ {
+ $version = FW::version();
+
+ $server = $this->getServer();
+ $queue = QueueFactory::make($server, $version);
+
+ $this->assertInstanceOf(QueueFactory::QUEUE_CLASS, $queue);
+ }
+
+ protected function getServer()
+ {
+ $server = m::mock('server');
+ $server->taskworker = false;
+ $server->master_pid = -1;
+
+ return $server;
+ }
+}
\ No newline at end of file
diff --git a/tests/Task/SwooleJobTest.php b/tests/Task/SwooleJobTest.php
index 7171e1ff..2f2ffaca 100644
--- a/tests/Task/SwooleJobTest.php
+++ b/tests/Task/SwooleJobTest.php
@@ -2,10 +2,10 @@
namespace SwooleTW\Http\Tests\Task;
-use Mockery as m;
-use SwooleTW\Http\Tests\TestCase;
use Illuminate\Container\Container;
+use Mockery as m;
use SwooleTW\Http\Task\SwooleTaskJob;
+use SwooleTW\Http\Tests\TestCase;
class SwooleJobTest extends TestCase
{
diff --git a/tests/Task/SwooleQueueTest.php b/tests/Task/SwooleQueueTest.php
index fecbc734..cbff7344 100644
--- a/tests/Task/SwooleQueueTest.php
+++ b/tests/Task/SwooleQueueTest.php
@@ -3,16 +3,16 @@
namespace SwooleTW\Http\Tests\Task;
use Mockery as m;
+use SwooleTW\Http\Helpers\FW;
+use SwooleTW\Http\Task\QueueFactory;
use SwooleTW\Http\Tests\TestCase;
-use SwooleTW\Http\Task\SwooleTaskQueue;
class SwooleQueueTest extends TestCase
{
public function testPushProperlyPushesJobOntoSwoole()
{
$server = $this->getServer();
-
- $queue = new SwooleTaskQueue($server);
+ $queue = QueueFactory::make($server, FW::version());
$server->shouldReceive('task')->once();
$queue->push(new FakeJob);
}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 9d2ff71a..28f32301 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -2,12 +2,12 @@
namespace SwooleTW\Http\Tests;
+use Illuminate\Support\Facades\Facade;
use Mockery as m;
use phpmock\Mock;
use phpmock\MockBuilder;
-use SwooleTW\Http\Coroutine\Context;
-use Illuminate\Support\Facades\Facade;
use PHPUnit\Framework\TestCase as BaseTestCase;
+use SwooleTW\Http\Coroutine\Context;
class TestCase extends BaseTestCase
{
@@ -26,7 +26,7 @@ public function tearDown()
protected function mockMethod($name, \Closure $function, $namespace = null)
{
- $builder = new MockBuilder();
+ $builder = new MockBuilder;
$builder->setNamespace($namespace)
->setName($name)
->setFunction($function);
@@ -34,4 +34,15 @@ protected function mockMethod($name, \Closure $function, $namespace = null)
$mock = $builder->build();
$mock->enable();
}
+
+ protected function mockEnv(string $namespace, array $variables = [])
+ {
+ $this->mockMethod('env', function ($key, $value = null) use ($variables) {
+ if (array_key_exists($key, $variables)) {
+ return $variables[$key];
+ }
+
+ return null;
+ }, $namespace);
+ }
}
diff --git a/tests/Transformers/RequestTest.php b/tests/Transformers/RequestTest.php
index 226ffa40..82adf818 100644
--- a/tests/Transformers/RequestTest.php
+++ b/tests/Transformers/RequestTest.php
@@ -2,11 +2,11 @@
namespace SwooleTW\Http\Tests\Transformers;
+use Illuminate\Http\Request as IlluminateRequest;
use Mockery as m;
+use Swoole\Http\Request as SwooleRequest;
use SwooleTW\Http\Tests\TestCase;
use SwooleTW\Http\Transformers\Request;
-use Swoole\Http\Request as SwooleRequest;
-use Illuminate\Http\Request as IlluminateRequest;
class RequestTest extends TestCase
{
@@ -34,25 +34,24 @@ public function testHandleStatic()
$fileSize = false;
$this->mockMethod('filesize', function () use (&$fileSize) {
$fileSize = true;
+
return 1;
});
- $mimeContentType = false;
- $this->mockMethod('mime_content_type', function () use (&$mimeContentType) {
- $mimeContentType = true;
- return 'foo';
+ $this->mockMethod('pathinfo', function () {
+ return 'css?id=bfaf14972de9d89ae8fc';
});
$response = m::mock('response');
$response->shouldReceive('status')
- ->with(200)
- ->once();
+ ->with(200)
+ ->once();
$response->shouldReceive('header')
- ->with('Content-Type', 'foo')
- ->once();
+ ->with('Content-Type', 'text/css')
+ ->once();
$response->shouldReceive('sendfile')
- ->with('/foo.bar')
- ->once();
+ ->with('/foo.bar')
+ ->once();
Request::handleStatic(new SwooleRequestStub, $response, '/');
@@ -66,7 +65,7 @@ public function testHandleStaticWithBlackList()
$request->server['request_uri'] = 'foo.php';
$result = Request::handleStatic($request, null, '/');
- $this->assertNull($result);
+ $this->assertFalse($result);
}
public function testHandleStaticWithNoneFile()
@@ -74,11 +73,12 @@ public function testHandleStaticWithNoneFile()
$isFile = false;
$this->mockMethod('is_file', function () use (&$isFile) {
$isFile = true;
+
return false;
});
$result = Request::handleStatic(new SwooleRequestStub, null, '/');
- $this->assertNull($result);
+ $this->assertFalse($result);
$this->assertTrue($isFile);
}
@@ -91,17 +91,23 @@ protected function mockMethod($name, \Closure $function, $namespace = null)
class SwooleRequestStub extends SwooleRequest
{
public $get = [];
+
public $post = [];
+
public $header = ['foo' => 'bar'];
+
public $server = [
'HTTP_CONTENT_LENGTH' => 0,
'CONTENT_LENGTH' => 0,
'HTTP_CONTENT_TYPE' => null,
'CONTENT_TYPE' => null,
- 'request_uri' => 'foo.bar'
+ 'request_uri' => 'foo.bar',
];
+
public $cookie = [];
+
public $files = [];
+
public $fd = 1;
function rawContent()
diff --git a/tests/Transformers/ResponseTest.php b/tests/Transformers/ResponseTest.php
index d25084e5..41df5999 100644
--- a/tests/Transformers/ResponseTest.php
+++ b/tests/Transformers/ResponseTest.php
@@ -2,10 +2,10 @@
namespace SwooleTW\Http\Tests\Transformers;
+use Illuminate\Http\Response as IlluminateResponse;
+use Swoole\Http\Response as SwooleResponse;
use SwooleTW\Http\Tests\TestCase;
use SwooleTW\Http\Transformers\Response;
-use Swoole\Http\Response as SwooleResponse;
-use Illuminate\Http\Response as IlluminateResponse;
class ResponseTest extends TestCase
{
diff --git a/tests/Websocket/HandShakeHandlerTest.php b/tests/Websocket/HandShakeHandlerTest.php
new file mode 100644
index 00000000..abb5956c
--- /dev/null
+++ b/tests/Websocket/HandShakeHandlerTest.php
@@ -0,0 +1,71 @@
+header['sec-websocket-key'] = 'Bet8DkPFq9ZxvIBvPcNy1A==';
+
+ $response = m::mock(Response::class);
+ $response->shouldReceive('header')->withAnyArgs()->times(4)->andReturnSelf();
+ $response->shouldReceive('status')->with(101)->once()->andReturnSelf();
+ $response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
+
+ $handler = new HandShakeHandler;
+
+ // act
+ $actual = $handler->handle($request, $response);
+
+ // assert
+ $this->assertTrue($actual);
+ }
+
+ public function testHandleReturnFalse()
+ {
+ // arrange
+ $request = m::mock(Request::class);
+ $request->header['sec-websocket-key'] = 'test';
+
+ $response = m::mock(Response::class);
+ $response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
+
+ $handler = new HandShakeHandler;
+
+ // act
+ $actual = $handler->handle($request, $response);
+
+ // assert
+ $this->assertFalse($actual);
+ }
+
+ public function testHandleWithSecWebsocketProtocol()
+ {
+ // arrange
+ $request = m::mock(Request::class);
+ $request->header['sec-websocket-key'] = 'Bet8DkPFq9ZxvIBvPcNy1A==';
+ $request->header['sec-websocket-protocol'] = 'graphql-ws';
+
+ $response = m::mock(Response::class);
+ $response->shouldReceive('header')->withAnyArgs()->times(5)->andReturnSelf();
+ $response->shouldReceive('status')->with(101)->once()->andReturnSelf();
+ $response->shouldReceive('end')->withAnyArgs()->once()->andReturnSelf();
+
+ $handler = new HandShakeHandler;
+
+ // act
+ $actual = $handler->handle($request, $response);
+
+ // assert
+ $this->assertTrue($actual);
+ }
+}
diff --git a/tests/Websocket/Middleware/AuthenticateTest.php b/tests/Websocket/Middleware/AuthenticateTest.php
index 95abf43a..d7dfa5be 100644
--- a/tests/Websocket/Middleware/AuthenticateTest.php
+++ b/tests/Websocket/Middleware/AuthenticateTest.php
@@ -2,23 +2,26 @@
namespace SwooleTW\Http\Tests\Websocket\Middleware;
-use Mockery as m;
+use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Http\Request;
+use Mockery as m;
use SwooleTW\Http\Tests\TestCase;
-use Illuminate\Contracts\Auth\Factory as Auth;
use SwooleTW\Http\Websocket\Middleware\Authenticate;
class AuthenticateTest extends TestCase
{
public function testAuthenticate()
{
- $auth = m::mock(Auth::class);
- $auth->shouldReceive('authenticate')
- ->once()
- ->andReturn('user');
-
$request = m::mock(Request::class);
$request->shouldReceive('setUserResolver')
+ ->once();
+
+ $auth = m::mock(Auth::class);
+ $auth->shouldReceive('authenticate')
+ ->once()
+ ->andReturn('user');
+ $auth->shouldReceive('setRequest')
+ ->with($request)
->once();
$middleware = new Authenticate($auth);
diff --git a/tests/Websocket/Middleware/DecrpytCookiesTest.php b/tests/Websocket/Middleware/DecrpytCookiesTest.php
index e9f8913d..8c0347b0 100644
--- a/tests/Websocket/Middleware/DecrpytCookiesTest.php
+++ b/tests/Websocket/Middleware/DecrpytCookiesTest.php
@@ -2,12 +2,12 @@
namespace SwooleTW\Http\Tests\Websocket\Middleware;
-use Mockery as m;
+use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
use Illuminate\Http\Request;
+use Mockery as m;
use SwooleTW\Http\Tests\TestCase;
-use Symfony\Component\HttpFoundation\ParameterBag;
use SwooleTW\Http\Websocket\Middleware\DecryptCookies;
-use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
+use Symfony\Component\HttpFoundation\ParameterBag;
class DecrpytCookiesTest extends TestCase
{
@@ -27,17 +27,17 @@ public function testDecrypt()
'foo' => 'bar',
'seafood' => 'sasaya',
'aaa' => [
- 'bbb' => 'ccc'
- ]
+ 'bbb' => 'ccc',
+ ],
]);
$encrypter = m::mock(EncrypterContract::class);
$encrypter->shouldReceive('decrypt')
- ->once()
- ->with('sasaya', false);
+ ->once()
+ ->with('sasaya', false);
$encrypter->shouldReceive('decrypt')
- ->once()
- ->with('ccc', false);
+ ->once()
+ ->with('ccc', false);
$middleware = $this->getMiddleware($encrypter);
$middleware->disableFor('foo');
diff --git a/tests/Websocket/Middleware/StartSessionTest.php b/tests/Websocket/Middleware/StartSessionTest.php
index ac95c708..0ab4c8e4 100644
--- a/tests/Websocket/Middleware/StartSessionTest.php
+++ b/tests/Websocket/Middleware/StartSessionTest.php
@@ -2,11 +2,7 @@
namespace SwooleTW\Http\Tests\Websocket\Middleware;
-use Mockery as m;
-use Illuminate\Http\Request;
use SwooleTW\Http\Tests\TestCase;
-use Illuminate\Session\SessionManager;
-use SwooleTW\Http\Websocket\Middleware\StartSession;
class StartSessionTest extends TestCase
{
diff --git a/tests/Websocket/PusherTest.php b/tests/Websocket/PusherTest.php
new file mode 100644
index 00000000..1913592c
--- /dev/null
+++ b/tests/Websocket/PusherTest.php
@@ -0,0 +1,149 @@
+ 1,
+ 'sender' => 3,
+ 'fds' => [1, 2],
+ 'broadcast' => true,
+ 'assigned' => true,
+ 'event' => 'event',
+ 'message' => 'message'
+ ];
+ $pusher = Pusher::make($data, null);
+
+ $this->assertInstanceOf(Pusher::class, $pusher);
+ $this->assertSame($data['opcode'], $pusher->getOpCode());
+ $this->assertSame($data['sender'], $pusher->getSender());
+ $this->assertSame($data['fds'], $pusher->getDescriptors());
+ $this->assertSame($data['opcode'], $pusher->getOpCode());
+ $this->assertSame($data['broadcast'], $pusher->isBroadCast());
+ $this->assertSame($data['assigned'], $pusher->isAssigned());
+ $this->assertSame($data['event'], $pusher->getEvent());
+ $this->assertSame($data['message'], $pusher->getMessage());
+ }
+
+ public function testAddDescriptor()
+ {
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 3,
+ 'fds' => [1, 2],
+ 'broadcast' => true,
+ 'assigned' => true,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], null);
+
+ $pusher->addDescriptor(3);
+ $this->assertSame([1, 2, 3], $pusher->getDescriptors());
+
+ $pusher->addDescriptor(1);
+ $this->assertSame([1, 2, 3], $pusher->getDescriptors());
+ }
+
+ public function testAddDescriptors()
+ {
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 3,
+ 'fds' => [1, 2],
+ 'broadcast' => true,
+ 'assigned' => true,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], null);
+
+ $pusher->addDescriptors([3]);
+ $this->assertSame([1, 2, 3], $pusher->getDescriptors());
+
+ $pusher->addDescriptors([1, 4]);
+ $this->assertSame([1, 2, 3, 4], $pusher->getDescriptors());
+ }
+
+ public function testHasDescriptor()
+ {
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 3,
+ 'fds' => [1, 2],
+ 'broadcast' => true,
+ 'assigned' => true,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], null);
+
+ $this->assertTrue($pusher->hasDescriptor(1));
+ $this->assertTrue($pusher->hasDescriptor(2));
+ $this->assertFalse($pusher->hasDescriptor(3));
+ }
+
+ public function testShouldBroadcast()
+ {
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 1,
+ 'fds' => [],
+ 'broadcast' => true,
+ 'assigned' => false,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], null);
+
+ $this->assertTrue($pusher->shouldBroadcast());
+ }
+
+ public function testShouldPushToDescriptor()
+ {
+ $server = m::mock(Server::class);
+ $server->shouldReceive('isEstablished')
+ ->with($fd = 1)
+ ->times(3)
+ ->andReturn(true);
+
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 3,
+ 'fds' => [],
+ 'broadcast' => true,
+ 'assigned' => false,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], $server);
+
+ $this->assertTrue($pusher->shouldPushToDescriptor($fd));
+
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 1,
+ 'fds' => [],
+ 'broadcast' => true,
+ 'assigned' => false,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], $server);
+
+ $this->assertFalse($pusher->shouldPushToDescriptor($fd));
+
+ $pusher = Pusher::make([
+ 'opcode' => 1,
+ 'sender' => 1,
+ 'fds' => [],
+ 'broadcast' => false,
+ 'assigned' => false,
+ 'event' => 'event',
+ 'message' => 'message'
+ ], $server);
+
+ $this->assertTrue($pusher->shouldPushToDescriptor($fd));
+ }
+}
diff --git a/tests/Websocket/RedisRoomTest.php b/tests/Websocket/RedisRoomTest.php
index 8acf0c24..eeda14c2 100644
--- a/tests/Websocket/RedisRoomTest.php
+++ b/tests/Websocket/RedisRoomTest.php
@@ -3,8 +3,8 @@
namespace SwooleTW\Http\Tests\Websocket;
use Mockery as m;
-use SwooleTW\Http\Tests\TestCase;
use Predis\Client as RedisClient;
+use SwooleTW\Http\Tests\TestCase;
use SwooleTW\Http\Websocket\Rooms\RedisRoom;
class RedisRoomTest extends TestCase
@@ -13,11 +13,11 @@ public function testPrepare()
{
$redis = $this->getRedis();
$redis->shouldReceive('keys')
- ->once()
- ->andReturn($keys = ['foo', 'bar']);
+ ->once()
+ ->andReturn($keys = ['foo', 'bar']);
$redis->shouldReceive('del')
- ->with($keys)
- ->once();
+ ->with($keys)
+ ->once();
$redisRoom = new RedisRoom([]);
$redisRoom->prepare($redis);
@@ -38,7 +38,7 @@ public function testAddValue()
{
$redis = $this->getRedis();
$redis->shouldReceive('pipeline')
- ->once();
+ ->once();
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->addValue(1, ['foo', 'bar'], 'fds');
@@ -48,7 +48,7 @@ public function testAddAll()
{
$redis = $this->getRedis();
$redis->shouldReceive('pipeline')
- ->times(3);
+ ->times(3);
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->add(1, ['foo', 'bar']);
@@ -58,7 +58,7 @@ public function testAdd()
{
$redis = $this->getRedis();
$redis->shouldReceive('pipeline')
- ->twice();
+ ->twice();
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->add(1, 'foo');
@@ -68,7 +68,7 @@ public function testDeleteAll()
{
$redis = $this->getRedis();
$redis->shouldReceive('pipeline')
- ->times(3);
+ ->times(3);
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->delete(1, ['foo', 'bar']);
@@ -78,7 +78,7 @@ public function testDelete()
{
$redis = $this->getRedis();
$redis->shouldReceive('pipeline')
- ->twice();
+ ->twice();
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->delete(1, 'foo');
@@ -88,8 +88,8 @@ public function testGetRooms()
{
$redis = $this->getRedis();
$redis->shouldReceive('smembers')
- ->with('swoole:fds:1')
- ->once();
+ ->with('swoole:fds:1')
+ ->once();
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->getRooms(1);
@@ -99,8 +99,8 @@ public function testGetClients()
{
$redis = $this->getRedis();
$redis->shouldReceive('smembers')
- ->with('swoole:rooms:foo')
- ->once();
+ ->with('swoole:rooms:foo')
+ ->once();
$redisRoom = $this->getRedisRoom($redis);
$redisRoom->getClients('foo');
diff --git a/tests/Websocket/SimpleParserTest.php b/tests/Websocket/SimpleParserTest.php
index 2ef763cd..f55f28e1 100644
--- a/tests/Websocket/SimpleParserTest.php
+++ b/tests/Websocket/SimpleParserTest.php
@@ -4,7 +4,6 @@
use Mockery as m;
use Swoole\Websocket\Frame;
-use Swoole\Websocket\Server;
use SwooleTW\Http\Tests\TestCase;
use SwooleTW\Http\Websocket\SimpleParser;
@@ -18,7 +17,7 @@ public function testEncode()
$parser = new SimpleParser;
$this->assertSame(json_encode([
'event' => $event,
- 'data' => $data
+ 'data' => $data,
]), $parser->encode($event, $data));
}
@@ -26,7 +25,7 @@ public function testDecode()
{
$payload = json_encode($data = [
'event' => 'foo',
- 'data' => 'bar'
+ 'data' => 'bar',
]);
$frame = m::mock(Frame::class);
$frame->data = $payload;
diff --git a/tests/Websocket/TableRoomTest.php b/tests/Websocket/TableRoomTest.php
index 4eaf412c..26a6813b 100644
--- a/tests/Websocket/TableRoomTest.php
+++ b/tests/Websocket/TableRoomTest.php
@@ -4,6 +4,7 @@
use Swoole\Table;
use SwooleTW\Http\Tests\TestCase;
+use SwooleTW\Http\Websocket\Rooms\RoomContract;
use SwooleTW\Http\Websocket\Rooms\TableRoom;
class TableRoomTest extends TestCase
@@ -16,7 +17,7 @@ public function setUp()
'room_rows' => 4096,
'room_size' => 2048,
'client_rows' => 8192,
- 'client_size' => 2048
+ 'client_size' => 2048,
];
$this->tableRoom = new TableRoom($config);
$this->tableRoom->prepare();
@@ -47,7 +48,7 @@ public function testInvalidTableName()
public function testSetValue()
{
- $this->tableRoom->setValue($key = 1, $value = ['foo', 'bar'], $table = 'fds');
+ $this->tableRoom->setValue($key = 1, $value = ['foo', 'bar'], $table = RoomContract::DESCRIPTORS_KEY);
$this->assertSame($value, $this->tableRoom->getValue($key, $table));
}
@@ -56,17 +57,17 @@ public function testAddAll()
{
$this->tableRoom->add($key = 1, $values = ['foo', 'bar']);
- $this->assertSame($values, $this->tableRoom->getValue($key, $table = 'fds'));
- $this->assertSame([$key], $this->tableRoom->getValue('foo', 'rooms'));
- $this->assertSame([$key], $this->tableRoom->getValue('bar', 'rooms'));
+ $this->assertSame($values, $this->tableRoom->getValue($key, $table = RoomContract::DESCRIPTORS_KEY));
+ $this->assertSame([$key], $this->tableRoom->getValue('foo', RoomContract::ROOMS_KEY));
+ $this->assertSame([$key], $this->tableRoom->getValue('bar', RoomContract::ROOMS_KEY));
}
public function testAdd()
{
$this->tableRoom->add($key = 1, $value = 'foo');
- $this->assertSame([$value], $this->tableRoom->getValue($key, $table = 'fds'));
- $this->assertSame([$key], $this->tableRoom->getValue($value, 'rooms'));
+ $this->assertSame([$value], $this->tableRoom->getValue($key, $table = RoomContract::DESCRIPTORS_KEY));
+ $this->assertSame([$key], $this->tableRoom->getValue($value, RoomContract::ROOMS_KEY));
}
public function testDeleteAll()
@@ -74,9 +75,9 @@ public function testDeleteAll()
$this->tableRoom->add($key = 1, $values = ['foo', 'bar']);
$this->tableRoom->delete($key);
- $this->assertSame([], $this->tableRoom->getValue($key, $table = 'fds'));
- $this->assertSame([], $this->tableRoom->getValue('foo', 'rooms'));
- $this->assertSame([], $this->tableRoom->getValue('bar', 'rooms'));
+ $this->assertSame([], $this->tableRoom->getValue($key, $table = RoomContract::DESCRIPTORS_KEY));
+ $this->assertSame([], $this->tableRoom->getValue('foo', RoomContract::ROOMS_KEY));
+ $this->assertSame([], $this->tableRoom->getValue('bar', RoomContract::ROOMS_KEY));
}
public function testDelete()
@@ -84,9 +85,9 @@ public function testDelete()
$this->tableRoom->add($key = 1, $values = ['foo', 'bar']);
$this->tableRoom->delete($key, 'foo');
- $this->assertSame(['bar'], $this->tableRoom->getValue($key, $table = 'fds'));
- $this->assertSame([], $this->tableRoom->getValue('foo', 'rooms'));
- $this->assertSame([$key], $this->tableRoom->getValue('bar', 'rooms'));
+ $this->assertSame(['bar'], $this->tableRoom->getValue($key, $table = RoomContract::DESCRIPTORS_KEY));
+ $this->assertSame([], $this->tableRoom->getValue('foo', RoomContract::ROOMS_KEY));
+ $this->assertSame([$key], $this->tableRoom->getValue('bar', RoomContract::ROOMS_KEY));
}
public function testGetRooms()
@@ -94,7 +95,7 @@ public function testGetRooms()
$this->tableRoom->add($key = 1, $values = ['foo', 'bar']);
$this->assertSame(
- $this->tableRoom->getValue($key, $table = 'fds'),
+ $this->tableRoom->getValue($key, $table = RoomContract::DESCRIPTORS_KEY),
$this->tableRoom->getRooms($key)
);
}
@@ -106,7 +107,7 @@ public function testGetClients()
$this->tableRoom->add($keys[1], $room);
$this->assertSame(
- $this->tableRoom->getValue($room, $table = 'rooms'),
+ $this->tableRoom->getValue($room, $table = RoomContract::ROOMS_KEY),
$this->tableRoom->getClients($room)
);
}
diff --git a/tests/Websocket/WebsocketTest.php b/tests/Websocket/WebsocketTest.php
index 91dfceab..9f1c5631 100644
--- a/tests/Websocket/WebsocketTest.php
+++ b/tests/Websocket/WebsocketTest.php
@@ -6,11 +6,13 @@
use Illuminate\Http\Request;
use InvalidArgumentException;
use Illuminate\Pipeline\Pipeline;
+use SwooleTW\Http\Server\Manager;
use SwooleTW\Http\Tests\TestCase;
use Illuminate\Container\Container;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use SwooleTW\Http\Websocket\Websocket;
+use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Websocket\Rooms\RoomContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
@@ -44,60 +46,60 @@ public function testJoin()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('add')
- ->with($sender = 1, $name = ['room'])
- ->once();
+ ->with($sender = 1, $name = ['room'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->join($name);
+ ->setSender($sender)
+ ->join($name);
}
public function testInAlias()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('add')
- ->with($sender = 1, $name = ['room'])
- ->once();
+ ->with($sender = 1, $name = ['room'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->in($name);
+ ->setSender($sender)
+ ->in($name);
}
public function testJoinAll()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('add')
- ->with($sender = 1, $names = ['room1', 'room2'])
- ->once();
+ ->with($sender = 1, $names = ['room1', 'room2'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->join($names);
+ ->setSender($sender)
+ ->join($names);
}
public function testLeave()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('delete')
- ->with($sender = 1, $name = ['room'])
- ->once();
+ ->with($sender = 1, $name = ['room'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->leave($name);
+ ->setSender($sender)
+ ->leave($name);
}
public function testLeaveAll()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('delete')
- ->with($sender = 1, $names = ['room1', 'room2'])
- ->once();
+ ->with($sender = 1, $names = ['room1', 'room2'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->leave($names);
+ ->setSender($sender)
+ ->leave($names);
}
public function testCallbacks()
@@ -119,50 +121,50 @@ public function testLoginUsing()
{
$user = m::mock(AuthenticatableContract::class);
$user->shouldReceive('getAuthIdentifier')
- ->once()
- ->andReturn($id = 1);
+ ->once()
+ ->andReturn($id = 1);
$room = m::mock(RoomContract::class);
$room->shouldReceive('add')
- ->with($sender = 1, ['uid_1'])
- ->once();
+ ->with($sender = 1, ['uid_1'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->loginUsing($user);
+ ->setSender($sender)
+ ->loginUsing($user);
}
public function testLoginUsingId()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('add')
- ->with($sender = 1, ['uid_1'])
- ->once();
+ ->with($sender = 1, ['uid_1'])
+ ->once();
$websocket = $this->getWebsocket($room)
- ->setSender($sender)
- ->loginUsingId(1);
+ ->setSender($sender)
+ ->loginUsingId(1);
}
public function testToUserId()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('getClients')
- ->with('uid_1')
- ->once()
- ->andReturn([$uid = 1]);
+ ->with('uid_1')
+ ->once()
+ ->andReturn([$uid = 1]);
$websocket = $this->getWebsocket($room)->toUserId($uid);
$this->assertTrue(in_array($uid, $websocket->getTo()));
$room->shouldReceive('getClients')
- ->with('uid_2')
- ->once()
- ->andReturn([2]);
+ ->with('uid_2')
+ ->once()
+ ->andReturn([2]);
$room->shouldReceive('getClients')
- ->with('uid_3')
- ->once()
- ->andReturn([3]);
+ ->with('uid_3')
+ ->once()
+ ->andReturn([3]);
$websocket->toUserId([2, 3]);
$this->assertTrue(in_array(2, $websocket->getTo()));
@@ -173,35 +175,35 @@ public function testToUser()
{
$user = m::mock(AuthenticatableContract::class);
$user->shouldReceive('getAuthIdentifier')
- ->once()
- ->andReturn($uid = 1);
+ ->once()
+ ->andReturn($uid = 1);
$room = m::mock(RoomContract::class);
$room->shouldReceive('getClients')
- ->with('uid_1')
- ->once()
- ->andReturn([$uid]);
+ ->with('uid_1')
+ ->once()
+ ->andReturn([$uid]);
$websocket = $this->getWebsocket($room)->toUser($user);
$this->assertTrue(in_array($uid, $websocket->getTo()));
$room->shouldReceive('getClients')
- ->with('uid_2')
- ->once()
- ->andReturn([2]);
+ ->with('uid_2')
+ ->once()
+ ->andReturn([2]);
$room->shouldReceive('getClients')
- ->with('uid_3')
- ->once()
- ->andReturn([3]);
+ ->with('uid_3')
+ ->once()
+ ->andReturn([3]);
$userA = m::mock(AuthenticatableContract::class);
$userA->shouldReceive('getAuthIdentifier')
- ->once()
- ->andReturn(2);
+ ->once()
+ ->andReturn(2);
$userB = m::mock(AuthenticatableContract::class);
$userB->shouldReceive('getAuthIdentifier')
- ->once()
- ->andReturn(3);
+ ->once()
+ ->andReturn(3);
$websocket->toUser($users = [$userA, $userB]);
$this->assertTrue(in_array(2, $websocket->getTo()));
@@ -212,9 +214,9 @@ public function testGetUserId()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('getRooms')
- ->with($sender = 1)
- ->once()
- ->andReturn(['uid_1']);
+ ->with($sender = 1)
+ ->once()
+ ->andReturn(['uid_1']);
$websocket = $this->getWebsocket($room)->setSender($sender);
$this->assertEquals($sender, $websocket->getUserId());
@@ -224,12 +226,12 @@ public function testLogout()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('getRooms')
- ->with($sender = 1)
- ->once()
- ->andReturn(['uid_1']);
+ ->with($sender = 1)
+ ->once()
+ ->andReturn(['uid_1']);
$room->shouldReceive('delete')
- ->with($sender, $name = ['uid_1'])
- ->once();
+ ->with($sender, $name = ['uid_1'])
+ ->once();
$websocket = $this->getWebsocket($room)->setSender($sender);
$websocket->logout();
@@ -239,9 +241,9 @@ public function testIsUserIdOnline()
{
$room = m::mock(RoomContract::class);
$room->shouldReceive('getClients')
- ->with('uid_1')
- ->once()
- ->andReturn([1]);
+ ->with('uid_1')
+ ->once()
+ ->andReturn([1]);
$websocket = $this->getWebsocket($room);
$this->assertTrue($websocket->isUserIdOnline(1));
@@ -251,8 +253,8 @@ public function testReset()
{
$websocket = $this->getWebsocket();
$websocket->setSender(1)
- ->broadcast()
- ->to('foo');
+ ->broadcast()
+ ->to('foo');
$websocket->reset(true);
@@ -264,26 +266,26 @@ public function testReset()
public function testPipeline()
{
App::shouldReceive('call')
- ->once()
- ->andReturnSelf();
+ ->once()
+ ->andReturnSelf();
$request = m::mock(Request::class);
- $middlewares = ['foo', 'bar'];
+ $middleware = ['foo', 'bar'];
$pipeline = m::mock(Pipeline::class);
$pipeline->shouldReceive('send')
- ->with($request)
- ->once()
- ->andReturnSelf();
+ ->with($request)
+ ->once()
+ ->andReturnSelf();
$pipeline->shouldReceive('through')
- ->with($middlewares)
- ->once()
- ->andReturnSelf();
+ ->with($middleware)
+ ->once()
+ ->andReturnSelf();
$pipeline->shouldReceive('then')
- ->once()
- ->andReturn($request);
+ ->once()
+ ->andReturn($request);
$websocket = $this->getWebsocket(null, $pipeline);
- $websocket->middleware($middlewares);
+ $websocket->middleware($middleware);
$websocket->on('connect', function () {
return 'connect';
});
@@ -321,34 +323,85 @@ public function testEmit()
$broadcast = true;
$room = m::mock(RoomContract::class);
$room->shouldReceive('getClients')
- ->with(m::type('string'))
- ->times(3)
- ->andReturn([3, 4, 5]);
+ ->with(m::type('string'))
+ ->times(3)
+ ->andReturn([3, 4, 5]);
+
+ $server = m::mock('server');
+ $server->taskworker = false;
App::shouldReceive('make')
- ->with('swoole.server')
- ->once()
- ->andReturnSelf();
-
- App::shouldReceive('task')
- ->with([
- 'action' => 'push',
- 'data' => [
- 'sender' => $sender,
- 'fds' => [1, 2, 3, 4, 5],
- 'broadcast' => $broadcast,
- 'assigned' => true,
- 'event' => $event = 'event',
- 'message' => $data = 'data'
- ]
- ])
+ ->with(Server::class)
+ ->once()
+ ->andReturn($server);
+
+ $server->shouldReceive('task')
+ ->with([
+ 'action' => 'push',
+ 'data' => [
+ 'sender' => $sender,
+ 'fds' => [1, 2, 3, 4, 5],
+ 'broadcast' => $broadcast,
+ 'assigned' => true,
+ 'event' => $event = 'event',
+ 'message' => $data = 'data',
+ ],
+ ])
+ ->once();
+
+ $websocket = $this->getWebsocket($room);
+ $websocket->setSender($sender)
+ ->to($to)
+ ->broadcast()
+ ->emit($event, $data);
+
+ $this->assertSame([], $websocket->getTo());
+ $this->assertFalse($websocket->getIsBroadcast());
+ }
+
+ public function testEmitInTaskWorker()
+ {
+ $sender = 1;
+ $to = [1, 2, 'a', 'b', 'c'];
+ $broadcast = true;
+ $room = m::mock(RoomContract::class);
+ $room->shouldReceive('getClients')
+ ->with(m::type('string'))
+ ->times(3)
+ ->andReturn([3, 4, 5]);
+
+ $payload = [
+ 'sender' => $sender,
+ 'fds' => [1, 2, 3, 4, 5],
+ 'broadcast' => $broadcast,
+ 'assigned' => true,
+ 'event' => $event = 'event',
+ 'message' => $data = 'data',
+ ];
+
+ $server = m::mock('server');
+ $server->taskworker = true;
+
+ $manager = m::mock(Manager::class);
+ $manager->shouldReceive('pushMessage')
+ ->with($server, $payload)
->once();
+ App::shouldReceive('make')
+ ->with(Server::class)
+ ->once()
+ ->andReturn($server);
+
+ App::shouldReceive('make')
+ ->with(Manager::class)
+ ->once()
+ ->andReturn($manager);
+
$websocket = $this->getWebsocket($room);
$websocket->setSender($sender)
- ->to($to)
- ->broadcast()
- ->emit($event, $data);
+ ->to($to)
+ ->broadcast()
+ ->emit($event, $data);
$this->assertSame([], $websocket->getTo());
$this->assertFalse($websocket->getIsBroadcast());
@@ -359,17 +412,16 @@ public function testClose()
$fd = 1;
App::shouldReceive('make')
- ->with('swoole.server')
- ->once()
- ->andReturnSelf();
+ ->with(Server::class)
+ ->once()
+ ->andReturnSelf();
App::shouldReceive('close')
- ->with($fd)
- ->once();
+ ->with($fd)
+ ->once();
$websocket = $this->getWebsocket();
$websocket->close($fd);
-
}
protected function getWebsocket(RoomContract $room = null, $pipeline = null)
@@ -378,8 +430,8 @@ protected function getWebsocket(RoomContract $room = null, $pipeline = null)
$pipeline = $pipeline ?: m::mock(Pipeline::class);
Config::shouldReceive('get')
- ->once()
- ->andReturn([]);
+ ->once()
+ ->andReturn([]);
return new Websocket($room, $pipeline);
}
diff --git a/tests/fixtures/bootstrap/app.php b/tests/fixtures/bootstrap/app.php
index a7ad3f4a..79e6824b 100644
--- a/tests/fixtures/bootstrap/app.php
+++ b/tests/fixtures/bootstrap/app.php
@@ -5,8 +5,8 @@
use Illuminate\Contracts\Container\Container;
$kernel = new TestKernel;
-
$app = m::mock(Container::class);
+
$app->shouldReceive('make')
->with(Kernel::class)
->once()
@@ -26,9 +26,10 @@
return $app;
-class TestKernel {
+class TestKernel
+{
public function bootstrappers()
{
return [];
}
-}
\ No newline at end of file
+}
diff --git a/tests/fixtures/laravel/config/swoole_http.php b/tests/fixtures/laravel/config/swoole_http.php
index 17447723..70454af0 100644
--- a/tests/fixtures/laravel/config/swoole_http.php
+++ b/tests/fixtures/laravel/config/swoole_http.php
@@ -21,5 +21,5 @@
],
'providers' => [
SwooleTW\Http\Tests\Fixtures\Laravel\App\Providers\TestServiceProvider::class,
- ]
+ ],
];
diff --git a/tests/fixtures/lumen/config/swoole_http.php b/tests/fixtures/lumen/config/swoole_http.php
index f9fa02d9..5a9e3c08 100644
--- a/tests/fixtures/lumen/config/swoole_http.php
+++ b/tests/fixtures/lumen/config/swoole_http.php
@@ -21,5 +21,5 @@
],
'providers' => [
SwooleTW\Http\Tests\Fixtures\Lumen\App\Providers\TestServiceProvider::class,
- ]
+ ],
];