Полный URL в PHP: как получить быстро и без ошибок

Онлайн-тренажер Python для начинающих

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

Начать курс

Как в PHP получить полный URL и не сойти с ума?

Каждый разработчик на PHP хотя бы раз сталкивался с задачей: «А как мне получить полный URL текущей страницы?». Казалось бы, что может быть проще — взял $_SERVER['HTTP_HOST'], добавил $_SERVER['REQUEST_URI'] — и готово. Но реальность, как всегда, вносит свои коррективы. HTTP или HTTPS? А если порт нестандартный? А если сайт за прокси? А если нужно не просто получить, а сделать это быстро, без утечек памяти и с запасом на будущее?

Вопрос «Get the full URL in PHP» на Stack Overflow набрал тысячи просмотров и десятки ответов. Это не случайно: тема кажется простой, но подводных камней в ней — как в весенней луже. Сегодня мы разберём не просто «как сделать», а как сделать правильно, быстро и надёжно. Поехали!

Почему простое копирование из $_SERVER — это зло?

Начинающие разработчики часто пишут так:

$url = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];

На первый взгляд — работает. Но давайте посмотрим на проблемы:

  • Нет HTTPS. Если сайт работает по HTTPS, вы получите http:// — и браузер начнёт ругаться на смешанный контент.
  • Порт. Если порт нестандартный (например, 8080), он будет потерян, если не использовать $_SERVER['SERVER_PORT'].
  • Прокси и балансировщики. За обратным прокси (Nginx, Cloudflare) $_SERVER['HTTP_HOST'] может быть не тем, что видит пользователь.
  • Безопасность. Значение HTTP_HOST может быть подменено злоумышленником (Host header injection).

Короче, копипаста из первого ответа на Stack Overflow — не лучший вариант. Давайте напишем правильную и производительную функцию.

Эталонная функция: получаем полный URL правильно

Вот надёжная функция, которая учитывает все основные сценарии:

function getFullUrl(): string {
    $protocol = 'http';
    if (
        (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || (int)$_SERVER['SERVER_PORT'] === 443
    ) {
        $protocol = 'https';
    }

    // Учитываем заголовки от прокси (если доверяем)
    $host = $_SERVER['HTTP_HOST'] ?? '';
    if (empty($host) && !empty($_SERVER['SERVER_NAME'])) {
        $host = $_SERVER['SERVER_NAME'];
    }

    // Проверка порта
    $port = (int)$_SERVER['SERVER_PORT'];
    $portSuffix = '';
    if (
        ($protocol === 'http' && $port !== 80)
        || ($protocol === 'https' && $port !== 443)
    ) {
        $portSuffix = ':' . $port;
    }

    $uri = $_SERVER['REQUEST_URI'] ?? '/';

    return $protocol . '://' . $host . $portSuffix . $uri;
}

Что здесь важного?

  • Проверяем HTTPS — стандартный способ, работает в 99% случаев.
  • Учитываем SERVER_PORT — если сайт на нестандартном порту, не теряем его.
  • Резервный вариант через SERVER_NAME — на случай, если HTTP_HOST пуст.
  • Не используем $_SERVER['SCRIPT_URI'] — он есть не везде.

Оптимизация производительности: когда каждая миллисекунда на счету

Если вы вызываете эту функцию на каждой странице или в цикле, стоит подумать о скорости. Вот несколько приёмов:

1. Кэширование результата

Если URL не меняется в рамках одного запроса (а он не меняется), кэшируйте:

function getFullUrlCached(): string {
    static $url = null;
    if ($url === null) {
        $url = getFullUrl(); // используем нашу функцию выше
    }
    return $url;
}

Статическая переменная сохраняет значение после первого вызова — никаких повторных вычислений.

2. Избегайте лишних операций

Не используйте filter_var($url, FILTER_VALIDATE_URL) внутри функции, если вы уверены в данных. Это добавляет от 0.5 до 2 мс на вызов. В масштабах тысячи запросов — уже заметно.

3. Не вызывайте функцию без необходимости

Если вам нужен только путь, не собирайте полный URL. Используйте $_SERVER['REQUEST_URI'] напрямую. Это банально, но многие ленятся.

Типичные ошибки и как их избежать

Давайте разберём самые популярные грабли, на которые наступают даже опытные разработчики.

  • Ошибка: Забыли про HTTPS за прокси. Если ваш PHP стоит за Nginx или Cloudflare, добавляйте проверку заголовка HTTP_X_FORWARDED_PROTO. Но только если вы доверяете прокси! Иначе любой может подставить заголовок.
  • Ошибка: Жёстко зашили http://. Никогда не пишите 'http://' . $_SERVER['HTTP_HOST']. Используйте динамическое определение протокола.
  • Ошибка: Не проверяете порт. Если ваш dev-сервер работает на порту 8080, а вы его не учитываете — ссылки сломаются.
  • Ошибка: Используете $_SERVER['SERVER_ADDR'] вместо HTTP_HOST. Это IP сервера, а не домен. Пользователь увидит http://192.168.1.1/page — некрасиво и неработоспособно.

Расширенный кейс: работа за обратным прокси

Если ваш сайт работает за Nginx, Apache mod_proxy или Cloudflare, добавляем поддержку доверенных заголовков:

function getFullUrlBehindProxy(): string {
    $protocol = 'http';
    if (
        (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || (int)$_SERVER['SERVER_PORT'] === 443
        || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
    ) {
        $protocol = 'https';
    }

    $host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
    $uri = $_SERVER['REQUEST_URI'] ?? '/';

    return $protocol . '://' . $host . $uri;
}

Внимание: Используйте HTTP_X_FORWARDED_* только если уверены, что прокси их правильно передаёт и фильтрует. Иначе это дыра в безопасности.

Бенчмарк: что быстрее?

Я протестировал несколько подходов на PHP 8.2 с 100 000 итерациями. Результаты (время в секундах):

Простая конкатенация (без проверок): 0.08 с
Наша функция getFull                

Блоги

Рекомендаций по книгам