Использование преимуществ встроенного PHP сервера

в 20:39, , рубрики: php, php 5.4, php сервер

Одним из крутейший новшеств в php 5.4 является встроенный сервер, созданный специально для разработки и тестирования. Теперь вы можете писать и тестировать свой код не имея полноценного веб-сервера — просто запустите встроенный сервер, протестируйте свой код, и выключите его, когда закончите.
Сервер, так же, предоставляет возможность и для творческого использования. Например, вы можете распространять портативное web-приложение на CD или USB, или даже как десктопное приложение, созданное на PHP без использования GTK или других графических библиотек.

В мануале PHP подчеркивается, что встроенный сервер предназначен для разработки и рекомендуется не использовать его на боевом сервере. Отсутствуют какие-либо INI директивы отвечающие за сервер (за исключением раскарашивания вывода в консоли), и, кажется, что основная идея документации: «у нас теперь тоже есть Web сервер, отстаньте от нас».
Несмотря на это, я считаю, что встроенные сервер может быть ценным инструментом для разработки и тестирования. К примеру, на моей машине я использую предустановленный Apache с кастомной конфигурацией, подходящей для меня, но иногда я хочу попробовать какие-либо новые Web-приложения. Со встроенным web-сервером я могу протестировать приложение прямо из папки «downloads» или временной папки и переместить в обычную среду, только тогда, когда это будет необходимо.
Но для начала, это не так просто, ведь многие написанные приложения используют .htaccess и mod_rewrite. Но я уверен, что кто-то (может быть один из вас, почему бы и нет?) напишет адаптер для этого, и я хотел бы быть первым, кто протестируют его.
В этой статье я объясню некоторые основные примеры использования встроенного сервера и покажу вам как сделать его полезным для разработки и тестирования.

Используем встроенный сервер

Итак, для использования сервера нам необходим php 5.4 или выше. Для проверки версии PHP, выполните:

php -v

Так же вы можете определить доступен ли сервер в вашей сборке выполнив:

php -h

и найдите там описание параметров "-S" и "-t", которые используются только для сервера.
Для проверки сервера вы можете создать в текущей директории файл index.php, который будет содержать в себе вызов функции phpinfo() и затем запустить сервер:

[ec2-user@ip-10-229-67-156 ~]$ php -S 127.0.0.1:8080
PHP 5.4.0RC7 Development Server started at Fri Feb 26 18:49:29 2012
Listening on 127.0.0.1:8080
Document root is /home/ec2-user
Press Ctrl-C to quit.

И теперь вы можете увидеть содержимое отданной встроенным web-сервером:
image
В консоль же будет писаться каждый запрос клиента:

[Sun Feb 26 18:55:30 2012] 80.180.55.37:36318 [200]: /
[Sun Feb 26 18:56:23 2012] 80.180.55.37:36584 [200]: /

Возвращаясь назад, разберем параметр командной строки "-S", который используется для указания адреса, с которого сервер будет доступен. Возможные значения:
localhost — сервер будет доступен только с локальной машины,
0.0.0.0 — на любому интерфейсе машины,
Любой внешний или серый IP — только на указанном IP
Параметр "-t" устанавливает указанную директорию «directory root». Например:

[ec2-user@ip-10-229-67-156 ~]$ php -S <localhost or your public IP>:8090 -t /home/ec2-user/public

Кроме того,. вы может указать имя конкретного файла-роутера. Например:

[ec2-user@ip-10-229-67-156 ~]$ php -S >localhost or your public IP>:8080 -t /home/ec2-user/public public/index.php

Вывод этого роутера будет парситься и выполняться сервером. Простой пример:

<?php
$extensions = array("php", "jpg", "jpeg", "gif", "css");

$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$ext = pathinfo($path, PATHINFO_EXTENSION);
if (in_array($ext, $extensions)) {
    // let the server handle the request as-is
    return false;  


}

echo "<p>Welcome to PHP</p>";

