From 3e915903a28f10fcafc6df18424579c81ce7aefc Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 4 Nov 2015 11:51:54 +0300 Subject: [PATCH] Fixes #10083: Added wrapper for PHP webserver --- framework/CHANGELOG.md | 1 + framework/console/Application.php | 1 + framework/console/controllers/ServeController.php | 141 ++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 framework/console/controllers/ServeController.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 85b9ea2..724ad93 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -55,6 +55,7 @@ Yii Framework 2 Change Log - Chg #9369: `Yii::$app->user->can()` now returns `false` instead of erroring in case `authManager` component is not configured (creocoder) - Chg #9411: `DetailView` now automatically sets container tag ID in case it's not specified (samdark) - Chg #9953: `TimestampBehavior::getValue()` changed to make value processing consistent with `AttributeBehavior::getValue()` (silverfire) +- New #10083: Added wrapper for PHP webserver (samdark) 2.0.6 August 05, 2015 --------------------- diff --git a/framework/console/Application.php b/framework/console/Application.php index 0668da8..b392d56 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -183,6 +183,7 @@ class Application extends \yii\base\Application 'cache' => 'yii\console\controllers\CacheController', 'asset' => 'yii\console\controllers\AssetController', 'fixture' => 'yii\console\controllers\FixtureController', + 'serve' => 'yii\console\controllers\ServeController', ]; } diff --git a/framework/console/controllers/ServeController.php b/framework/console/controllers/ServeController.php new file mode 100644 index 0000000..7be5561 --- /dev/null +++ b/framework/console/controllers/ServeController.php @@ -0,0 +1,141 @@ + + * @since 2.0.7 + */ +class ServeController extends Controller +{ + const EXIT_CODE_NO_DOCUMENT_ROOT = 2; + const EXIT_CODE_NO_ROUTING_FILE = 3; + const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_SERVER = 4; + const EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS = 5; + + /** + * @var int port to serve on. Either "host" or "host:port". + */ + public $port = 8080; + + /** + * @var string path to directory to serve + */ + public $docroot = 'web'; + + /** + * @var string path to router script. + * See https://secure.php.net/manual/en/features.commandline.webserver.php + */ + public $router; + + /** + * Runs PHP built-in web server + * + * @param string $address address to serve on + * + * @return int + */ + public function actionIndex($address = 'localhost') + { + $basePath = Yii::$app->basePath; + $documentRoot = $basePath . '/' . $this->docroot; + + if (strpos($address, ':') === false) { + $address = $address . ':' . $this->port; + } + + if (!is_dir($documentRoot)) { + $this->stdout("Document root \"$documentRoot\" does not exist.\n", Console::FG_RED); + return self::EXIT_CODE_NO_DOCUMENT_ROOT; + } + + if ($this->isOtherServerProcessRunning($address)) { + $this->stdout("There's another server running on http://$address.\n", Console::FG_RED); + return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_SERVER; + } + + if ($this->isAddressTaken($address)) { + $this->stdout("http://$address is taken by another process.\n", Console::FG_RED); + return self::EXIT_CODE_ADDRESS_TAKEN_BY_ANOTHER_PROCESS; + } + + if ($this->router !== null && !file_exists($this->router)) { + $this->stdout("Routing file \"$this->router\" does not exist.\n", Console::FG_RED); + return self::EXIT_CODE_NO_ROUTING_FILE; + } + + $this->stdout("Server started on http://{$address}/\n"); + $this->stdout("Document root is \"{$documentRoot}\"\n"); + if ($this->router) { + $this->stdout("Routing file is \"$this->router\"\n"); + } + $this->stdout("Quit the server with CTRL-C or COMMAND-C.\n"); + + $lock = $this->getLockFile($address); + touch($lock); + + passthru('"' . PHP_BINARY . '"' . " -S {$address} -t \"{$documentRoot}\" $this->router"); + + unlink($lock); + } + + /** + * @inheritdoc + */ + public function options($actionID) + { + return array_merge(parent::options($actionID), [ + 'docroot', + 'router', + 'port', + ]); + } + + /** + * @param string $address server address + * @return string path to pid file + */ + protected function getLockFile($address) + { + return sys_get_temp_dir() . '/' . strtr($address, '.:', '--') . '.pid'; + } + + /** + * @param string $address server address + * @return boolean if address is already in use + */ + protected function isAddressTaken($address) + { + list($hostname, $port) = explode(':', $address); + $fp = @fsockopen($hostname, $port, $errno, $errstr, 3); + if ($fp === false) { + return false; + } + fclose($fp); + return true; + } + + /** + * @param string $address server address + * @return boolean if another server is running at address specified + */ + protected function isOtherServerProcessRunning($address) + { + return file_exists($this->getLockFile($address)); + } +}