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 на сайте.