首发于:个人博客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一个技巧.感受挺好的.
你们若是对此有什么疑问能够评论进行交流.