天天看點

php7内置服務,PHP之内置web伺服器

前言

PHP從5.4開始,就提供了一個内置的web伺服器。

這個主要是用來做本地的開發用的。不能用于線上環境。現在我就介紹一下這個工具如何使用。

基礎應用

首先我們假定項目目錄是/home/baoguoxiao/www/php/demo,外界可通路的目錄是/home/baoguoxiao/www/php/demo/public。然後通路的端口是8000,入口檔案是index.php和index.html。那麼我們可以執行如下指令:cd /home/baoguoxiao/www/php/demo/public

php -S localhost:8000

然後這個時候就可以正常通路了。

那麼現在有個問題,就是難道每次必須要進入public檔案夾才能啟動web伺服器嗎,其實我們可以指定根目錄的,那麼可以使用如下指令:cd /home/baoguoxiao/www/php/demo

php -S localhost:8000 -t public/

那麼現在有一個問題就是說,如果我們使用了單入口,而且還是用了PATHINFO模式。那麼上面的可能就有問題了。

對此,我們可以使用如下方案: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";

通過這個路由檔案,我們就可以支援目前常用的開發情況了。

架構參考

上面的方式是我們自己的實作,那麼我們也可以看看相關知名架構的實作方法。

比如 Laravel 和 Symfony。

Laravel

在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("Laravel development server started: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.phpnote:

這裡我們可以看到一個差別就是之前我自己寫的代碼,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

$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 架構話,發現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()) {

// 寫入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一個技巧.感覺挺好的.