Если скрипт вернет FALSE, тогда запрашиваемый URI будет обрабатываться сервером, который будет выдавать запрошенный ресурс, либо вернет 404 ошибку. Если скрипт возвращает что-либо ещё, вывод скрипта передастся клиенту.
Хотя данный подход даёт нам больше контроля, есть несколько вещей, которые вы должны знать. Во-первых, PHP сервер отдаёт только минимальный набор HTTP заголовков:

Connection: closed
Content-Type: text/html
Host: aws-dev-01.vtardia.com
X-Powered-By: PHP/5.4.0RC7

D
Сравним это с заголовками, возвращаемыми сервером Apache:

Accept-Ranges: bytes
Connection: Keep-Alive
Content-Length: 631
Content-Type: text/html
Date: Sat, 04 Feb 2012 18:24:42 GMT
Etag: "bbb99-277-4ace8c5470a40"
Keep-Alive: timeout=15, max=100
Last-Modified: Wed, 14 Sep 2011 15:54:09 GMT
Server: Apache/2.2.21 (Unix) DAV/2

Если ваше приложение использует заголовки, то оно должно учитывать разницу в development-среде и в production.
Во-вторых, встроенный сервер имеет другое SAPI (Server API). Таким образом выполняя маршрутизацию в index,php вы можете определить на тестовом или боевом сервер происходит обращение к скрипту. php_sapi_name() вернет «cli-server» на встроенном сервере:

<?php
if (php_sapi_name() == "cli-server") {
    // running under built-in server so
    // route static assets and return false
    $extensions = array("php", "jpg", "jpeg", "gif", "css");
    $path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
    $ext = pathinfo($path, PATHINFO_EXTENSION);
    if (in_array($ext, $extensions)) {
        return false;  
    }
}

Существует одна специальная INI директива — «cli_server.color». Данная директива возвращает раскрашенный вывод в консоли. Создайте пустой файл с именем cli-server.ini и вставьте эту строку:

cli_server.color = on

Вы можете создать уникальную конфигурацию окружения для вашего сервера, указав в вашем INI файле необходимые директивы. Не объявленные директивы примут значения по-умолчанию. Сейчас мы объявили только одну директиву — cli_server.color.
Запустить сервер с параметром "-c" с указанием INI файла:

[ec2-user@ip-10-229-67-156 ~]$ php -S localhost -c cli-server.ini

Если ваш терминал поддерживает цвета, то вы сможете увидеть «цветной» вывод в консоли. 200 статус будет выделен зеленым, 404 — оранжевым, а ошибки сценария будут выделены красным цветом.

Создаём персональный сервер

Теперь, когда вы знаете всё, что необходимо знать о встроенном сервере, давайте сделаем что-нибудь крутое. Создадим собственный портативный сервер!
Я начну со следующей структуры нашего приложения:
Использование преимуществ встроенного PHP сервера
Папка «library» содержит код приложения, «public» — корневая директория, содержит index.php и несколько статичных файлов. Особое внимание в этом руководстве будет уделено папке «server», и поэтому наше приложение будет состоять из простого «Hello Word!» и нескольких картинок и css.
Наша цель — получить возможность запускать сервер из директории приложения одной командой, а наш сервер будет заботиться о роутинге, HTTP заголовках и ошибках.

[ec2-user@ip-10-229-67-156 myapp]$  ./start.sh

Давайте рассмотрим сценарий запуска:

#! /bin/bash

INIFILE="$(pwd)/server/server.ini"
DOCROOT="$(pwd)/public"
ROUTER="$(pwd)/server/router.php"
HOST=0.0.0.0
PORT=8080

PHP=$(which php)
if [ $? != 0 ] ; then
    echo "Unable to find PHP"
    exit 1
fi

$PHP -S $HOST:$PORT -c $INIFILE -t $DOCROOT $ROUTER

