HTTP-запрос
Nette инкапсулирует HTTP-запрос в объекты с понятным API и при этом предоставляет фильтр очистки.
HTTP-запрос представлен объектом Nette\Http\Request. Если вы работаете с
Nette, этот объект создаёт фреймворк автоматически, и вы можете получить
его через внедрение
зависимостей. В презентерах достаточно вызвать метод
$this->getHttpRequest(). Если вы работаете вне Nette Framework, объект можно
создать с помощью RequestFactory.
Большое преимущество Nette в том, что при создании объекта он автоматически очищает все входные параметры (GET, POST, COOKIE), а также URL от управляющих символов и некорректных последовательностей UTF-8. С этими данными вы затем можете безопасно работать. Очищенные данные потом используются в презентерах и формах.
Nette\Http\Request
Этот объект неизменяем. У него нет сеттеров, есть только один так
называемый виттер withUrl(), который объект не меняет, а возвращает
новый экземпляр с изменённым значением.
withUrl (Nette\Http\UrlScript $url): Nette\Http\Request
Возвращает клон с другим URL.
getUrl(): Nette\Http\UrlScript
Возвращает URL запроса как объект UrlScript.
$url = $httpRequest->getUrl();
echo $url; // https://nette.org/en/documentation?action=edit
echo $url->getHost(); // nette.org
Внимание: браузеры фрагмент на сервер не отправляют, поэтому
$url->getFragment() вернёт пустую строку.
getQuery (?string $key=null): string|array|null
Возвращает параметры GET-запроса.
$all = $httpRequest->getQuery(); // массив всех параметров URL
$id = $httpRequest->getQuery('id'); // вернёт GET-параметр 'id' (либо null)
getPost (?string $key=null): string|array|null
Возвращает параметры POST-запроса.
$all = $httpRequest->getPost(); // массив всех параметров POST
$id = $httpRequest->getPost('id'); // вернёт POST-параметр 'id' (либо null)
getFile (string|string[] $key): ?Nette\Http\FileUpload
Возвращает загруженный файл как объект Nette\Http\FileUpload:
$file = $httpRequest->getFile('avatar');
if ($file?->hasFile()) { // был ли загружен какой-нибудь файл?
$file->getUntrustedName(); // имя файла, отправленное пользователем
$file->getSanitizedName(); // имя без опасных символов
}
Для доступа к вложенной структуре передайте массив ключей.
// <input type="file" name="my-form[details][avatar]">
$file = $request->getFile(['my-form', 'details', 'avatar']);
Поскольку внешним данным доверять нельзя и, стало быть, нельзя
полагаться на структуру файлов, такой подход безопаснее, чем, например,
$request->getFiles()['my-form']['details']['avatar'], который может дать сбой.
getFiles(): array
Возвращает дерево всех загруженных файлов в нормализованной структуре, листьями которой являются объекты Nette\Http\FileUpload:
$files = $httpRequest->getFiles();
getCookie (string $key): ?string
Возвращает cookie или null, если её нет.
$sessId = $httpRequest->getCookie('sess_id');
getCookies(): array
Возвращает все cookie.
$cookies = $httpRequest->getCookies();
getMethod(): string
Возвращает HTTP-метод, которым был выполнен запрос.
$httpRequest->getMethod(); // GET, POST, HEAD, PUT
isMethod (string $method): bool
Проверяет HTTP-метод, которым был выполнен запрос. Параметр нечувствителен к регистру.
if ($httpRequest->isMethod('GET')) // ...
getHeader (string $header): ?string
Возвращает HTTP-заголовок или null, если его нет. Параметр
нечувствителен к регистру.
$userAgent = $httpRequest->getHeader('User-Agent');
getHeaders(): array<string, string>
Возвращает все HTTP-заголовки ассоциативным массивом. Ключи приводятся к нижнему регистру.
$headers = $httpRequest->getHeaders();
echo $headers['content-type'];
isSecured(): bool
Зашифровано ли соединение (HTTPS)? Для правильной работы может потребоваться настройка прокси.
isSameSite(): bool
Пришёл ли запрос с того же сайта? Начиная с версии 3.4 его заменяет более способный isFrom().
isFrom (FetchSite|array $site, FetchDest|array|null $dest=null, ?bool $user=null): bool
Говорит, откуда пришёл запрос и как браузер его выполнил, на основе
заголовков Sec-Fetch-* (так называемых Fetch Metadata), которые
браузер задаёт сам и которые страница, работающая в браузере жертвы, не
может ни подделать, ни удалить. Nette использует его внутри для
автоматической защиты форм и сигналов от Cross-Site Request Forgery (CSRF). Он
полезен, когда вы хотите защитить собственные чувствительные
действия, например конечные точки API или разрушительные ссылки.
Метод возвращает true, только когда запрос отвечает всем
заданным вами условиям. Первый параметр $site описывает отношение
между страницей, которая инициировала запрос, и вашим сайтом
(заголовок Sec-Fetch-Site). Он принимает одно значение или список таких
вариантов FetchSite:
FetchSite::SameOrigin– ровно с того же источника (схема, хост и порт)FetchSite::SameSite– с того же сайта, возможно с другого поддоменаFetchSite::CrossSite– с чужого сайтаFetchSite::None– пользователь инициировал его напрямую, например введя URL или открыв закладку
// пришёл ли запрос с наших собственных страниц?
if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) {
// блокируем действие
}
Необязательный параметр $dest (заголовок Sec-Fetch-Dest)
говорит, какой ресурс браузер получает, например FetchDest::Document для
перехода верхнего уровня или FetchDest::Empty для запроса,
выполненного из JavaScript. Необязательный параметр $user (заголовок
Sec-Fetch-User) обозначает, был ли переход вызван настоящим действием
пользователя, например щелчком по ссылке или отправкой формы;
передайте true, чтобы этого требовать.
Проверка того, что действие доступно только с ваших собственных страниц и только через реальное действие пользователя, выглядит тогда так:
if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) {
$this->error();
}
Старые браузеры (Safari до 16.4) заголовки Sec-Fetch-* не
отправляют. Для них Nette откатывается к cookie SameSite=Strict, которая
доказывает лишь то, что запрос не межсайтовый. Проверку, дополнительно
требующую $dest или $user, так подтвердить нельзя, и в таких
браузерах она возвращает false; если это слишком строго,
проверяйте только $site.
isAjax(): bool
Это AJAX-запрос?
getRemoteAddress(): ?string
Возвращает IP-адрес пользователя. Для правильной работы может потребоваться настройка прокси.
getRemoteHost(): ?string
Устаревший, всегда возвращает null. Обратные запросы к DNS были
медленными и ненадёжными; если вам нужно имя хоста, разрешите его сами
из getRemoteAddress().
getBasicCredentials(): ?array
Возвращает учётные данные для базовой HTTP-аутентификации.
[$user, $password] = $httpRequest->getBasicCredentials();
getRawBody(): ?string
Возвращает тело HTTP-запроса.
$body = $httpRequest->getRawBody();
getOrigin(): ?UrlImmutable
Возвращает источник, из которого пришёл запрос. Источник состоит из
схемы (протокола), имени хоста и порта, например https://example.com:8080.
Возвращает null, если заголовка origin нет или он равен 'null'.
$origin = $httpRequest->getOrigin();
echo $origin; // https://example.com:8080
echo $origin?->getHost(); // example.com
Браузер отправляет заголовок Origin в следующих случаях:
- запросы с другого источника (AJAX-вызовы к другому домену)
- POST, PUT, DELETE и другие изменяющие запросы
- запросы, выполненные через Fetch API
Браузер НЕ отправляет заголовок Origin при:
- обычных GET-запросах к тому же домену (переход в рамках того же источника)
- прямом переходе вводом URL в адресную строку
- запросах от клиентов, не являющихся браузерами
В отличие от заголовка Referer, Origin содержит только
схему, хост и порт, а не полный путь URL. Это делает его более подходящим
для проверок безопасности и при этом сохраняет приватность
пользователя. Заголовок Origin в первую очередь используется для
проверки CORS (Cross-Origin
Resource Sharing).
detectLanguage (array $langs): ?string
Определяет язык. Параметром $langs передайте массив языков,
которые поддерживает приложение, и метод вернёт тот, который
предпочитает браузер посетителя. Никакого волшебства, он просто
использует заголовок Accept-Language. Если совпадений нет, возвращает
null.
// Браузер отправляет, например, Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3
$langs = ['hu', 'pl', 'en']; // языки, поддерживаемые приложением
echo $httpRequest->detectLanguage($langs); // en
RequestFactory
Класс Nette\Http\RequestFactory
служит для создания экземпляра Nette\Http\Request, представляющего
текущий HTTP-запрос. (Если вы работаете с Nette, объект HTTP-запроса создаёт
фреймворк автоматически.)
$factory = new Nette\Http\RequestFactory;
$httpRequest = $factory->fromGlobals();
Метод fromGlobals() создаёт объект запроса на основе текущих
глобальных переменных PHP ($_GET, $_POST, $_COOKIE,
$_FILES и $_SERVER). При создании объекта он автоматически
очищает все входные параметры (GET, POST, COOKIE), а также URL от управляющих
символов и некорректных последовательностей UTF-8, что обеспечивает
безопасность при дальнейшей работе с этими данными.
RequestFactory можно настроить до вызова fromGlobals():
- метод
$factory->setBinary()отключает автоматическую очистку входных параметров от управляющих символов и некорректных последовательностей UTF-8. - метод
$factory->setProxy(...)задаёт IP-адрес прокси-сервера, что необходимо для правильного определения IP-адреса пользователя. - метод
$factory->setForceHttps().{data-version:3.3.4} принудительно задаёт схему запроса HTTPS независимо от окружения сервера.
RequestFactory позволяет определить фильтры, которые автоматически преобразуют части URL запроса. Эти фильтры убирают из URL нежелательные символы, которые могли попасть туда, например, из-за неправильных реализаций систем комментариев на разных сайтах:
// убираем пробелы из пути
$requestFactory->urlFilters['path']['%20'] = '';
// убираем точку, запятую или правую скобку с конца URI
$requestFactory->urlFilters['url']['[.,)]$'] = '';
// очищаем путь от двойных слешей (фильтр по умолчанию)
$requestFactory->urlFilters['path']['/{2,}'] = '/';
Первый ключ, 'path' или 'url', определяет, к какой части URL
будет применён фильтр. Второй ключ – регулярное выражение для поиска,
а значение – замена, которая будет использована вместо найденного
текста.
Загруженные файлы
Метод Nette\Http\Request::getFiles() возвращает массив всех загруженных
файлов в нормализованной структуре, листьями которой являются объекты
Nette\Http\FileUpload. Они
инкапсулируют данные, отправленные элементом формы
<input type=file>.
Структура отражает именование элементов в HTML. В простейшем случае это может быть один именованный элемент формы, отправленный так:
<input type="file" name="avatar">
В этом случае $request->getFiles() вернёт массив:
[
'avatar' => /* экземпляр FileUpload */
]
Объект FileUpload создаётся, даже если пользователь никакого файла
не загрузил или загрузка не удалась. Метод hasFile() возвращает true,
если файл был отправлен:
$request->getFile('avatar')?->hasFile();
В случае имени элемента с записью через массив:
<input type="file" name="my-form[details][avatar]">
возвращаемое дерево выглядит так:
[
'my-form' => [
'details' => [
'avatar' => /* экземпляр FileUpload */
],
],
]
Можно создавать и массивы файлов:
<input type="file" name="my-form[details][avatars][]" multiple>
В таком случае структура выглядит так:
[
'my-form' => [
'details' => [
'avatars' => [
0 => /* экземпляр FileUpload */,
1 => /* экземпляр FileUpload */,
2 => /* экземпляр FileUpload */,
],
],
],
]
Лучше всего обращаться к элементу с индексом 1 вложенного массива так:
$file = $request->getFile(['my-form', 'details', 'avatars', 1]);
if ($file instanceof Nette\Http\FileUpload) {
// ...
}
Поскольку внешним данным доверять нельзя и, стало быть, нельзя
полагаться на структуру файлов, такой подход безопаснее, чем, например,
$request->getFiles()['my-form']['details']['avatars'][1], который может дать сбой.
Обзор методов FileUpload
hasFile(): bool
Возвращает true, если пользователь загрузил файл.
isOk(): bool
Возвращает true, если файл был загружен успешно.
getError(): int
Возвращает код ошибки, связанный с загруженным файлом. Это одна из
констант UPLOAD_ERR_XXX. Если файл был
загружен успешно, возвращает UPLOAD_ERR_OK.
move (string $dest)
Перемещает загруженный файл в новое место. Если целевой файл уже существует, он будет перезаписан.
$file->move('/path/to/files/name.ext');
getContents(): ?string
Возвращает содержимое загруженного файла. Если загрузка не удалась,
возвращает null.
getContentType(): ?string
Определяет MIME-тип содержимого загруженного файла по его сигнатуре.
Если загрузка не удалась или определение не удалось, возвращает
null.
Требует PHP-расширения fileinfo.
getUntrustedName(): string
Возвращает исходное имя файла в том виде, в каком его отправил браузер.
Не доверяйте значению, которое возвращает этот метод. Клиент мог отправить вредоносное имя файла с намерением повредить или взломать ваше приложение.
getSanitizedName(): string
Возвращает очищенное имя файла. Оно содержит только ASCII-символы
[a-zA-Z0-9.-]. Если имя таких символов не содержит, возвращает
'unknown'. Если файл – изображение JPEG, PNG, GIF, WebP или AVIF, возвращает
ещё и правильное расширение файла.
Требует PHP-расширения fileinfo.
getSuggestedExtension(): ?string
Возвращает подходящее расширение файла (без точки), соответствующее определённому MIME-типу.
Требует PHP-расширения fileinfo.
getUntrustedFullPath(): string
Возвращает исходный путь к файлу в том виде, в каком его отправил браузер при загрузке каталога. Полный путь доступен только в PHP 8.1 и новее. В предыдущих версиях этот метод возвращает исходное имя файла.
Не доверяйте значению, которое возвращает этот метод. Клиент мог отправить вредоносное имя файла с намерением повредить или взломать ваше приложение.
getSize(): int
Возвращает размер загруженного файла. Если загрузка не удалась,
возвращает 0.
getTemporaryFile(): string
Возвращает путь ко временному расположению загруженного файла. Если
загрузка не удалась, возвращает ''.
__toString(): string
Возвращает путь ко временному расположению загруженного файла. Это
позволяет использовать объект FileUpload прямо как строку.
isImage(): bool
Возвращает true, если загруженный файл – изображение JPEG, PNG, GIF,
WebP или AVIF. Определение выполняется по его сигнатуре и не проверяет
целостность всего файла. Выяснить, не повреждено ли изображение, можно,
например, попыткой его загрузить.
Требует PHP-расширения fileinfo.
getImageSize(): ?array
Возвращает пару [width, height] с размерами загруженного
изображения. Если загрузка не удалась или это не корректное
изображение, возвращает null.
toImage(): Nette\Utils\Image
Загружает изображение как объект Image.
Если загрузка не удалась или это не корректное изображение,
выбрасывает Nette\Utils\ImageException.