29.06.2026 12 мин чтения

HttpRequest вместо $_GET и $_SERVER: зачем и какие методы есть

Кирилл Новожилов

Кирилл Новожилов

Автор

HttpRequest вместо $_GET и $_SERVER: зачем и какие методы есть

Введение

Когда в коде на D7 встречается $_GET['id'] или $_SERVER['HTTP_HOST'], кажется, что это просто короткий путь к данным. На самом деле вы перепрыгиваете через целый слой ядра: тот самый, что фильтрует ввод, разбирает JSON-тело, расшифровывает cookie и помнит про urlrewrite. Иногда это сходит с рук, иногда оборачивается тонким багом, который ловишь полдня.

Дальше разберёмся, почему правильнее использовать Context::getCurrent()->getRequest() вместо суперглобалов, и какие методы HttpRequest закрывают повседневные задачи — от чтения параметров до заголовков и серверных переменных.

Что вы узнаете

  • Что ядро успевает сделать с запросом ещё до того, как до него доберётся ваш код.
  • Почему суперглобалы ломают контракт с ядром.
  • Чем HttpRequest отличается от «просто массива параметров».
  • Какие методы использовать для GET, POST, файлов, заголовков, cookie, JSON и серверных переменных.

Часть 1. Что происходит под капотом

Чтобы понять, почему $_GET и HttpRequest — это не одно и то же, полезно один раз посмотреть, что ядро делает с запросом на старте хита. Дальше будет много про «фильтры» и «расшифровку», и проще держать в голове реальную картину, чем верить на слово.

Откуда вообще берётся объект запроса

Точка сборки — HttpApplication::initializeContext(). Ядро читает суперглобалы и заворачивает их в объекты:

        // bitrix/modules/main/lib/httpapplication.php
$server  = new Server($params['server']);          // обёртка над $_SERVER
$request = new HttpRequest(
    $server,
    $params['get'],     // $_GET
    $params['post'],    // $_POST
    $params['files'],   // $_FILES
    $params['cookie']   // $_COOKIE
);
$context->initialize($request, $response, $server, ['env' => $params['env']]);

    

После этого «правдой» становится объект HttpRequest. Именно поэтому ручная правка $_SERVER где-то в середине хита ни на что не влияет: ядро на него больше не смотрит.

Что делает конструктор

Самое интересное — внутри HttpRequest::__construct(). Он не просто раскладывает массивы по полям, а сразу выполняет несколько неочевидных вещей:

        // bitrix/modules/main/lib/httprequest.php
public function __construct(Server $server, array $queryString, array $postData, array $files, array $cookies, array $jsonData = [])
{
    $request = array_merge($queryString, $postData);   // объединённый GET+POST
    parent::__construct($server, $request);
 
    $this->queryString = new Type\ParameterDictionary($queryString);     // только GET
    $this->postData    = new Type\ParameterDictionary($postData);        // только POST
    $this->files       = new Type\ParameterDictionary($files);
    $this->cookiesRaw  = new Type\ParameterDictionary($cookies);         // как в браузере
    $this->cookies     = new Type\ParameterDictionary($this->prepareCookie($cookies)); // обработанные
    $this->headers     = $this->buildHttpHeaders($server);               // нормализованные заголовки
    $this->jsonData    = new Type\ParameterDictionary($jsonData);
}

    

Обратите внимание на три момента. Во-первых, GET и POST хранятся и по отдельности, и в объединённом виде — отсюда потом возьмутся раздельные getQuery()/getPost() и универсальный get(). Во-вторых, cookie сразу существуют в двух версиях: сырые (cookiesRaw) и пропущенные через prepareCookie(). В-третьих, заголовки тут же собираются в объект HttpHeaders с нормализованными именами, а не остаются строками HTTP_* из $_SERVER.

Cookie: префикс и расшифровка

