PHP header(): HTTP‑заголовки, редирект и кеширование — практическое руководство

PHP header(): HTTP‑заголовки, редирект и кеширование — практическое руководство

PHP header(): HTTP‑заголовки, редирект и кеширование — практическое руководство

Функция header() — один из базовых инструментов PHP для управления ответом сервера. С её помощью вы задаёте Content-Type, выполняете редиректы, настраиваете кеширование, CORS и даже раздачу файлов. В статье — практичные паттерны и готовые сниппеты, чтобы быстро решать повседневные задачи и избегать подводных камней.

Когда отправляются заголовки и откуда берётся «Headers already sent»

Заголовки уходят клиенту до первого байта тела ответа. Если вы что-то выводили (echo, var_dump, HTML) — менять заголовки уже нельзя. Проверить можно так:

<?php
if (headers_sent($file, $line)) {
    error_log("Заголовки уже отправлены в $file:$line");
}

Частые причины ошибки:

  • Пробелы или переносы строк до <?php или после ?>
  • BOM в файлах (UTF‑8 without BOM обязателен)
  • Ранняя отладочная печать
  • На время разработки можно включить буферизацию как «подушку безопасности», но лучше дисциплинировать вывод:

    <?php
    ob_start(); // Стартуем как можно раньше
    // ... ваш код
    ob_end_flush();
    

    Синтаксис header() и установка статуса

    Базовые примеры отправки заголовков и статуса ответа:

    <?php
    header('Content-Type: application/json; charset=UTF-8');
    header('X-Frame-Options: DENY');
    header('X-Content-Type-Options: nosniff');
    
    // Редирект со статусом
    header('Location: /login', true, 302);
    exit; // Завершаем скрипт после редиректа
    
    // Либо отдельно код:
    http_response_code(404);
    echo json_encode(['error' => 'Not found']);
    

    Аргумент replace (второй) позволяет не перезаписывать уже установленный одноимённый заголовок: header('Cache-Control: max-age=60', false);

    Редиректы правильно: 301/302/303/307/308 и PRG-паттерн

  • 301 — постоянный редирект (SEO, кэшируется браузером).
  • 302 — временный (по умолчанию в PHP).
  • 303 — после POST перенаправляет на GET (PRG-паттерн).
  • 307/308 — сохраняют метод (307 временный, 308 постоянный).
  • <?php
    // Пример PRG после успешного логина
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $ok = login($_POST['email'] ?? '', $_POST['pass'] ?? '');
        if ($ok) {
            header('Location: /dashboard', true, 303); // POST -> GET
            exit;
        }
    }
    // Показываем форму логина (GET)
    

    JSON API: Content-Type, статус и кеш одним хелпером

    <?php
    function sendJson($data, int $status = 200, int $cacheTtl = 0): void {
        header('Content-Type: application/json; charset=UTF-8');
        http_response_code($status);
    
        if ($cacheTtl > 0) {
            header('Cache-Control: public, max-age=' . $cacheTtl);
        } else {
            header('Cache-Control: no-store, no-cache, must-revalidate');
            header('Pragma: no-cache');
        }
    
        echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }
    
    // Использование
    sendJson(['ok' => true, 'items' => [1,2,3]], 200, 60);
    

    Кеширование: Cache-Control, ETag и Last-Modified

    Для динамики хватит Cache-Control. Для тонкой валидации — ETag и Last-Modified с поддержкой условных запросов.

    <?php
    $content = render_article($id);
    $etag = 'W/"' . md5($content) . '"';
    $lastModified = gmdate('D, d M Y H:i:s', filemtime(article_path($id))) . ' GMT';
    
    header('ETag: ' . $etag);
    header('Last-Modified: ' . $lastModified);
    header('Cache-Control: public, max-age=300');
    
    $ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
    $ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? '';
    
    if ($ifNoneMatch === $etag || $ifModifiedSince === $lastModified) {
        http_response_code(304); // Not Modified
        exit;
    }
    
    echo $content;
    

    Раздача файлов: Content-Disposition и безопасность

    <?php
    $file = __DIR__ . '/reports/2026.pdf';
    if (!is_file($file)) {
        http_response_code(404);
        exit('File not found');
    }
    
    header('Content-Type: application/pdf');
    header('Content-Length: ' . filesize($file));
    header('Content-Disposition: attachment; filename="report-2026.pdf"');
    header('X-Content-Type-Options: nosniff');
    
    readfile($file);
    exit;
    

    Никогда не подставляйте имена файлов напрямую из пользовательского ввода без проверки пути и белых списков расширений.

    Базовый CORS для простых сценариев

    <?php
    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
    $allowed = ['https://app.example.com'];
    if (in_array($origin, $allowed, true)) {
        header('Access-Control-Allow-Origin: ' . $origin);
        header('Vary: Origin');
    }
    
    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('Access-Control-Allow-Headers: Content-Type, Authorization');
        header('Access-Control-Max-Age: 600');
        http_response_code(204);
        exit;
    }
    

    Частые ошибки и как их избежать

  • BOM и пробелы: сохраняйте файлы в UTF‑8 без BOM, не закрывайте PHP-теги в файлах с чистым PHP-кодом.
  • Вывод до заголовков: никакого echo/var_dump до header(). Используйте лог в файл вместо вывода на экран.
  • Нет exit после редиректа: всегда завершайте скрипт после Location.
  • Неверный статус при POST‑редиректе: используйте 303 для PRG, 307/308 — когда важно сохранить метод.
  • Кеш не инвалидируется: обновляйте ETag/Last-Modified и корректно отвечайте 304.
  • Отладка заголовков

    <?php
    print_r(headers_list()); // Какие заголовки уже выставлены
    
    if (headers_sent($f, $l)) {
        error_log("Headers sent at $f:$l");
    }
    

    Мини‑шпаргалка

  • JSON: header('Content-Type: application/json; charset=UTF-8')
  • Редирект: header('Location: /url', true, 302); exit;
  • PRG: 303 See Other
  • Кеш: Cache-Control: public, max-age=...
  • Валидация кеша: ETag, Last-Modified + 304
  • Файлы: Content-Disposition: attachment
  • Безопасность: X-Content-Type-Options: nosniff, X-Frame-Options
  • Что дальше изучать

    Если хотите системно прокачать PHP с нуля до уверенной разработки бэкенда и БД, посмотрите программу и примеры из курса: Прокачать PHP на живых проектах: «PHP и MySQL с Нуля до Гуру 3.0».

    Теперь вы знаете, как уверенно управлять HTTP‑заголовками в PHP и избегать «Headers already sent». Сохраните шпаргалку и переиспользуйте сниппеты в своих проектах!

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий