Реализация HTTP сервера с помощью PHP: варианты и инструкции

Раздел: Настройка сервера -> Веб-сервер

Встроенный сервер PHP для разработки

Наиболее простой и быстрый способ запустить HTTP сервер на PHP - использовать встроенный сервер, доступный начиная с PHP 5.4. Он запускается из командной строки одной командой и не требует дополнительных зависимостей. Этот вариант подходит для локальной разработки, тестирования и создания прототипов. Сервер работает в однопоточном режиме и не рассчитан на высокую нагрузку, но для большинства задач на этапе разработки его возможностей достаточно.

Команда для запуска:

php -S localhost:8000

После запуска сервер будет слушать порт 8000 на локальном интерфейсе. Все запросы обрабатываются скриптом index.php в текущей директории. Для смены корневой папки используется параметр -t:

php -S 0.0.0.0:8080 -t /var/www/html

Если требуется перенаправлять запросы через единую точку входа (роутер), можно указать PHP-файл, который будет выполнен для всех запросов:

php -S localhost:8000 router.php

Файл router.php должен вернуть false для статических файлов, которые сервер должен отдать напрямую, или обработать запрос самостоятельно.

Какие проблемы возникают при использовании встроенного сервера?

Сервер не поддерживает параллельные соединения - каждый запрос обрабатывается последовательно. При длительных операциях (например, работа с API) все остальные запросы будут ждать. Также сервер не поддерживает HTTPS, требует ручного запуска и не подходит для production-среды. Для решения можно использовать PHP-FPM в паре с Nginx или альтернативные серверы, описанные ниже.

Асинхронный сервер с ReactPHP

Как создать HTTP сервер, обрабатывающий запросы параллельно без блокировки?

ReactPHP - библиотека для событийно-ориентированного программирования. Она позволяет реализовать асинхронный HTTP сервер, который может одновременно обслуживать множество соединений без многопоточности. Решение подходит для приложений реального времени, веб-сокетов и высоконагруженных систем, где важна производительность.

Пример установки через Composer:

composer require react/http

Простой сервер:

<?php
require 'vendor/autoload.php';

use React\EventLoop\Factory;
use React\Http\Server;
use Psr\Http\Message\ServerRequestInterface;

$loop = Factory::create();
$server = new Server($loop, function (ServerRequestInterface $request) {
    return new React\Http\Response(
        200,
        ['Content-Type' => 'text/plain'],
        "Hello, World!"
    );
});

$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$server->listen($socket);
$loop->run();

Сервер запускается на порту 8080 и отвечает «Hello, World!» на любой запрос. Для обработки маршрутов потребуется дополнительная маршрутизация, например, через react/http-middleware.

Возможные ошибки при работе с ReactPHP

Частая проблема - блокирующие вызовы внутри обработчика (например, sleep() или файловые операции). Они останавливают весь цикл событий. Рекомендуется использовать асинхронные версии библиотек (ReactPHP MySQL, Guzzle async и т.д.) или оборачивать блокирующие операции в Promise.

Высокопроизводительный сервер на Swoole

Как достичь производительности на уровне C-расширения с поддержкой корутин и многопоточности?

Swoole - PHP-расширение, реализующее асинхронный I/O, корутины и собственный HTTP-сервер. Оно подходит для production-сред, API-шлюзов и приложений, требующих максимальной пропускной способности. Устанавливается через PECL или сборку из исходников.

Пример простого HTTP сервера:

<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);

$http->on('request', function ($request, $response) {
    $response->header('Content-Type', 'text/plain');
    $response->end('Hello from Swoole\n');
});

$http->start();

Сервер слушает порт 9501. Внутри коллбека можно использовать корутины, вызывать базы данных асинхронно, работать с файлами. Swoole также поддерживает WebSocket и TCP.

Типичные ошибки при использовании Swoole

Несовместимость с традиционными PHP-фреймворками - требуется адаптация к асинхронному стилю. Ошибка «Swoole only works in CLI mode» - сервер должен запускаться из консоли, не через веб-сервер. Также нужно следить за памятью: утечки в обработчиках могут привести к падению worker-процессов.

Собственный сервер на сокетах (низкоуровневая реализация)

Как написать HTTP сервер с нуля для понимания протокола?

Этот вариант - учебный, позволяет разобраться с механизмами сетевого программирования. Реализация включает создание сокета, обработку соединений и парсинг HTTP-заголовков. В production использовать не рекомендуется из-за сложности обработки ошибок и безопасности.

Минимальный пример:

<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '127.0.0.1', 8080);
socket_listen($socket);

while ($client = socket_accept($socket)) {
    $request = socket_read($client, 1024);
    $response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, Socket!";
    socket_write($client, $response);
    socket_close($client);
}
socket_close($socket);

Этот код создает сокет, принимает одно соединение, отправляет минимальный HTTP-ответ и завершает работу. Для полноценного сервера нужно добавить парсинг, многопоточность или мультиплексирование (например, с stream_select).

Сложности самописного сервера

