Предположим, вам нужно ограничить возможность загрузки файлов и у вас PHP приложение. Файлы же могут находиться на удаленном или локальном по отношению к сайту хосте.
Есть пара вариантов как это сделать:
- Загружать файлы средствами PHP в TMP, проверять может ли пользователь их скачать или нет и отдавать или не отдавать файл
- Воспользоваться заголовком x-accel-redirect доступном в Nginx.
Первый способ прекрасно позволяет отдавать файлы, но нагружает сервер, так как скрипту нужно скачать, а потом отдать файл, более того иногда происходят ошибки и во временной директории остается мусор, который нужно подчищать за собой. Так что рассмотрим механизм, который предоставляет нам Nginx.
Общая концепция заключается в том, что у нас есть пару locations, один из которых доступен публично, а второй — нет. И в приложении, при обращении к публичному адресу мы можем проверить со стороны приложения допустима ли загрузка, если да — отправить специальный заголовок. Получив заголовок — nginx начнет отдавать файл.
Попробуем реализовать:
Вот пример конфигурации nginx (все внутри секции server):
location /cloud/file {
proxy_pass https://s3storage/url/path/to/file.jpg;
internal;
}
выше описан location, который отправляется на внешний url (путь взят для примера, тут может быть любой публично доступный URL до файла). ключевое слово internal указывает на то, что переход сюда может быть только внутри nginx, при публичном обращении по пути /cloud/file будет возвращен код 404.
Мы сконфигурировали location, теперь рассмотрим как с этим работать со стороны приложения. Предположим, что публичный URL (который увидит пользователь) будет по пути example.com/download/file, тогда код на PHP (для примера — здесь Action из Symfony) будет таким:
[Route('/download/file', name: 'download', methods: ["GET"])]
public function download(): Response
{
// Можно выполнить проверку - можно ли скачивать файл или нет прямо в этой строке, если нельзя - бросить исключение здесь.
$response = new Response();
$response->headers->set("X-Accel-Redirect", '/cloud/file');
$disposition = HeaderUtils::makeDisposition(
HeaderUtils::DISPOSITION_INLINE,
'file.jpg'
);
$response->headers->set('Content-Disposition', $disposition);
return $response;
}
В этом примере у нас есть route до /download/file, с единственным разрешенным методом — GET, в этом примере мы не делаем проверку прав доступа, хотя можем написать любое условие — от сходить в базу до сравнить текущее время, а сразу создаем запрос и выставляем заголовок X-Accel-Redirect, который и ведет как раз на сконфигурированный в nginx location.
Кроме того — задаем disposition, то есть указываем, что этот файл нужно отдать в браузер.
Такой метод позволяет не скачивать файл на промежуточный (читай веб-сервер приложения), а сразу проксировать его на s3-хранилище, что позволяет не нагружать процессор на сервере.