内置Web Server

PHP 5.4.0起, CLI SAPI 提供了一个内置的Web服务器。

这个内置的Web服务器主要用于本地开发使用,不可用于线上产品环境。

URI请求会被发送到PHP所在的的工作目录(Working Directory)进行处理,除非你使用了-t参数来自定义不同的目录。

如果请求未指定执行哪个PHP文件,则默认执行目录内的index.php 或者 index.html。如果这两个文件都不存在,服务器会返回404错误。

当你在命令行启动这个Web Server时,如果指定了一个PHP文件,则这个文件会作为一个“路由”脚本,意味着每次请求都会先执行这个脚本。如果这个脚本返回 FALSE ,那么直接返回请求的文件(例如请求静态文件不作任何处理)。否则会把输出返回到浏览器。

Example #1 启动Web服务器

$ cd ~/public_html
$ php -S localhost:8000

终端窗口会显示:

PHP 5.4.0 Development Server started at Thu Jul 21 10:43:28 2011
Listening on localhost:8000
Document root is /home/me/public_html
Press Ctrl-C to quit

接着访问http://localhost:8000/和http://localhost:8000/myscript.html,窗口会显示:

PHP 5.4.0 Development Server started at Thu Jul 21 10:43:28 2011
Listening on localhost:8000
Document root is /home/me/public_html
Press Ctrl-C to quit.
[Thu Jul 21 10:48:48 2011] ::1:39144 GET /favicon.ico - Request read
[Thu Jul 21 10:48:50 2011] ::1:39146 GET / - Request read
[Thu Jul 21 10:48:50 2011] ::1:39147 GET /favicon.ico - Request read
[Thu Jul 21 10:48:52 2011] ::1:39148 GET /myscript.html - Request read
[Thu Jul 21 10:48:52 2011] ::1:39149 GET /favicon.ico - Request read

Example #2 启动时指定根目录

$ cd ~/public_html
$ php -S localhost:8000 -t foo/

终端窗口显示:

PHP 5.4.0 Development Server started at Thu Jul 21 10:50:26 2011
Listening on localhost:8000
Document root is /home/me/public_html/foo
Press Ctrl-C to quit

Example #3 使用路由(Router)脚本

请求图片直接显示图片,请求HTML则显示“Welcome to PHP”

<?php
// router.php
if (preg_match('/\.(?:png|jpg|jpeg|gif)$/'$_SERVER["REQUEST_URI"]))
    return 
false;    // 直接返回请求的文件
else { 
    echo 
"<p>Welcome to PHP</p>";
}
?>
$ php -S localhost:8000 router.php

执行之后终端显示:

PHP 5.4.0 Development Server started at Thu Jul 21 10:53:19 2011
Listening on localhost:8000
Document root is /home/me/public_html
Press Ctrl-C to quit.
[Thu Jul 21 10:53:45 2011] ::1:55801 GET /mylogo.jpg - Request read
[Thu Jul 21 10:53:52 2011] ::1:55803 GET /abc.html - Request read
[Thu Jul 21 10:53:52 2011] ::1:55804 GET /favicon.ico - Request read
add a note

User Contributed Notes 10 notes

up
47
jonathan at reinink dot ca
2 years ago
In order to set project specific configuration options, simply add a php.ini file to your project, and then run the built-in server with this flag:

php -S localhost:8000 -c php.ini

This is especially helpful for settings that cannot be set at runtime (ini_set()).
up
24
Ivan Ferrer
2 years ago
On Windows you may find useful to have a phpserver.bat file in shell:sendto with the folowing:
explorer http://localhost:8888
rem check if arg is file or dir
if exist "%~1\" (
  php -S localhost:8888 -t "%~1"
) else (
  php -S localhost:8888 -t "%~dp1"
)

then for fast web testing you only have to SendTo a file or folder to this bat and it will open your explorer and run the server.
up
6
php dot chaska at xoxy dot net
8 months ago
Note that to listen on a naked IPv6 address, you have to strangely enclose the address in so-called square brackets.  Example:

$ php -S [9990:116:a001:1900::1001]:8080 -t web/
up
7
tamas at bartatamas dot hu
1 year ago
If your URI contains a dot, you'll lose the $_SERVER['PATH_INFO'] variable, when using the built-in webserver.
I wanted to write an API, and use .json ending in the URI-s, but then the framework's routing mechanism broke, and it took a lot of time to discover that the reason behind it was its router relying on $_SERVER['PATH_INFO'].

References:
//bugs.php.net/bug.php?id=61286
up
1
yuanoook at gmail dot com
1 month ago
This is my local php server