Я предполагаю, что скрипт запускается из директории приложения, поэтому INIFILE, DOCROOT, ROUTER определяются используя pwd. Путь до php определяется используя команду which. Если php не был найден в пользовательском $PATH, то скрипт завершит работу ошибкой.
Данный способ работает достаточно хорошо, но давайте предоставим пользователю возможность изменить любой из заданных параметров из командной строки, например:

if [ ! -z $INIFILE ]; then
    INIFILE="$(pwd)/server/server.ini"
fi

Продолжим, папка «errors» содержит файлы для сообщений об HTTP ошибках. Вот пример о 403 ошибке: хотя я и использовал только HTML, скрипт будет подключен, использую include, поэтому вы можете использовать любой php код:

<!doctype html>  
<html lang="en">
 <head>
  <meta charset="utf-8">
  <title>403</title>  
 </head>
 <body>
  <h1>403: Forbidden</h1>
  <p>Sorry, the requested resource is not accessible.</p>
 </body>  
</html>

Теперь посмотрим на router.php. Задача данного файла в получении и управлении всеми запросами и передавать их серверу, только если данный файл существует. Все страницы ошибку отображаются путём подключения шаблона.

<?php
// Set timezone
date_default_timezone_set("UTC");

// Directory that contains error pages
define("ERRORS", dirname(__FILE__) . "/errors");

// Default index file
define("DIRECTORY_INDEX", "index.php");

// Optional array of authorized client IPs for a bit of security
$config["hostsAllowed"] = array();

function logAccess($status = 200) {
    file_put_contents("php://stdout", sprintf("[%s] %s:%s [%s]: %sn",
        date("D M j H:i:s Y"), $_SERVER["REMOTE_ADDR"],
        $_SERVER["REMOTE_PORT"], $status, $_SERVER["REQUEST_URI"]));
}

// Parse allowed host list
if (!empty($config['hostsAllowed'])) {
    if (!in_array($_SERVER['REMOTE_ADDR'], $config['hostsAllowed'])) {
        logAccess(403);
        http_response_code(403);
        include ERRORS . '/403.php';
        exit;
    }
}

// if requesting a directory then serve the default index
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$ext = pathinfo($path, PATHINFO_EXTENSION);
if (empty($ext)) {
    $path = rtrim($path, "/") . "/" . DIRECTORY_INDEX;
}

// If the file exists then return false and let the server handle it
if (file_exists($_SERVER["DOCUMENT_ROOT"] . $path)) {
    return false;
}

// default behavior
logAccess(404);
http_response_code(404);
include ERRORS . "/404.php";

В первых строках я определяю некоторые глобальные параметры, такие как DIRECTORY_INDEX, директория с шаблонами ошибок. Параметр date_default_timezone_set() должен совпадать с настройками ОС, иначе будут несоответствия между записями в логе и на сервере. Так же я добавил список разрешенных IP адресов, для повышения безопасности.
Функция logAccess() необходима, потому что когда скрипт роутинга принимает запрос лог сервера по-умолчанию игнорируется. Функция принимает только код статуса, а формат вывода полностью соответствует формату сервера.
Наша первая задача — проверка безопасности. Если IP клиента не находится в массиве разрешенных IP, выводим сообщение об ошибке и завершаем работу скрипта. Нам необходимо отдавать код статуса отличный от 200 и функция header() не будет работать в здесь, поэтому мы используем новую функцию — http_response_code.
Если IP клиента находится в массиве разрешенных IP, то следующий наш шаг — получение запрашиваемого пути и расширения файла. Если расширение пустое, считаем, что пользователь запрашивает папку и строим получаем путь, используя определенный сначала DIRECTORY_INDEX.
В завершении, если запрашиваемый файл существует, возвращаем FALSE, и позволяем серверу обратиться к файлу. Если же нет, то отображается сообщение о 404 ошибке.

Резюме

Это всё. Как видите, php сервер просто в использовании. Наш персональный сервер очень прост. Код можно оптимизировать и включать в более сложные и функциональные классы. Happy coding!

p.s. С радостью приму критику и замечания к переводу в личку.

Автор: WildZero


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js