Как работает прием и обработка HTTP запросов средствами PHP
Основные техники обработки HTTP запросов в PHP
Универсальное решение с суперглобальными массивами и потоком php://input
Этот подход подходит для любого HTTP метода и формата данных. PHP заполняет $_GET для параметров строки запроса, $_POST для данных, отправленных с типом application/x-www-form-urlencoded или multipart/form-data. Тело запроса для других методов (PUT, PATCH, DELETE) или типов (JSON, XML) читается из потока php://input. Также доступны заголовки через getallheaders() или $_SERVER.
<?php
// Метод запроса
$method = $_SERVER['REQUEST_METHOD'];
// Параметры GET
$get = $_GET;
// Данные POST (только для form-data и urlencoded)
$post = $_POST;
// Тело запроса (любой метод и тип)
$rawBody = file_get_contents('php://input');
$parsedBody = [];
if ($rawBody) {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (strpos($contentType, 'application/json') !== false) {
$parsedBody = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// Обработка ошибки
}
} elseif (strpos($contentType, 'application/xml') !== false) {
$parsedBody = simplexml_load_string($rawBody);
} else {
parse_str($rawBody, $parsedBody); // для urlencoded
}
}
// Заголовки
$headers = getallheaders();
// Пример использования
$response = ['method' => $method, 'params' => $get, 'body' => $parsedBody];
header('Content-Type: application/json');
echo json_encode($response);
Пояснения:
$_SERVER['REQUEST_METHOD']содержит метод (GET, POST и т.д.).file_get_contents('php://input')читает сырое тело запроса. Он доступен только один раз, поэтому читайте его сразу.json_decode($rawBody, true)преобразует JSON в массив. Второй параметрtrueвозвращает ассоциативный массив.getallheaders()возвращает все заголовки в виде ассоциативного массива (ключи в нижнем регистре в зависимости от SAPI).
Часто встречающиеся проблемы:
- Пустой $_POST при JSON: Если данные отправлены с Content-Type: application/json, PHP не заполняет $_POST. Решение: читать php://input.
- Ошибка json_decode: Всегда проверять
json_last_error(). Некорректный JSON приводит к ошибке парсинга. - Многократное чтение потока: После первого прочтения поток php://input закрывается. Сохранять результат в переменную.
- CORS заголовки: При запросах с другого домена могут не отправляться пользовательские заголовки. Необходимо обрабатывать предварительные OPTIONS запросы.
Цели и случаи использования:
Базовый метод подходит для простых API, обработки форм, а также для любых проектов, где нет необходимости в сложных абстракциях. Он обеспечивает полный контроль над входящими данными.
Как получить параметры из GET запроса?
GET параметры передаются в URL после знака вопроса. PHP автоматически помещает их в суперглобальный массив $_GET.
<?php
// URL: http://example.com/page?name=John&age=30
$name = $_GET['name'] ?? null;
$age = $_GET['age'] ?? null;
echo "Имя: $name, Возраст: $age";
// Вывод: Имя: John, Возраст: 30
Проблемы:
- Если параметр отсутствует, обращение к ключу вызовет ошибку. Используйте оператор
??илиisset(). - Входные данные могут содержать специальные символы, которые нужно экранировать (например, для вывода в HTML используйте
htmlspecialchars()).
Как обработать POST запрос с JSON?
При отправке JSON через POST (Content-Type: application/json) данные не попадают в $_POST, поэтому их читают напрямую из потока.
<?php
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo 'Некорректный JSON';
exit;
}
// Теперь $data содержит массив с данными
print_r($data);
Ошибка:
- Пропуск проверки
json_last_error()может привести к дальнейшим ошибкам при попытке использовать невалидные данные.
Как принять данные от PUT или DELETE запроса?
Эти методы не заполняют $_POST. Тело запроса читается через php://input.
<?php
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'PUT' || $method === 'DELETE') {
$body = file_get_contents('php://input');
// Парсинг в зависимости от Content-Type
parse_str($body, $params); // если form-urlencoded
// или json_decode для JSON
}
// Для DELETE можно также получать параметры из URL
Ошибка:
- Некоторые веб-серверы (например, nginx) могут не передавать тело DELETE запроса, если не настроены соответствующие модули. Проверять на рабочем сервере.
Как получить заголовки запроса?
Функция getallheaders() возвращает все HTTP-заголовки. Также доступны отдельные заголовки в $_SERVER с префиксом HTTP_.
<?php
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? null;
// Или через $_SERVER
$authHeader2 = $_SERVER['HTTP_AUTHORIZATION'] ?? null;
echo $authHeader;
Проблема:
- В некоторых окружениях (CGI)
getallheaders()может быть недоступен. Альтернатива: перебор$_SERVERс ключамиHTTP_*.
Как загрузить файл через POST?
Файлы передаются с типом multipart/form-data. Данные формы попадают в $_POST, а файлы в $_FILES.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
if ($file['error'] === UPLOAD_ERR_OK) {
$dest = __DIR__ . '/uploads/' . basename($file['name']);
if (move_uploaded_file($file['tmp_name'], $dest)) {
echo 'Файл загружен в ' . $dest;
}
} else {
echo 'Ошибка загрузки: ' . $file['error'];
}
}
Типичные ошибки:
- Превышение лимита
upload_max_filesizeилиpost_max_sizeв php.ini. Проверять конфигурацию. - Файл загружен частично или отсутствует временная директория. Код ошибки
$file['error']укажет причину. - Небезопасное использование
basename($file['name'])может привести к перезаписи или инъекциям. Рекомендуется генерировать уникальное имя.
Как использовать $_REQUEST и в чем опасность?
$_REQUEST объединяет данные из $_GET, $_POST и $_COOKIE (порядок зависит от request_order в php.ini). Использование удобно, но небезопасно: злоумышленник может отправить параметр через GET, переопределив POST-данные.
<?php
// Не рекомендуется
$name = $_REQUEST['name'];
// Лучше явно указать источник
$name = $_POST['name'] ?? $_GET['name'] ?? null;
Риски:
- Смешивание источников данных может привести к непредсказуемому поведению.
- Лучше отказаться от использования
$_REQUESTдля критичных данных.
Как использовать PSR-7 для объектного представления запроса?
PSR-7 (HTTP Message Interface) предлагает стандартные интерфейсы для запросов и ответов. Библиотеки, такие как nyholm/psr7 или zendframework/zend-diactoros, позволяют создать объект запроса.
<?php
// Пример с использованием nyholm/psr7
use Nyholm\Psr7\ServerRequest;
$request = ServerRequest::fromGlobals();
echo $request->getMethod(); // GET
echo $request->getUri(); // URI объект
$parsedBody = $request->getParsedBody(); // массив (если JSON, то null)
$jsonBody = $request->getBody()->getContents(); // сырое тело
$headers = $request->getHeaders();
Сложности:
- Требуется установка дополнительных библиотек через Composer.
- Не все среды поддерживают PSR-7 нативно, может потребоваться адаптер для фреймворка.
Когда использовать:
PSR-7 удобен в крупных проектах, где важна интероперабельность между компонентами, и при построении собственных фреймворков.
Расширенные примеры обработки HTTP запросов
Пример 1. Простой роутер с поддержкой методов и путей
Роутер анализирует URI и метод, вызывает соответствующую функцию. Демонстрирует чтение разных параметров.
<?php
$routes = [
'GET /users' => function() {
$db = getUsers(); // воображаемая функция
return json_encode($db);
},
'POST /users' => function() {
$body = json_decode(file_get_contents('php://input'), true);
if (!$body || !isset($body['name'])) {
http_response_code(400);
return json_encode(['error' => 'Name required']);
}
$id = createUser($body['name']);
http_response_code(201);
return json_encode(['id' => $id]);
},
'GET /users/(\d+)' => function($id) {
$user = getUserById($id);
if (!$user) {
http_response_code(404);
return json_encode(['error' => 'User not found']);
}
return json_encode($user);
},
];
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$matched = false;
foreach ($routes as $pattern => $handler) {
list($routeMethod, $routePath) = explode(' ', $pattern, 2);
if ($method !== $routeMethod) continue;
$regex = preg_replace('/\{(\w+)\}/', '(\w+)', $routePath);
$regex = '#^' . $regex . '$#';
if (preg_match($regex, $uri, $matches)) {
array_shift($matches);
echo call_user_func_array($handler, $matches);
$matched = true;
break;
}
}
if (!$matched) {
http_response_code(404);
echo json_encode(['error' => 'Route not found']);
}
Пример запроса: GET /users/42
Ответ: {"id":42,"name":"Alice"}
Пример 2. Обработка запросов с разными Content-Type (JSON, XML, multipart)
Универсальная функция парсинга тела запроса в зависимости от заголовка Content-Type.
<?php
function parseRequestBody() {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
$raw = file_get_contents('php://input');
if (empty($raw)) {
return null;
}
if (stripos($contentType, 'application/json') !== false) {
$data = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidArgumentException('Invalid JSON: ' . json_last_error_msg());
}
return $data;
}
if (stripos($contentType, 'application/xml') !== false || stripos($contentType, 'text/xml') !== false) {
$data = simplexml_load_string($raw);
if ($data === false) {
throw new InvalidArgumentException('Invalid XML');
}
// Преобразуем SimpleXMLElement в массив
return json_decode(json_encode($data), true);
}
if (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
parse_str($raw, $data);
return $data;
}
// Для multipart/form-data данные уже в $_POST и $_FILES
if (stripos($contentType, 'multipart/form-data') !== false) {
return ['post' => $_POST, 'files' => $_FILES];
}
// Fallback: возвращаем сырой текст
return ['raw' => $raw];
}
try {
$parsed = parseRequestBody();
header('Content-Type: application/json');
echo json_encode(['success' => true, 'data' => $parsed]);
} catch (Exception $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
}
Запрос с JSON: POST /api/data Content-Type: application/json {"key":"value"}
Ответ: {"success":true,"data":{"key":"value"}}
Запрос с XML: POST /api/data Content-Type: application/xml <root><item>abc</item></root>
Ответ: {"success":true,"data":{"root":{"item":"abc"}}}
Пример 3. Обработка аутентификации через Bearer token из заголовка
Извлечение JWT токена из заголовка Authorization и его проверка.
<?php
function getBearerToken(): ?string {
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
return $matches[1];
}
return null;
}
$token = getBearerToken();
if (!$token) {
http_response_code(401);
echo json_encode(['error' => 'Token not provided']);
exit;
}
// Проверка токена (пример с php-jwt)
// try {
// $decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
// $userId = $decoded->sub;
// } catch(Exception $e) {
// http_response_code(401);
// echo json_encode(['error' => 'Invalid token']);
// exit;
// }
echo json_encode(['user_id' => $userId ?? 'example_user']);
Запрос: GET /api/user Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Ответ: {"user_id":"1234567890"}
Пример 4. Обработка входящих HTTP запросов с использованием Symfony HttpFoundation
Компонент Symfony предоставляет объектно-ориентированный доступ к запросу.
<?php
require 'vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
$method = $request->getMethod();
$uri = $request->getPathInfo();
$params = $request->query->all(); // GET
$body = $request->request->all(); // POST form-data
$json = json_decode($request->getContent(), true); // тело (JSON)
$headers = $request->headers->all();
$files = $request->files->all();
$response = new Response(json_encode([
'method' => $method,
'uri' => $uri,
'params' => $params,
'body' => $json ?: $body,
'headers' => $headers,
'files' => array_keys($files),
]), 200, ['Content-Type' => 'application/json']);
$response->send();
Ответ будет содержать JSON с данными запроса.