<?php
$request_uri
= '.'.urldecode($_SERVER['REQUEST_URI']);
if(
is_dir($request_uri) ){

   
// server use the relative directory
   
$dirname = preg_replace('/\/{2}/', '/', $request_uri.'/');

   
$files_arr = array_filter(scandir($dirname), function($file_name){
        return
false == (bool)preg_match('/^\.+/', $file_name);
    });

   
// browser use the absolute path_name which bases on the server root directory
   
$browser_path = preg_replace('/^\./', '', $dirname);

   
printf('<!DOCTYPE HTML>
            <html>
                <head>
                    <meta charset="utf-8">
                    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
                </head>
                <body>
                    %s
                </body>
            </html>'
,
           
implode(
               
preg_replace('/^(.*)$/', '<a href="'.$browser_path.'${1}">${1}</a>', $files_arr),
               
'<br/>'
           
)
        );
}else{
   
header('Content-Type: '.mime_content_type($request_uri));
    echo
file_get_contents($request_uri);
}
up
12
dstrout at dstrout dot net
3 years ago
Just gave it a try, and it is IPv6 compatible, if anyone was wondering.
up
2
simonbengt at gmail dot com
1 year ago
My routing file looks like this:

if (file_exists($_SERVER["DOCUMENT_ROOT"] . $_SERVER["REQUEST_URI"])) {
    return false;
} else {
    require "index.html";
}

The reason is to support all static files.
up
1
synnus at gmail dot com
11 days ago
<?php

   
/*
     *  Web serv mod v2 with  ./WWW/
     *
     */

$request_uri = '.'.urldecode($_SERVER['REQUEST_URI']);
//var_dump($request_uri);
if($request_uri == './' ) { $request_uri = './www'; }

if(
is_dir($request_uri) ){

   
// server use the relative directory
   
$dirname = preg_replace('/\/{2}/', '/', $request_uri.'/');

   
// browser use the absolute path_name which bases on the server root directory
   
$browser_path = preg_replace('/^\./', '', $dirname);
   
   
$ex = explode('/',$browser_path);
   
   
$g = '../';
    foreach(
$ex as $k => $l) {
        echo
'<a href="../' . $g. '">' . str_ireplace('//' , '/' ,($l == ''  ?   '' $g . '/'  )) . '</a>';
       
$g = $l ;
    }
    echo
'<br />', PHP_EOL;
   
   
$files_arr = '';
   
$d = dir($dirname);
   
//echo $d->path , '<br />', PHP_EOL;

   
while (false !== ($entry = $d->read())) {
       if(
$entry != '.') {
           if(
is_dir($d->path . $entry) ){
               
$files_arr .= '<a href="' . $browser_path . $entry . '"> <img src="http://127.0.0.1:8000/res/folder.png" /> ' . $entry . '</a><br />' . PHP_EOL;
           }
           else {
              
$files_arr .= '<a href="' . $browser_path . $entry . '"> <img src="http://127.0.0.1:8000/res/file.png" /> ' . $entry . '</a><br />' . PHP_EOL;
           }
       }
    }
   
$d->close();

   

   echo
'<!DOCTYPE HTML>
            <html>
                <head>
                    <meta charset="utf-8">
                    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
                </head>
                <body>
                    '
, $files_arr,'
                </body>
            </html>'
;
}else{
   
   
//$exp_url = explode('/',$request_uri);
   
   
   
@header('Content-Type: ' . @mime_content_type($request_uri));
   
//echo file_get_contents('www/' . $request_uri);
   
   
$handle = @fopen($request_uri, "rb");
    if (
FALSE === $handle) {
        exit(
"Echec lors de l'ouverture du flux vers l'URL");
    }

    while (!
feof($handle)) {
        echo
fread($handle, 8192);
    }
   
   
fclose($handle);

}

?>
up
0
@salmanapk
27 days ago
I improved Ivan Ferrer's phpserver.bat to open the Send To'd files in the browser directly:-

rem check if arg is file or dir
if exist "%~1\" (
  explorer http://localhost:8888
  php -S localhost:8888 -t "%~1"
) else (
  explorer http://localhost:8888/%~nx1
  php -S localhost:8888 -t "%~dp1"
)
up
-1
leandro at leandroleite dot info
2 months ago
when I need to up a server for develop with rewrite, I use it:

// php -S 0.0.0.0:8080 -file webServer.php

// webServer.php
<?php
if (preg_match('/\.css|\.js|\.jpg|\.png|\.map$/', $_SERVER['REQUEST_URI'], $match)) {
   
$mimeTypes = [
       
'.css' => 'text/css',
       
'.js'  => 'application/javascript',
       
'.jpg' => 'image/jpg',
       
'.png' => 'image/png',
       
'.map' => 'application/json'
   
];
   
$path = __DIR__ . $_SERVER['REQUEST_URI'];
    if (
is_file($path)) {
       
header("Content-Type: {$mimeTypes[$match[0]]}");
        require
$path;
        exit;
    }
}
require_once
__DIR__.'/../app/bootstrap.php';