prepareCookie() — хороший пример того, сколько всего ядро берёт на себя. Он отсекает служебный префикс (по умолчанию BITRIX_SM_, но это опция cookie_name) и расшифровывает значения, которые Bitrix хранит зашифрованными:

        // bitrix/modules/main/lib/httprequest.php
$cookiePrefix = Config\Option::get("main", "cookie_name", "BITRIX_SM") . "_";
$cookiesCrypter = new Web\CookiesCrypter();
// ...
$name = mb_substr($name, $cookiePrefixLength);          // срезаем префикс
if ($cookiesCrypter->shouldDecrypt($name, $value)) {
    $cookiesToDecrypt[$name] = $value;                  // и при необходимости расшифровываем
}

    

Поэтому getCookie('SOME_NAME') отдаёт уже расшифрованное значение и без префикса, а в $_COOKIE вы бы увидели сырое BITRIX_SM_SOME_NAME с зашифрованным содержимым. Если сырой вид всё-таки нужен — для этого есть getCookieRaw().

Фильтры: единая точка санитизации

А вот фильтрация навешивается уже после конструктора — через addFilter(). Метод прогоняет все источники сразу и аккуратно складывает результат обратно:

        // main/lib/httprequest.php (сокращённо)
public function addFilter(Type\IRequestFilter $filter)
{
    parent::addFilter($filter);
 
    $filteredValues = $filter->filter([
        'get'    => $this->queryString->values,
        'post'   => $this->postData->values,
        'files'  => $this->files->values,
        'cookie' => $this->cookiesRaw->values,
        'json'   => $this->jsonData->values,
    ]);
 
    // ... записываем отфильтрованные значения обратно в каждый источник ...
 
    // и пересобираем объединённый GET+POST
    $this->setValuesNoDemand(array_merge($this->queryString->values, $this->postData->values));
 
    // requestedPage пересчитается заново — URL мог измениться
    $this->requestedPage = null;
    $this->requestedPageDirectory = null;
}

    

Главный фильтр здесь — proactive-защита из модуля security (класс Bitrix\Security\Filter\Request). Именно он навешивается на запрос вызовом $this->getHttpRequest()->addFilter(...) и режет потенциально опасные конструкции.

⚠️ Важно
Работает он только когда модуль проактивной защиты включён, так что фильтрация — это страховка ядра, а не замена вашей собственной валидации. Можно зарегистрировать и свой IRequestFilter — интерфейс открытый.

JSON и urlrewrite — по запросу

Две вещи ядро намеренно не делает автоматически на старте.

JSON-тело не парсится само: суперглобалы его не содержат в принципе, оно лежит в php://input. Разбор запускается явно — decodeJson() проверяет isJson(), читает поток и складывает результат в jsonData:

        // bitrix/modules/main/lib/httprequest.php
public function decodeJson(): void
{
    if ($this->isJson()) {
        $json = Web\Json::decode(static::getInput()); // file_get_contents('php://input')
        if (is_array($json)) {
            $this->jsonData = new Type\ParameterDictionary($json);
        }
    }
}

    

А urlrewrite учитывается в момент, когда вы спрашиваете про текущий файл. getScriptFile() понимает, что физически отработал диспетчер роутинга, и подменяет его на реальный путь:

        // bitrix/modules/main/lib/httprequest.php
public function getScriptFile()
{
    $scriptName = $this->getScriptName();
    if ($scriptName == "/bitrix/routing_index.php" || $scriptName == "/bitrix/urlrewrite.php" || $scriptName == "/404.php") {
        if (($v = $this->server->get("REAL_FILE_PATH")) != null) {
            $scriptName = $v; // настоящий файл после rewrite
        }
    }
    return $scriptName;
}

    

Дальше разберём по пунктам, чем это оборачивается на практике.

Часть 2. Почему не суперглобалы

Фильтрация ввода

