PHP之內建web伺服器

步履不停
發布: 2023-04-06 21:16:01
原創
7902 人瀏覽過

PHP之內建web伺服器

前言

#PHP從5.4開始,就提供了一個內建的web伺服器。

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

基礎應用程式

首先我們假定專案目錄是/home/baoguoxiao/www/php/demo,外界可存取的目錄是/home/baoguoxiao /www/php/demo/public。然後存取的連接埠是8000,入口檔案是index.phpindex.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("<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 框架話,發現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(&#39;Unable to start the server process.&#39;);
    }

    // 进入这个判断,表示执行的是父进程,表示不用继续向下执行
    if ($pid > 0) {
        return self::STARTED;
    }

    // 从此往后是子进程运行,首先通过 posix_setsid 变为守护进程,意思是使其脱离终端的管理,自立门户,谁也没办法管理这个进程,除了PID。
    if (posix_setsid() < 0) {
        throw new \RuntimeException(&#39;Unable to set the child process as session leader.&#39;);
    }

    // 创建命令,命令类似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一個技巧.感覺挺好的.

大家如果對此有什麼疑問可以評論進行交流.

#更多PHP相關技術文章,請造訪PHP教學欄位進行學習!

以上是PHP之內建web伺服器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!