首發於:個人博客php
PHP從5.4開始,就提供了一個內置的web服務器。html
這個主要是用來作本地的開發用的。不能用於線上環境。如今我就介紹一下這個工具如何使用。laravel
首先咱們假定項目目錄是/home/baoguoxiao/www/php/demo
,外界可訪問的目錄是/home/baoguoxiao/www/php/demo/public
。而後訪問的端口是8000
,入口文件是index.php
和index.html
。那麼咱們能夠執行以下命令:git
cd /home/baoguoxiao/www/php/demo/public
php -S localhost:8000
複製代碼
而後這個時候就能夠正常訪問了。github
那麼如今有個問題,就是難道每次必需要進入public
文件夾才能啓動web服務器嗎,其實咱們能夠指定根目錄的,那麼可使用以下命令:web
cd /home/baoguoxiao/www/php/demo
php -S localhost:8000 -t public/
複製代碼
那麼如今有一個問題就是說,若是咱們使用了單入口,並且仍是用了PATHINFO模式。那麼上面的可能就有問題了。瀏覽器
對此,咱們可使用以下方案:bash
cd /home/baoguoxiao/www/php/demo
php -S localhost:8000 router.php
複製代碼
router.php 文件的代碼服務器
/** * 對URL進行解析,並獲取請求的文件名 */
$uri = urldecode(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));
/** * 判斷是否存在該文件,若是不存在,則直接繼續加載入口文件 */
if ($uri !== "/" && file_exists(__DIR__ . "$uri")) {
return false;
}
/** * 加載入口文件 */
require_once "./index.php";
複製代碼
經過這個路由文件,咱們就能夠支持目前經常使用的開發狀況了。session
上面的方式是咱們本身的實現,那麼咱們也能夠看看相關知名框架的實現方法。
好比 Laravel 和 Symfony。
在Laravel中的安裝一節中介紹了一個命令可使用PHP內置web服務器實現外部訪問的命令。實現的命令是:
php artisan serve
複製代碼
咱們能夠看一下相關代碼:
具體的文件路徑爲:vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php
/** * 執行命令. * * @return int * * @throws \Exception */
public function handle() {
// 切換路徑到 public 目錄
chdir(public_path());
// 在命令臺進行輸出相關內容
$this->line("<info>Laravel development server started:</info> <http://{$this->host()}:{$this->port()}>");
// 執行外部程序,而且 $status 爲系統的返回狀態
passthru($this->serverCommand(), $status);
// $status 爲0 表示執行正常, 爲其餘大於0的數字表示出現了錯誤,有多是端口被搶佔了,這個時候就會接着判斷是否進行再次嘗試
if ($status && $this->canTryAnotherPort()) {
// 對綁定的端口號加1 默認是8000, 若是失敗則重試端口號爲8001,再次失敗重試端口號爲8002,以此類推。
$this->portOffset += 1;
// 再次調用此程序
return $this->handle();
}
// 返回狀態值
return $status;
}
/** * 獲取完整的 server 命令. * * @return string */
protected function serverCommand() {
return sprintf('%s -S %s:%s %s',
// 獲取PHP可執行命令的路徑
ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)),
// 獲取須要綁定的host
$this->host(),
// 獲取須要綁定的端口
$this->port(),
// 對須要執行的參數進行轉義處理。這裏的 server 就是咱們以前說的路由文件,它在項目的根路徑下
ProcessUtils::escapeArgument(base_path('server.php'))
);
}
複製代碼
對上面的命令進行翻譯一下,實際上就是執行的
cd ./public
php -S 0.0.0.0:8000 ../server.php
複製代碼
note:
這裏咱們能夠看到一個區別就是以前我本身寫的代碼,host 都是 localhost, 可是這裏寫的是 0.0.0.0。這兩個有什麼區別呢?
其實區別很簡單,好比我以前寫的 localhost 綁定的ip 是 127.0.0.1, 這個至關於一個迴環地址,那麼咱們就只容許本機的IP進行訪問。而 0.0.0.0,則表示咱們對ip不進行限制,全部的IP均可以進行訪問。
那咱們接着再來看看項目根目錄下面的server.php
:
/** * Laravel - A PHP Framework For Web Artisans * * @package Laravel * @author Taylor Otwell <taylor@laravel.com> */
$uri = urldecode(
parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
);
// 這個文件容許咱們從內置 PHP web 服務器中模擬 Apache 的 "mod_rewrite" 功能.
// 這提供了一種測試 Laravel 應用程序的便捷方法,
// 而無需在此安裝"真正的" web 服務器軟件。
if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
return false;
}
require_once __DIR__.'/public/index.php';
複製代碼
發現跟我以前寫的路由文件相同。沒錯,我就是從這裏抄過來的。
基本上 Larvel 的實現方法就是這樣了。
若是你在使用 Symfony 框架話,發現Symfony有一個組件叫作web-server-bundle,這個組件的做用跟Laravel相同,也是不借助web服務器,實現經過瀏覽器訪問應用程序。
基本的操做能夠參考該頁面
我在這裏主要說一下Symfony是如何實現的.
在Symfony中有一段代碼是這樣的:
public function start(WebServerConfig $config, $pidFile = null) {
// 獲取默認的PID文件位置
$pidFile = $pidFile ?: $this->getDefaultPidFile();
// 判斷是否在運行,若是運行則提示已經在監聽了
if ($this->isRunning($pidFile)) {
throw new \RuntimeException(sprintf('A process is already listening on http://%s.', $config->getAddress()));
}
// fork了一個子進程,若是成功,會有兩個進程進行同時執行下面的文件,父進程,也就是當前執行的進程會返回子進程的PID,而子進程則返回的PID爲0,
// 若是失敗,則子進程不會建立,而且父進程會返回的pid爲-1。更多內容可查看 https://www.php.net/manual/zh/function.pcntl-fork.php
$pid = pcntl_fork();
// 表示fork進程失敗
if ($pid < 0) {
throw new \RuntimeException('Unable to start the server process.');
}
// 進入這個判斷,表示執行的是父進程,表示不用繼續向下執行
if ($pid > 0) {
return self::STARTED;
}
// 今後日後是子進程運行,首先經過 posix_setsid 變爲守護進程,意思是使其脫離終端的管理,自立門戶,誰也沒辦法管理這個進程,除了PID。
if (posix_setsid() < 0) {
throw new \RuntimeException('Unable to set the child process as session leader.');
}
// 建立命令,命令相似Laravel,不過這裏的路由文件跟Laravel相似。也是處理加載規則,並加載入口文件。具體的router.php 路徑爲:
// vendor\symfony\web-server-bundle/Resources/router.php
// 下面是禁用輸出而且開始運行
$process = $this->createServerProcess($config);
$process->disableOutput();
$process->start();
// 判斷是否運行成功
if (!$process->isRunning()) {
throw new \RuntimeException('Unable to start the server process.');
}
// 寫入PID文件
file_put_contents($pidFile, $config->getAddress());
// 檢測PID文件,若是PID文件刪除了,那麼進程就當即退出。
while ($process->isRunning()) {
if (!file_exists($pidFile)) {
$process->stop();
}
sleep(1);
}
// 返回中止的狀態
return self::STOPPED;
}
/** * 啓動PHP內置web服務器 * @return Process The process */
private function createServerProcess(WebServerConfig $config) {
// 查找PHP的可執行程序
$finder = new PhpExecutableFinder();
if (false === $binary = $finder->find(false)) {
throw new \RuntimeException('Unable to find the PHP binary.');
}
$xdebugArgs = ini_get('xdebug.profiler_enable_trigger') ? ['-dxdebug.profiler_enable_trigger=1'] : [];
// 實例化PHP要執行的命令 php_path -dvariables_order=EGPCS -S 127.0.0.1:8000 vendor\symfony\web-server-bundle/Resources/router.php
$process = new Process(array_merge([$binary], $finder->findArguments(), $xdebugArgs, ['-dvariables_order=EGPCS', '-S', $config->getAddress(), $config->getRouter()]));
// 設置工做目錄
$process->setWorkingDirectory($config->getDocumentRoot());
// 設置超時時間
$process->setTimeout(null);
// 設置環境變量
if (\in_array('APP_ENV', explode(',', getenv('SYMFONY_DOTENV_VARS')))) {
$process->setEnv(['APP_ENV' => false]);
$process->inheritEnvironmentVariables();
}
// 返回相關變量
return $process;
}
複製代碼
我在上面的代碼中進行了註釋, 描述了Symfony是如何啓動的.
裏面有一個問題就是使用pcntl_fork
, 該擴展在Windows中是不受支持的. 因此 Symfony框架會提示使用php bin/console server:run
命令運行程序.
其實還有一個方式, 就是 Workman 是經過自身的實現的web服務器,它並無藉助php -S
命令。這一塊的代碼我尚未吃透,而且我以爲這個也能夠單獨拎幾章出來說。但願之後有這個機會。
經過咱們學習 PHP 命令實現web服務器訪問以及對 Laravel 和 Symfony 框架的分析, 讓我瞭解到在Windows的開發過程當中,咱們徹底能夠藉助該方式來擺脫對web服務器的依賴.既能方便咱們在Windows環境進行開發而且學習了PHP一個技巧.感受挺好的.
你們若是對此有什麼疑問能夠評論進行交流.