Как мы видели выше, фильтры навешиваются на запрос через addFilter(). Практический итог для вас простой: всё, что вы достаёте через $request->get('name') или $request['name'], уже прошло через эту цепочку (как минимум через проактивную-защиту). Суперглобалы же остаются ровно тем, что прислал клиент, — ядро их не трогает.

        // С фильтрами ядра
$id = (int)$request->get('id');
 
// Сырое значение до фильтров — только если осознанно нужно
$raw = $request->getQueryList()->getRaw('title');

    

$_REQUEST — ловушка

С $_REQUEST отдельная история. Он сваливает в кучу GET, POST и cookie, причём приоритет источников зависит от request_order в php.ini. На практике это значит, что параметр из cookie может незаметно перебить GET — и вот вам классическая дыра в логике, а заодно подспорье для CSRF-сценариев. В HttpRequest такого не случится: источники честно разведены по getQuery(), getPost() и getCookie().

$_SERVER без контекста urlrewrite

Ядро переписывает URI — через старый urlrewrite.php и через новый роутинг. После этого реальный путь оседает в объекте Server (поле REAL_FILE_PATH), а getScriptFile() отдаёт тот PHP-файл, который действительно отработает. С $_SERVER['SCRIPT_NAME'] это совпадает далеко не всегда.

        $script = $request->getScriptFile();
// /bitrix/routing_index.php → /local/routes/... или /news/index.php

    

Если ориентироваться только на $_SERVER, легко промахнуться при определении текущей страницы или каталога — особенно на ЧПУ.

JSON-тело, заголовки, cookie

REST и BX.ajax.runAction присылают данные как application/json. В суперглобалы это тело не попадает в принципе, и достать его помогает HttpRequest::decodeJson(), складывая результат в getJsonList().

С cookie похожая ситуация. Bitrix хранит их с префиксом BITRIX_SM_ и часть значений шифрует. getCookie() вернёт уже расшифрованное значение и без префикса, а если зачем-то понадобился сырой вид как в $_COOKIE — есть getCookieRaw().

Заголовки тоже приводятся к человеческому виду через HttpHeaders: пишете getHeader('X-Auth-Token') и не вспоминаете, что в $_SERVER это превратилось бы в HTTP_X_AUTH_TOKEN.

Тестируемость и единый API

Наконец, Context::getCurrent() — это точка входа для всего HTTP-хита, и в тестах или CLI её можно подменить. Суперглобалы так не умеют: это глобальное изменяемое состояние процесса, и в юнит-тесте вы с ним намучаетесь.

Всю длинную цепочку при этом писать необязательно:

        use Bitrix\Main\Context;
 
$request = Context::getCurrent()->getRequest();
// то же, что Application::getInstance()->getContext()->getRequest()

    

Часть 3. Карта методов HttpRequest

Теперь к практике. Сам класс лежит в Bitrix\Main\HttpRequest (bitrix/modules/main/lib/httprequest.php), часть методов он подхватывает от родителя Bitrix\Main\Request. Ниже — карта по группам задач, чтобы не держать всё это в голове.

Параметры запроса

Метод Назначение
get($name) GET + POST (объединённый словарь), с фильтрами
getQuery($name) / getQueryList() Только GET
getPost($name) / getPostList() Только POST
getFile($name) / getFileList() Загрузки, структура как в $_FILES
getJsonList() Тело JSON после decodeJson()
toArray() Все GET+POST одним массивом

ParameterDictionary у списков: getRaw($name), getValues(), isEmpty().

Заголовки и cookie

Метод Назначение
getHeader($name) Один заголовок (имя без HTTP_)
getHeaders() Объект HttpHeaders
getCookie($name) / getCookieList() Cookie Bitrix, расшифрованные
getCookieRaw($name) / getCookieRawList() Как пришло из браузера
getCookiesMode() Режим постоянных cookie (Y/N)

Метод, URI, окружение

