PHP и HTTP/2 от теории до практических решений

Раздел: -> Веб-сервер

Основы HTTP/2 и PHP

Наиболее эффективное решение: настройка веб-сервера с поддержкой HTTP/2

PHP как язык сценариев не требует специальной адаптации для работы поверх HTTP/2. Достаточно настроить веб-сервер (Apache или Nginx) на использование HTTP/2, и PHP-FPM будет получать запросы уже по новому протоколу. При этом все существующие PHP-приложения продолжают работать без изменений, а производительность при передаче множества мелких ресурсов (CSS, JS, изображения) возрастает за счёт мультиплексирования.

Как включить HTTP/2 в Apache?

# Убедиться, что модуль mod_http2 установлен:
sudo a2enmod http2
# В конфигурации виртуального хоста (или .htaccess) добавить:
Protocols h2 http/1.1
# Перезапустить Apache:
sudo systemctl restart apache2

Типичная ошибка: модуль mod_http2 не найден

Некоторые дистрибутивы не включают mod_http2 по умолчанию. Установите пакет libapache2-mod-http2 (Debian/Ubuntu) или соберите Apache с флагом --enable-http2.

Как включить HTTP/2 в Nginx?

# В блоке server добавить:
server {
    listen 443 ssl http2;
    # ... остальные настройки SSL
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        include fastcgi_params;
    }
}
# Проверить конфигурацию и перезагрузить:
sudo nginx -t && sudo systemctl reload nginx

Проблема: HTTP/2 требует HTTPS

Без SSL/TLS большинство браузеров отказываются использовать HTTP/2 (исключение – h2c для внутренних сетей). Создайте самоподписанный сертификат для тестов:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /etc/ssl/private/selfsigned.key \
    -out /etc/ssl/certs/selfsigned.crt

В production используйте Let's Encrypt (Certbot).

После настройки сервера PHP-приложения автоматически получают преимущества HTTP/2: мультиплексирование, сжатие заголовков, возможность Server Push (через заголовки Link).

Как использовать Server Push в PHP?

Server Push позволяет серверу отправлять ресурсы (CSS, JS, шрифты) вместе с основным HTML-документом, не дожидаясь, пока браузер их запросит. В PHP это реализуется через HTTP-заголовок Link:

<?php
// index.php
header('Link: </style.css>; rel=preload; as=style');
header('Link: </script.js>; rel=preload; as=script');
header('Link: </font.woff2>; rel=preload; as=font; crossorigin');
// ... остальной код
?>

Предостережение: чрезмерный Push

Отправка слишком большого числа ресурсов может ухудшить производительность, так как они конкурируют за полосу пропускания с самим HTML. Рекомендуется Push только для критических ресурсов, которые браузер не может сразу обнаружить (например, шрифты, загружаемые из CSS).

Как проверить, что HTTP/2 активен?

Самый простой способ – использовать cURL:

curl -I --http2 https://ваш-сайт.ру/

В выводе ищите строку HTTP/2 200 или проверьте заголовок X-Firefox-Spdy: h2 (в зависимости от сервера). Также можно написать небольшой PHP-скрипт:

<?php
if ($_SERVER['SERVER_PROTOCOL'] === 'HTTP/2') {
    echo 'Протокол HTTP/2 активен';
} else {
    echo 'Текущий протокол: ' . $_SERVER['SERVER_PROTOCOL'];
}
?>
Вывод (при успешной настройке): Протокол HTTP/2 активен

Как отправить запрос по HTTP/2 из PHP (клиентская часть)?

Используйте библиотеку cURL с флагом CURL_HTTP_VERSION_2_0:

<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://http2.golang.org/reqinfo');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$response = curl_exec($ch);
if ($response === false) {
    echo 'Ошибка cURL: ' . curl_error($ch);
} else {
    echo $response;
}
curl_close($ch);
?>

Альтернативный пример с Guzzle:

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client([
    'version' => 2.0, // принудительно HTTP/2
]);

$response = $client->get('https://http2.golang.org/reqinfo');
echo $response->getBody();

Как создать собственный HTTP/2 сервер на PHP (без Apache/Nginx)?

Для продвинутых сценариев можно использовать асинхронные библиотеки, например Swoole или ReactPHP. Swoole предоставляет встроенную поддержку HTTP/2:

<?php
// Установите расширение Swoole: pecl install swoole
$http = new Swoole\Http\Server('0.0.0.0', 9501, SWOOLE_BASE);
$http->set([
    'open_http2_protocol' => true,
    'ssl_cert_file' => '/etc/ssl/certs/selfsigned.crt',
    'ssl_key_file' => '/etc/ssl/private/selfsigned.key',
]);

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

$http->start();
?>

Запуск: php server.php. Затем проверьте запросом:

curl --http2 -k https://localhost:9501/
Hello HTTP/2 from Swoole

ReactPHP также имеет пакет для HTTP/2 (react/http и ext-http2). Пример базового сервера:

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

$loop = React\EventLoop\Factory::create();
$server = new React\Http\Server($loop, function (Psr\Http\Message\ServerRequestInterface $request) {
    return new React\Http\Message\Response(
        200,
        ['Content-Type' => 'text/plain'],
        'Hello HTTP/2 from ReactPHP'
    );
});

$socket = new React\Socket\Server('127.0.0.1:9502', $loop, [
    'tls' => [
        'local_cert' => '/etc/ssl/certs/selfsigned.crt',
        'local_pk' => '/etc/ssl/private/selfsigned.key',
    ]
]);
new React\Socket\SecureServer($socket, $loop, []);
$server->listen($socket);
echo 'Server running on https://127.0.0.1:9502' . PHP_EOL;
$loop->run();
?>

Примечание:

