Pull to refresh

Comments 59

А можно передать отправку файла чему-нибудь более заточенному для работы со статикой и медленными клиентами, например nginx:
header(«X-Accel-Redirect: ».$file);die;
Метод эквивалентный третьему, но с nginx. Удивлен, что не увидел его в статье.
Спасибо, надо обновить статью.
Всегда использовал этот метод, т.к. веб сервер и предназначен для отдачи файлов клиенту.
Для каждой задачи должен использоваться подходящий инструмент.
А первые 2 метода я бы не публиковал. Вижу строчку, которая будет необоснованно кушать лишнюю память и не читаю код дальше.
Первые два метода не только плохи, но и легко кладут сервер на лопатки при средней нагрузке.
Я думаю есть проекты, где нет возможности ставить свои модули для Apache или тот же Nginx.
Не верю. Приведите примеры. Берем Apache и связываем с nginx. Апач обрабатывает динамику, nginx берет на себя статику.
Если хостинг не предоставляет необходимых инструментов, в топку такой хостинг.
Если есть уже nginx, то я не особо вижу смысла ставить Apache. Разве что в очень специфических случаях с нехваткой каких-то определенных модулей.
.htaccess? Если на сервере куча сайтов на различных движках — не хватит никаких рук переписывать кучи .htaccess в конфиги nginx.
Это как раз и относится к специфическим случаям.
Не нужна даже средняя нагрузка. Достаточно большого файла и 1-2 клиентов на медленном канале, соответственно с бешеным (иначе на плохом канале совсем не жизнь) download manager, качающим в несколько потоков.
А разве первые два метода не отдают файл постепенно? Мне кажется, несколько потоков тут никакого профита не дадут.
В том то и дело, что они отдают постепенно и пока не отдадут полностью процесс (поток) висит. А кол-во таких процессов ограничено настройками веб-сервера, т.е. если качать кол-во файлов равное кол-ву процессов, то сайт перестанет отвечать.
Я о том, что 2 и даже 5 клиентов не положат сервер, так как качалка не будет качать в несколько потоков — смысл от них при последовательному скачиванию. Но да — это далеко не лучший вариант отдачи контента.
Подскажите, при этом методе работает докачка?
Кто-нибудь подскажет, как быть с IIS?
У меня файл лежит на диске с уникальным именем, но в момент отдачи клиенту имя файла меняется на вменяемое, поэтому приходится делать что-то, типа первого варианта. А хотелось бы обойтись без php. Это реально?
Вы можете указать любое имя, например:

header('Content-Disposition: attachment; filename=FAKE_NAME.TXT');

Оно не должно совпадать с именем на сервере. Пользователю браузер должен показать именно FAKE_NAME.TXT
Это я знаю, хотелось как раз обойтись при отдаче файла БЕЗ PHP — каким-нибудь хитрым редиректом именно в IIS,
что-нибудь типа
header(«x_redirect:real_name,fake_name), чтобы после этого сработали какие-нибудь механизмы IIS или расширения и файл бы отдался штатными средствами, но с новым именем.
В заголовке.
с помощью PHP
не ясно, за что минус человеку влепили. Явно вопрос был, об альтернативах XSendFile и X-Accel-Redirect под IIS.
Посмотрите здесь.
www.helicontech.com/ape/
А как быть с русскими символами вместо FAKE_NAME.TXT?
Можно попробовать отталкиваться от RFC2231 и заворачивать примерно так:
header("Content-Disposition: attachment; filename*=\"utf8'ru-ru'кириллический utf8 текст\"");
Последний метод хорош, но негодится, если нужно ограничить доступ к файлу (только для авторизованных пользователей). Как решение — нужно создавать симлинк на файл с произвольным именем и периодически подчищать симлинки
Последний метод у меня как раз используется в системе, где все файлы приватные. Доступ к директории закрыт в .htaccess. Файлы отдаются PHP скриптом, который делает все проверки (в реальности у меня Drupal).
Извините, симлинки это тотальный капец, и главное зачем?
Сверху nginx, за ним любой сервер. Предположим, что все «файлы» просятся из папки /files/. Например /files/some-file.file.
В конфиге nginx'а говорим, что такие файлы нао спросить у backend'а по такому то пути. На беке, хоть средствами того же php, проверяем авторизацию пользователя, если все ок — выдаем заголовки с mime-type и т.п. и через x-accel-redirect выдаем на nginx реальное имя файла, вычисленное каким-то кастомным образом из some-file.file. Для таких файлов в ngixn делаем internal location. Если юзер не авторизован или еще что-то не так, выдаем backend'ом 403, 404 или что вам там нужно
В случае аудио или видеофайлов первый метод не подойдет, так как браузер запрашивают файлы по частям, передавая заголовок Range. Придется обрабатывать его вручную, как описано в статье, но проще воспользоваться заголовками X-Accel-Redirect / X-SendFile.
>>> Файл читается в внутренний буфер функции readfile(), размер которого нигде не указан (может кто подскажет)

Функция readfile использует внутреннию функцию php_stream_passthru для отправки файла в output буффер. Сама по себе функция php_stream_passthru это алиас на функцию _php_stream_passthru в коде которого есть декларация буффера размером 8192 символов. Это означает что файл читаеться и отправляеться порция по 8 кБ.

