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

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

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

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

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

php -S localhost:8000

Apache php (apache и php)

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

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

Php http 2 (php и http/2)

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

php -S localhost:8000 router.php

Default php (файл по умолчанию (default.php) в php)

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

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

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

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

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

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

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

composer require react/http

Redirect php https (редирект на https в php)

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

<?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();

Php server https (настройка https на сервере php)

Сервер запускается на порту 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();

Php http server (http сервер на php)

Сервер слушает порт 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)