Для работы ReactPHP с HTTP/2 требуются расширения ext-http2 или ext-uv, а также использование HTTPS. Данный подход подходит для специализированных приложений, где нужен нестандартный цикл обработки.

Расширенные примеры и нестандартные сценарии

1. Детектирование поддержки HTTP/2 на стороне клиента через JavaScript

Хотя PHP работает на сервере, иногда требуется узнать, может ли браузер клиента использовать HTTP/2 (например, для выбора стратегии загрузки ресурсов). Однако такая информация со стороны сервера недоступна напрямую. Можно отправить JavaScript-код, который сам проверит поддержку:

Пример
<script>
if (window.fetch) {
    var supportsH2 = false;
    fetch('/check_http2.php')
        .then(r => r.text())
        .then(t => { if (t === 'h2') supportsH2 = true; });
}
</script>

Серверный файл check_http2.php:

Пример
<?php
echo ($_SERVER['SERVER_PROTOCOL'] === 'HTTP/2') ? 'h2' : 'h1';
?>

Такой подход позволяет адаптировать загрузку стилей и скриптов (например, использовать <link rel="preload"> только при HTTP/2).

2. Множественный Server Push с использованием HTTP/2 multiplexing

В PHP можно динамически формировать список Link-заголовков на основе зависимостей страницы. Пример для простого CMS:

Пример
<?php
function pushResources(array $resources)
{
    $links = [];
    foreach ($resources as $res) {
        $linkValue = '<' . $res['url'] . '>; rel=preload; as=' . $res['as'];
        if (!empty($res['crossorigin'])) {
            $linkValue .= '; crossorigin';
        }
        $links[] = $linkValue;
    }
    header('Link: ' . implode(', ', $links));
}

// Использование
pushResources([
    ['url' => '/css/theme.css', 'as' => 'style'],
    ['url' => '/js/app.js', 'as' => 'script'],
    ['url' => '/fonts/roboto.woff2', 'as' => 'font', 'crossorigin' => true],
]);
?>

Результат (заголовки, отправленные сервером):

Link: ; rel=preload; as=style, ; rel=preload; as=script, ; rel=preload; as=font; crossorigin

Браузер, поддерживающий HTTP/2, одновременно запросит все указанные ресурсы, не ожидая парсинга HTML.

3. Использование HTTP/2 с прокси-сервером (например, Varnish)

Если сайт использует Varnish или HAProxy, необходимо настроить передачу протокола HTTP/2 до бэкенда с PHP-FPM. Varnish 6+ поддерживает HTTP/2, но между Varnish и Apache/Nginx обычно используется HTTP/1.1. Для организации сквозного HTTP/2 требуется специальная конфигурация. Пример настройки Nginx как reverse proxy с поддержкой HTTP/2:

Пример
server {
    listen 443 ssl http2;
    server_name example.com;

    location / {
        proxy_pass http://backend; # внутренний сервер с PHP-FPM (HTTP/1.1)
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        # ...
    }
}

upstream backend {
    server 127.0.0.1:8080;
}

Внутренний сервер (Apache с PHP-FPM) получает запросы по HTTP/1.1, но для клиента соединение остаётся HTTP/2.

4. Тестирование производительности HTTP/2 с PHP-скриптом

Напишите скрипт, который генерирует множество маленьких файлов и использует Server Push для их одновременной доставки:

Пример
<?php
// push_many.php
$pushPaths = [];
for ($i = 0; $i < 20; $i++) {
    $file = '/assets/block_' . $i . '.js';
    $pushPaths[] = '<' . $file . '>; rel=preload; as=script';
    // Создаём контент (имитация блоков) - для теста можно просто выводить строку
}
header('Link: ' . implode(', ', $pushPaths));
header('Content-Type: text/html');
echo '<!DOCTYPE html><html><head><title>Push Test</title></head><body>'; 
foreach ($pushPaths as $p) {
    echo '<script src="' . $p . '"></script>';
}
echo '</body></html>';
?>

Загрузка такой страницы через HTTP/2 должна быть заметно быстрее, чем через HTTP/1.1, из-за мультиплексирования и Push.

5. Нестандартный случай: h2c (HTTP/2 cleartext) для внутренних сервисов

Если вы разрабатываете микросервисы внутри локальной сети (без HTTPS), можно использовать h2c (HTTP/2 без шифрования). Это поддерживается Apache (через протокол h2c) и некоторыми библиотеками PHP (например, amphp). Пример настройки Apache для внутреннего порта с h2c:

Пример
Listen 8080
<VirtualHost *:8080>
    Protocols h2c http/1.1
    DocumentRoot /var/www/html
</VirtualHost>

Соответственно, PHP-скрипт для отправки запросов по h2c может использовать cURL с CURL_HTTP_VERSION_2_0 и адресом без https:

Пример
curl --http2 http://внутренний-сервер:8080/

Важно: Браузеры не поддерживают h2c, поэтому такой вариант применим только для сервер-серверного взаимодействия.

6. Мониторинг HTTP/2 в реальном времени через PHP

Создайте простой дашборд, который показывает количество HTTP/2 запросов, используя веб-серверные логи (например, Nginx записывает протокол в переменную $server_protocol). Парсинг логов осуществляйте с помощью PHP:

Пример
<?php
$logFile = '/var/log/nginx/access.log';
$lines = file($logFile);
$h2Count = 0;
$total = 0;
foreach ($lines as $line) {
    if (strpos($line, 'HTTP/2') !== false) {
        $h2Count++;
    }
    $total++;
}
echo "Всего запросов: $total, из них HTTP/2: $h2Count";
?>

Результат:

Всего запросов: 1500, из них HTTP/2: 1423

Такой скрипт помогает отслеживать внедрение HTTP/2 на сайте.

PHP и HTTP/2 - comments

En
Php http 2 (php)