Метод Назначение
getRequestMethod() GET, POST, …
isPost() Метод POST
isAjaxRequest() X-Requested-With: XMLHttpRequest или HTTP_BX_AJAX
isHttps() Порт 443, HTTPS, плюс https_request из конфига
isAdminSection() /bitrix/admin/, updates, ADMIN_SECTION
getRequestUri() Полный URI с query string
getRequestedPage() Путь страницы (с учётом decode/normalize)
getRequestedPageDirectory() Каталог с завершающим /
getScriptFile() Файл после urlrewrite
getScriptName() / getPhpSelf() Из Server
getHttpHost() Хост без порта
getDecodedUri() URI в кодировке сайта
getRemoteAddress() IP клиента
getUserAgent() User-Agent
getAcceptedLanguages() Массив из Accept-Language
getServerPort() Порт

Для PUT и DELETE готового хелпера в ядре нет, так что сравнивайте напрямую: $request->getRequestMethod() === 'PUT'.

JSON и сырое тело

Метод Назначение
isJson() Content-Type application/json или *+json
decodeJson() Мягкий разбор тела в jsonData
decodeJsonStrict() Строгий разбор, исключение при ошибке
getInput() Статически: file_get_contents('php://input')

Серверные переменные — через Server

Если нужна именно серверная переменная, не лезьте в $_SERVER руками. $request->getServer() отдаёт Bitrix\Main\Server — ту же обёртку над $_SERVER, но с фильтрами и удобными хелперами:

        $server = Context::getCurrent()->getServer();
// или $request->getServer()
 
$server->get('REMOTE_ADDR');
$server->getDocumentRoot();
$server->getHttpHost();
$server->getRequestUri();
$server->getPersonalRoot();   // BX_PERSONAL_ROOT или /bitrix
$server->parseAuthRequest(); // Basic/Digest auth

    

Методы Server::rewriteUri() и transferUri() — это внутренняя кухня urlrewrite. Важно помнить, что если вы правите $_SERVER вручную, эти механизмы о ваших изменениях не узнают и контекст разъедется.

Системные GET-параметры Bitrix

Ещё одна мелочь, которая иногда выручает: HttpRequest::getSystemParameters() отдаёт белый список служебных ключей (sessid, logout, clear_cache и прочие). Удобно, когда нужно отделить параметры пользователя от того, что подмешивает само ядро.

Часть 4. Практический минимум

Собственно, как это выглядит в живом коде — типовая обработка запроса, где есть и query-параметр, и POST, и заголовок, и JSON-тело:

        <?php declare(strict_types=1);
 
use Bitrix\Main\Context;
 
$request = Context::getCurrent()->getRequest();
$request->decodeJson();
 
$id = (int)$request->getQuery('id');
$email = (string)$request->getPost('email');
$token = (string)$request->getHeader('X-Auth-Token');
$payload = $request->getJsonList()->get('filter');
 
if ($request->isPost() && $request->isAjaxRequest()) {
    // ...
}
 
if ($request->isAdminSection()) {
    // ...
}

    

В контроллерах D7 параметры экшена нередко autowire-ятся из запроса автоматически, так что там getRequest() руками дёргать почти не приходится. А вот в сервисах, агентах и компонентах явный вызов остаётся нормой — и это нормально.

Антипаттерны

Нельзя Почему
$_REQUEST['id'] Смешение источников, непредсказуемый приоритет
$_GET в сервисе модуля Нет фильтров ядра, сложно тестировать
$_SERVER['HTTP_X_...'] напрямую Нет нормализации имён заголовков
Читать JSON из $_POST Тело JSON туда не попадает
Править $_SERVER вручную Расходится с Context и urlrewrite

Итог

В новом коде внутри /local/ суперглобалам на входе делать нечего — единственное исключение — bootstrap, пока ядро ещё не подняло контекст. Во всех остальных местах привыкайте начинать с Context::getCurrent()->getRequest(), и половина тонких багов с источниками данных отпадёт сама собой.

Опубликовано 4 дня назад

Комментарии (0)

Пока нет ни одного комментария. Будьте первым!

Похожие статьи

Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.