Отсутствие поддержки keep-alive, фрагментированного тела запроса, маршрутизации. Без обработки ошибок клиент может повиснуть. Рекомендуется использовать только для обучения.

Расширенные примеры работы с HTTP серверами на PHP

1. Встроенный сервер с динамическим роутингом

Создайте файл router.php для перенаправления запросов на разные обработчики:

Пример
<?php
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

// Отдача статики
if (file_exists(__DIR__ . $path) && is_file(__DIR__ . $path)) {
    return false;
}

// Маршруты
switch ($path) {
    case '/':
        echo "<h1>Добро пожаловать!</h1>";
        break;
    case '/api/data':
        header('Content-Type: application/json');
        echo json_encode(['status' => 'ok']);
        break;
    default:
        http_response_code(404);
        echo "<h1>404 - Страница не найдена</h1>";
}

Запуск:

Пример
php -S localhost:8000 router.php

Теперь GET-запрос к /api/data вернёт JSON, а к корню - HTML-приветствие.

2. ReactPHP с маршрутизацией и middleware

Устанавливаем дополнительные пакеты:

Пример
composer require react/http-middleware
composer require react/psr7

Пример сервера с роутером:

Пример
<?php
require 'vendor/autoload.php';

use React\EventLoop\Factory;
use React\Http\Server;
use React\Http\Response;
use React\Http\Middleware\StreamingRequestMiddleware;
use React\Http\Middleware\LimitConcurrentRequestsMiddleware;
use Psr\Http\Message\ServerRequestInterface;

$loop = Factory::create();

$server = new Server($loop, function (ServerRequestInterface $request) {
    $path = $request->getUri()->getPath();
    switch ($path) {
        case '/':
            return new Response(200, ['Content-Type' => 'text/html'], '<h1>ReactPHP</h1>');
        case '/api/time':
            $time = date('Y-m-d H:i:s');
            return new Response(200, ['Content-Type' => 'application/json'], json_encode(['time' => $time]));
        default:
            return new Response(404, ['Content-Type' => 'text/plain'], 'Not Found');
    }
});

$socket = new React\Socket\Server('0.0.0.0:8080', $loop);
$server->listen($socket);
$loop->run();

Результат: при обращении к /api/time возвращается текущее время в формате JSON.

3. Swoole с корутинной базой данных и статикой

Убедитесь, что расширение Swoole установлено и активно. Пример сервера, который отдаёт статические файлы и использует корутину для запроса к MySQL (через swoole_mysql или PDO с корутиной):

Пример
<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);

// Обработка статических файлов
$http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
    $path = $request->server['request_uri'];
    $filePath = __DIR__ . $path;
    if (is_file($filePath)) {
        $response->header('Content-Type', mime_content_type($filePath));
        $response->sendfile($filePath);
        return;
    }
    // Динамический ответ
    $response->header('Content-Type', 'application/json');
    $response->end(json_encode(['message' => 'Swoole server']));
});

$http->start();

Запуск:

Пример
php server.php

Теперь любые файлы в той же директории будут отдаваться. Если файл не найден - возвращается JSON.

4. Самописный сервер с поддержкой keep-alive (мультиплексирование через stream_select)

Более продвинутый учебный пример, использующий stream_select для одновременной обработки нескольких клиентов:

Пример
<?php
$server = stream_socket_server('tcp://0.0.0.0:8080', $errno, $errstr);
stream_set_blocking($server, false);
$clients = [];

while (true) {
    $read = array_merge([$server], $clients);
    $write = null;
    $except = null;
    if (stream_select($read, $write, $except, 0, 200000) > 0) {
        if (in_array($server, $read)) {
            $client = @stream_socket_accept($server, 0);
            if ($client) {
                stream_set_blocking($client, false);
                $clients[] = $client;
                echo "Новый клиент\n";
            }
            $read = array_filter($read, fn($r) => $r !== $server);
        }
        foreach ($read as $client) {
            $data = @fread($client, 1024);
            if ($data === false || feof($client)) {
                fclose($client);
                $clients = array_filter($clients, fn($c) => $c !== $client);
                continue;
            }
            $response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nHello from non-blocking server";
            fwrite($client, $response);
            fclose($client);
            $clients = array_filter($clients, fn($c) => $c !== $client);
        }
    }
}

Этот сервер обрабатывает несколько соединений без блокировки главного цикла, но не использует keep-alive - после каждого ответа соединение закрывается.

5. Использование Workerman для создания демона с многопоточностью

Workerman - ещё одна популярная PHP-библиотека для многопроцессных сетевых приложений. Пример HTTP сервера:

Пример
<?php
require_once __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;
use Workerman\Lib\Timer;

$http_worker = new Worker('http://0.0.0.0:8080');
$http_worker->count = 4; // число процессов

$http_worker->onMessage = function ($connection, $data) {
    $connection->send('Hello from Workerman');
};

Worker::runAll();

Запуск из командной строки:

Пример
php worker.php start

Workerman автоматически форкает процессы, каждый из которых обрабатывает свои соединения. Отличное решение для high-availability при невысокой сложности.

HTTP сервер на PHP - comments

En
Php http server (php)