Пруф линки:

github.com/php/php-src/blob/master/ext/standard/file.c#L1345
github.com/php/php-src/blob/master/main/php_streams.h#L448
github.com/php/php-src/blob/master/main/streams/streams.c#L1378
Спасибо! Добавлю в пост.
Случайно отправил недописанный коммент.
Я бы читал content-type из файла, а лучше хранить что бы файл не дергать из PHP лишний раз.
Реальное приложение так и делает :) Drupal кстати записывает mime type в таблицу файлов тоже. Всё как Вы и описали.
А еще можно читать HTTP-RANGE и отдавать то, что запросил клиент, тогда получим докачку файлов.
Допустим, мы скриптом отдаем картинку. В хэдере пишем Content-Type: image/jpeg, но не указываем сontent-disposition (не attachment). Браузер ее корректно отображает. Но при ручном сохранении картинка сохраняется под именем имя_скрипта.php. Как предложить корректное имя?
Можно сделать так:
Делаем новый обработчик пути в системе, например /file/private. Далее все запросы к картинкам делаем через него в формате /file/private/path/inside/your/private/directory/picture.jpg. Обработчик пути должен выдать картинку исходя из параметров переданных скрипту.
Работать будет только с включенным mod_rewrite.
Для браузера путь картинки будет обычным, соответственно при сохранении будет показано имя файла.

Идея нагло подсмотрена в Drupal (обработка приватных файлов).
Да, забыл про этот способ. Надо посмотреть как это реализовано у Drupal.

Кстати, у vbulletin проблема с сохранением файла решена — там прикрепленные картинки также отдаются скриптом, но сохраняются под нормальным именем.
Кажется, таким поведением грешит только хром.
Я пользуюсь как раз решением, описанным в комментарии выше (расширение в урле).
От себя хочу добавить: в таком случае лучше ещё сделать и правильное имя файла, чтобы у сохраняющего не получились файлы типа picture(5).jpg
полные заголовки таких ответов бы привели что ли
    // сбрасываем буфер вывода PHP, чтобы избежать переполнения памяти выделенной под скрипт
    // если этого не сделать файл будет читаться в память полностью!
    if (ob_get_level()) {
      ob_end_clean();
    }


output buffering может быть вложенным, поэтому правильным вариантом будет такой:
while (ob_get_level()) {
  ob_end_clean();
}
кстати если PHP не сможет очистить буфер, то наверное лучше выйти из функции, иначе файл все равно будет читаться в память
как раз напоролся на это, спасибо!
Согласно заголовку: «Отдаем файлы эффективно с помощью PHP»
стоило бы провести хоть маленькое исследование на предмет эффективности.
первые два метода — это как отдавать НЕ ЭФФЕКТИВНО
так что тут проводить на эффективность нечего
мой опыт работы говорит тоже самое,
но! вдруг, автор знает особое конфуособые настройки, чтобы эти методы нормально работали.
и поэтому тестирование хоть какой либо нагрузки, помогло бы автору, закончить статью. и написать реалии использования того или иного метода
чтоб работали особые настройки, нужны особые нагрузки… а без нагрузок смысл говорить об эффективности? что выиигрываем, что проигрываем — один черт, все равно работает ;) и пофиг как работает…
а вот нагрузочку даем, сразу видно что, где и как
Третий метод должен был быть первым.
В целом статья хорошая, все правильно расписано про заголовки, но файлы отдавать напрямую РНР — это Зло, нет — это даже очень Большое Зло! Так что первые два пункта, можно рассматривать только как пример того, как не надо делать.

Файлы отдавать надо напрямую с WEB сервера специальными модулями, если это не аналог рапидшара, в которой куча серверов на отдачу и сделана принудительная задержка на отдачу контента для бесплатных скачиваний.

Как в принципе и закачивать надо средствами WEB сервера а не РНР.
Разработано куча специальных модулей. Я с Апачем не работаю лет уже как пять, и кроме XSendFile — я не знаю, но для nginx есть модули: X-Accel-Redirect,
Для закачки надо использовать ngx_upload_module — на эту тему статьи были на Хабре
для создания защищенной зоны от скачивания есть модули accesskey wiki.nginx.org/HttpAccessKeyModule
отдача приватных файлов организуется с помощью этих двух модулей
а что если мне нужно отдать юзеру файл не со своего сервера а с внешнего? + учесть вариант «докачки» файла?

и, важный момент, редирект на сам файл не считается =)
Проксировать файлы на php это еще хуже первых двух вариантов.
Можно за nginx'ом запустить хоть на python сервер прокачки, отдающий ответ nginx'у. Так его сложнее будет «положить».
Если докачка не поддерживается источником, то тогда в любом случае придется предварительно выкачивать весь файл.
UFO just landed and posted this here
UFO just landed and posted this here
Первые два варианта полезны и даже очень, если нет web сервера вообще, а есть работа с клиентом на микроконтроллере, только переписать пришлось на С++.
Надо только отметить, что контент диспозишены и прочее надо отсылать не в составе страницы, а в ответе сервера.
Sign up to leave a comment.

Articles