Контролируемая отдача файлов PHP

Скачиваем файлы с помощью PHP и возможностью докачки

Давайте соберем воедино всю информацию, которую мы знаем о том, как отдавать файлы средствами PHP. Вообще, файлы можно отдавать напрямую средствами NGINX или Apache, но иногда требуется, чтобы мы могли учитывать количество скачиваний определенного файла или могли разрешать каким-то пользователям скачивать файл, а каким-то пользователям запрещать. Сделать это можно только в том случае, если за отдачу файла пользователю будет отвечать какой-то скрипт. В нашем случае, скрипт будет написан на языке PHP.

Давайте перейдем сразу к делу и напишем PHP код, который будет отдавать какую-то картинку с нашего сервера:

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

Проверить, включена ли буферизация вывода можно с помощью функции ob_get_level(). Подробнее об этой функции есть в официальной документации PHP. Если говорить коротко, то эта функция возвращает уровень вложенности буферизации. Если буферизация отключена, то функция вернет 0.

Собственно, перед выводом файла нам нужно проверить буферизацию, а если она включена, то отключить.

Функция ob_end_clean() очищает буфер вывода и отключает его, если он был включен. Если буфер был включен с помощью ob_start() и был какой-то вывод до вызова этой функции, то он будет удален.

Но вернемся к нашему коду. Допишем код скачивания, но теперь сделаем проверку на буферизацию и отключим ее, если она была включена:

Давайте теперь разберемся, что означают эти заголовки?

В этом заголовке мы даем понять браузеру, что будет скачана картинка в формате JPEG. Есть множество MIME типов и желательно, чтобы при отдаче файла вы указывали реальный MIME тип файла. В зависимости от этого типа файла, браузер у пользователя может показывать правильное окно для сохранения файла или сразу показывать файл в браузере. Список MIME типов можно посмотреть на странице в Википедии:

Прочитать правильный MIME тип файла перед его отправкой пользователю можно с помощью функции mime_content_type($filePath)

Если вы не хотите узнавать точный MIME тип файла, то можно воспользоваться универсальным MIME типом, который подходит для всех файлов — application/octet-stream:

Мы говорим браузеру: браузер, мы передали вам двоичный файл без указания точного формата файла. Делай с ним что хочешь. При этом не важно, какое будет расширение у файла. Это может быть картинка с расширением .jpg или .php

В этой строчке содержится описание того, что мы будем делать. Собственно на этом функционал этого header’a заканчивается. К сожалению, я не нашел точную информацию относительно строки File Transfer. Мне кажется, браузеры ее игнорируют.

Это очень важный header. При использовании этого header’a мы даем браузеру понять, что нужно скачать файл. Браузер при этом сам решает, скачивать файл автоматически или показать его в браузере. Все зависит от настроек браузера и от переданного MIME типа. Если мы передаем filename, то в браузер будет возвращен файл с этим именем. Очень полезно, когда скачивая картинку, у вас она скачивается с расширением .html или .php.

Остальные параметры не столь важны, за исключением Content-Length. С помощью параметра Content-Length мы передаем точный размер скачиваемого файла в байтах. В этом случае, в браузере будет реализован механизм просмотра оставшегося времени скачивания файла и сколько мегабайт уже скачалось.

Собственно, код скачивания файла готов, но у этого кода есть один очень серьезный недостаток. Код, который мы написали выше не поддерживает докачку файла. Если пользователи скачивают небольшие файлы, например картинки, то это не проблема. Но что делать, если размер файла больше гигабайта? Правильно! Организовывать докачку файла. Сделать докачку файла на PHP очень просто. Сначала я покажу готовый код:

Код очень простой, а благодаря комментариям, я думаю вы в нем разберетесь. Что мы делаем в коде выше? Да собственно ничего серьезного, проверяем существование файла, открываем файл на чтение, смотрим был ли передан range, чтобы пропустить то количество байт, которые пользователь скачал и в конце начинаем отдавать пользователю файл по 8192 килобайт. Это стандартный размер буфера функции readfile.

Код, представленный выше дает возможность скачивания файлов с помощью PHP. Вам остается лишь добавить по желанию запись в базу данных о скачиваемом файле или выполнить еще какие-то действия.

Отдаем файлы эффективно с помощью PHP

Когда вообще нужно отдавать файлы через PHP? Например, когда нужно собрать какую-то статистику скачиваний файла или осуществить запрет на скачивание файлов, позволив скачивать по ссылке только один раз и ограниченной группе пользователей.

Допустим, чтобы скачать какой-то файл, мы отправляем пользователя не на прямую ссылку с расположением файла:

а на адрес скрипта, который отдает пользователю файл через PHP:

Где, в параметр file, для скрипта можно передавать идентификатор файла, который требуется скачать, после чего можно выстраивать различные проверки и выдавать пользователю файл для скачивания:

Сделать это можно несколькими способами, о которых речь пойдет ниже.

Функция readfile()

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

Чтение и отправка файла вручную

Эта функция аналогична той, которая описана выше, но для чтения и отдали файла используются: fopen, feof, fread, fclose. Функция ждет когда файл будет прочитан и отдан, также позволяет экономить память.

Отдаем файл через сервер

Отдать файл можем не скриптом, через PHP, а с помощью Apache или Nginx. Отдача файла средствами сервера дает максимальное быстродействие, минимум потребляет памяти и ресурсов сервера.

Для Apache есть модуль XSendFile, который поможет с помощью специального заголовка сделать отправку файла Apache. В настройках хоста включитте директиву перехвата заголовка:

Функция для отправки файла будет следующей:

Nginx умеет отправку файла из коробки, все что нужно, настроить конфиг, указав запрет на доступ к каталогу (my/path/protected/):

PHP отдача файла для загрузки

Если Вам необходимо правильно отдавать файл для загрузки пользователю для всех современных браузеров, то данный пример поможет Вам в этом.

1 Шаг.

Для начала проверим размер файла:

Данная функция принимает один параметр — ссылку на удаленный файл (который может находиться на другом сервере или внутри данного сервера).

Например, её можно использовать так:

2 Шаг.

Итог. Если файл имеет размер, больший 0, то мы отдаем его для скачивания браузеру. При этом, любой файл будет отдаваться для загрузки, а не открываться в браузере, например видео или текстовый файл.

Возможно, этот файл (6 KB) даст больше информации.

Используя функцию получения размера файла, мы также получаем некоторые данные о файле, а именно:

Получив данный массив, мы можем вывести пользователю дату изменения файла:

После чего, мы можем вывести пользователю данные таким образом:

Контролируемые скачивания в Nginx

Контролируемое скачивание, реализуемое в веб сервере Nginx с использованием заголовка X-Accel-Redirect, подразумевает следующий алгоритм при отдаче контента:

  • Клиент нажимает на ссылку «скачать файл»
  • Запрос передается веб серверу Nginx
  • Nginx в свою очередь передает запрос некоему скрипту на проверку
  • Скрипт, после проверки, например валидности запроса на скачивание, возвращает запрос в Nginx, устанавливая заголовок X-Accel-Redirect
  • Запрос с заголовком X-Accel-Redirect попадает в специальный внутренний location, сервера Nginx, откуда и отдается клиенту

Итак, в данном примере, используются: операционная система FreeBSD 7.1-STABLE amd64, веб сервер Nginx версии 0.7.64 (последний релиз из стабильной ветки на момент написания данного материала). В качестве бакэнда будем использовать PHP в режиме FastCGI сервера, запущенный с помощью утилиты spawn_fcgi.

Корень сайта у нас будет расположен в /home/test/public_html, файлы для скачивания /home/test/public_html/download_files. Для примера набросаем примитивный скрипт down.php и положим в корень сайта а в папку download_files, положим какой-нибудь файл для скачивания, например архив test.zip

Конфигурация Nginx у нас, будет выглядеть следующим образом:

Контролируемая отдача файлов PHP

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

Доброго времени суток!

Предо мной стоит задача, отдавать файлы через PHP (защищая их от прямого скачивания). Нашел множество решений, большинство из которых, по мнению «специалистов» (о уровне их квалификации я судить не могу) — потребляет очень много оперативной памяти. То есть, файл перед «отдачей» загружается в память целиком. Соответственно, если у нас лежит 10 файлов по 1Гб и их необходимо будет отдать все одновременно. Память на сервере, как я понял, может внезапно кончиться.

Нашел вот такой способ, который, по идее вроде бы как кушает памяти поменьше:

Подскажите пожалуйста, насколько затратный по объёму ОЗУ такой способ, хотя бы примерно?

Возможно есть какие-то более оптимальные варианты, для экономии оперативной памяти?

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

Мастер Йода рекомендует:  Бесконечная лента

Профиль
Группа: Завсегдатай
Сообщений: 1670
Регистрация: 19.11.2006
Где: Voronezh

Репутация: 41
Всего: 60

Sanchezzz
Дата 28.8.2012, 07:04 (ссылка) | (нет голосов) Загрузка .
Код
$start = memory_get_usage();
echo $start / 1024, ‘kb
‘;

$foo = new stdClass;
echo ( memory_get_usage() — $start ) / 1024 , ‘kb
‘ ;

$foo2 = new stdClass;
echo ( memory_get_usage() — $start ) / 1024 , ‘kb
‘ ;

отдача скриптом файла потребляет столько же оперативы сколько занесется в буфур при чтении +- еще на работу скрипта.

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

WolfAlone
Дата 28.8.2012, 10:02 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

Результаты:
1. Запуск скрипта

300Кб
2. Внутри while

Что-то мне подсказывает, что я что-то не так делаю. или буфер («буфер» = объём «отъеденой» памяти) на самом деле может быть размером 1.5-2Кб для файла в

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

WolfAlone
Дата 28.8.2012, 10:26 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Комодератор
Сообщений: 6815
Регистрация: 13.4.2007
Где: СПб

Репутация: 96
Всего: 383

WolfAlone, Смешнее всего не то, что копирование файла функцией stream_copy_to_stream не занимает дополнительный объем. Смешно то, что после этой функции куда-то деваются лишние 2k памяти. Либо это баг в php, либо погрешностьизмерения

Впрочем, кто-то, судя по комментариям уже натыкался на такое поведение этой функции.

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

ksnk
Дата 28.8.2012, 11:16 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

350Кб ОЗУ, но для «надежности», будем считать, что условно, такой скрипт кушает 0.5Мб оперативки в течение всего времени затраченного на отдачу файла)?

Добавлено через 4 минуты и 41 секунду
Ещё, подскажите пожалуйста, если взять во внимание, что по умолчанию на выполнение скрипта интерпретатор PHP отводит 30сек., по прошествии этого времени отдача файла прекратиться?

Это сообщение отредактировал(а) WolfAlone — 28.8.2012, 23:43

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

WolfAlone
Дата 28.8.2012, 23:40 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Комодератор
Сообщений: 6815
Регистрация: 13.4.2007
Где: СПб

Репутация: 96
Всего: 383

Посмотрите на функцию readfile. Это, случайно, не то, что нужно?

Для чего ведутся такие расчеты? Для определения нужных параметров в php.ini или для определения нужных объемов памяти в виртуальной машине?

ksnk
Дата 29.8.2012, 07:55 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

1Гб. Номинальная нагрузка на сервер (для расчёта) 1000 одновременно скачиваемых файлов 24х7.

Расчитать нужно, именно объём оперативной памяти, необходимый для этой задачи.

Интересен именно объём потребляемой оперативной памяти по двум причинам:
Основная причина: я думаю, что «раздача файлов» не потребляет большое кол-во процессорных ресурсов
Косвеная причина: если придётся делать уменьшенную копию аналогичного сервера (сервиса), например на VPS (для экономии денег), все провайдеры из тех, что мне встречались (XEN, VmWare), предоставляют в пользование 1-2 ядра от Xeon

2.4ГГц. Основное отличие тарифов друг от друга заключается только в объёме оперативной памяти.

WolfAlone
Дата 29.8.2012, 11:37 (ссылка) | (нет голосов) Загрузка .
Цитата(ksnk @ 29.8.2012, 07:55 )
Посмотрите на функцию readfile. Это, случайно, не то, что нужно?

Внимательно изучил Вашу ссылку, посмотрел пример: Example #1 Forcing a download using readfile(). В принципе, это как раз то, что мне нужно. Вопрос сключительно в том, какой из методов отдачи файла будет кушать наименьшее кол-во ОЗУ, растёт ли потребление ОЗУ пропорционально размеру файла (в каждом конкретном случае) и какой примерно объём ОЗУ выделяется (затрачивается) на скрипт отдающий файл.

Это сообщение отредактировал(а) WolfAlone — 29.8.2012, 11:40

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

Профиль
Группа: Завсегдатай
Сообщений: 2200
Регистрация: 13.11.2007
Где: Донецк

Репутация: 20
Всего: 42

WolfAlone, а погуглите.
И даже по этому форуму.

Где-то были упоминания о том, как создавать временные ссылки через скрипт и отдавать скачивание на откуп тому же nginx
то ли Feldmarshal, то ли Ипатьев про это писали.

Fortop
Дата 29.8.2012, 12:33 (ссылка) | (нет голосов) Загрузка .

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

WolfAlone
Дата 29.8.2012, 14:23 (ссылка) | (нет голосов) Загрузка .
Цитата(Fortop @ 29.8.2012, 12:33 )
Где-то были упоминания о том, как создавать временные ссылки через скрипт и отдавать скачивание на откуп тому же nginx

Безусловно есть альтернативные решения! Можно, например, написать на С/С++ маленькую программку, контроллируя выделяемую ей (ею) память буквально до килобайта, и с помощью программы отдавать файлы, но прежде всего, я хотел бы рассмотреть самое стандартное (простое) решение.

Это сообщение отредактировал(а) WolfAlone — 29.8.2012, 14:27

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

Профиль
Группа: Комодератор
Сообщений: 6815
Регистрация: 13.4.2007
Где: СПб

Репутация: 96
Всего: 383

ksnk
Дата 29.8.2012, 14:48 (ссылка) | (нет голосов) Загрузка .
Цитата(WolfAlone @ 29.8.2012, 14:23 )
прежде всего, я хотел бы рассмотреть самое стандартное (простое) решение.
Цитата(WolfAlone @ 29.8.2012, 11:37 )
Номинальная нагрузка на сервер (для расчёта) 1000 одновременно скачиваемых файлов 24х7.

оно не подойдет

Нормально по производительности такие задачи решаются только созданием временных «жестких» ссылок на скачивание файла. Сама скачка файла передоверяется в дальнейшем какому-нибудь nginx’у.

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

WolfAlone
Дата 29.8.2012, 16:18 (ссылка) | (нет голосов) Загрузка .
Цитата(ksnk @ 29.8.2012, 14:48 )
Нормально по производительности такие задачи решаются только

Вот это самый интересный момет, который я как раз и пытаюсь выяснить. При скачивании более одного файла нагрузка возрастает не линейно?

Какая примерно нагрузка (потребление ресурсов машины) будет при одновременном скачивании 10-и, 100-а файлов, размером допустим 10Мб? Где примерно пролегает граница, между тем когда «разумно» отдавать файлы скриптом и когда такой подход уже не оптимален, в виду каких-то причин? Как определить эту «границу»?

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

Профиль
Группа: Комодератор
Сообщений: 6815
Регистрация: 13.4.2007
Где: СПб

Репутация: 96
Всего: 383

1000 одновременно отдаваемых скриптом файлов — это 1000 одновременно запущенных версий PHP и 1000 соответствующих им веток апача, обслуживающих связь с клиентской машиной. Насколько бы оптимально не были выставлены запросы на память и ресурсы — никакой одинокий сервер с такой нагрузкой не справится. И апач и php довольно тяжелые продукты и запускать их в массовом порядке не рекомендуется.

ksnk
Дата 29.8.2012, 16:32 (ссылка) | (нет голосов) Загрузка .
Цитата(WolfAlone @ 29.8.2012, 16:18 )
Где примерно пролегает граница, между тем когда «разумно» отдавать файлы скриптом и когда такой подход уже не оптимален, в виду каких-то причин? Как определить эту «границу»?

Профиль
Группа: Завсегдатай
Сообщений: 1010
Регистрация: 16.9.2008
Где: Рай

Репутация: 1
Всего: 5

WolfAlone
Дата 29.8.2012, 16:41 (ссылка) | (нет голосов) Загрузка .
Цитата(ksnk @ 29.8.2012, 16:32 )
Зачем определять такую границу?

И сказал Бог: «Тогда я построю свой мир с блэк-джеком и шлюхами!»

Ф топку Ubuntu, Debian наше фсё!

(с) Евгений Вольф

Профиль
Группа: Завсегдатай
Сообщений: 1670
Регистрация: 19.11.2006
Где: Voronezh

Репутация: 41
Всего: 60

Sanchezzz
Дата 29.8.2012, 16:55 (ссылка) | (нет голосов) Загрузка .
Код
location / <
rewrite ^/download/(.*)/(.*) /download.php?q=$2&key=$1 last;
>

location /files <
root /home/host/www;
internal;
>

далее создаем скрипт логики который будет это дело обслуживать и перенаправлять куда надо на файл

$path = $_GET[«q»]; // имя файла или какой другой индикатор который поможет нам узнать имя файла
$key = $_GET[«key»]; //секретный ключ который мы получили ввели и проверили где то ниже верный не верный

$file = dirname(__FILE__).’/files/’.$path;
if (file_exists($file)) <
header(«Content-type: «.mime_content_type($file));
header(«Content-length: «.filesize($file));
header(«X-Accel-Redirect: /files/» . $path);
>

Полноценная отдача файлов для не разумных пределов через чистый PHP долго не живет

Это сообщение отредактировал(а) Sanchezzz — 29.8.2012, 17:15

Отдача файла через PHP

Как то давно дорабатывал один скрипт для отдачи файла через PHP и поддержку докачки.
Вот код:

Проблема в том, что после установки PHP5.2.0(был 4.4.4) и апача 1.3.37(был 1.3.33) перестало иногда передаваться имя файла, хотя не всегда. Отдается файл download.php. Х

Цитата (tolik777 @ 9.1.2007, 14:28 )
$downloads[‘orders_products_filename’];

P.S. с кодом не разбирался

Блин только сейчас увидел, что запостилось только половина моего сообщения. Куда то вторая часть потерялась.

$downloads[‘orders_products_filename’]; — это из БД береться

А писал я там, что $downloads[‘orders_products_filename’] — имеет вид «каталог/имя файла», к примеру: arx/zenon.rar и т.п. Может быть из-за этого?

В приведенном выше коде не пойму зачем header(‘Connection: close’); ?
И обязательно ли ETAG использовать: header(‘ETag: «‘ . $etag . ‘»‘); ?

Цитата (tolik777 @ 9.1.2007, 15:33 )
Может быть из-за этого?
Цитата (tolik777 @ 9.1.2007, 15:33 )
В приведенном выше коде не пойму зачем header(‘Connection: close’); ?

И обязательно ли ETAG использовать: header(‘ETag: «‘ . $etag . ‘»‘); ?

Незнаю, надо в rfc копаться. Может для дозакачки нужно.,

отдача файла с помощью PHP

_Дмитрий_

Новичок

Есть ссылка, в которой href указывает на реальный файл на сервере, но запрещенный для прямой скачки
ссылка

ajax-ом отправляю href на сервер
$(«.JS_send»).on(‘click’, function(e) <
var url = $(this).attr(‘href’);
e.preventDefault();
$.ajax( <
url: ‘/url’,
type: ‘POST’,
dataType: ‘JSON’,
data: ‘url=’+url>);
>)

на сервере ловлю эту ссылку на файл в переменную $file и пытаюсь отправить этот файл для скачивания

герр M:)ller

_Дмитрий_

Новичок

«скачки» от слова скачивать
запрет делаю в .htaccess, но не в этом суть

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

просто мне надо отдавать файл на «скачку» не перегружая страницу

c0dex

web.dev 2002-.

Переделываем ссылку в что-то вида ссылка

Далее все по накатанному через readfile и заголовки. Ясен пень, что как надо без перезагрузки. При чем тут ваще AJAX?

_Дмитрий_

Новичок

да с сылкой все нормально, файл читается, содержимое возвращается
AJAX при том чтоб без перезагрузки.

ладно счас набросаю примерчик, мож непонятно объясняю

c0dex

web.dev 2002-.

_Дмитрий_

Новичок

отправить это «/public/backup/2013.08.01_19-14-14.zip» на сервак
на серваке взять этот файл и выдать его в браузер но только чтоб было сообщение «сохранить или открыть», а не просто содержимое

счас получается вернуть только содержимое

_Дмитрий_

Новичок

c0dex

web.dev 2002-.

_Дмитрий_

Новичок

да с файлом проблем нет, он читается, но отдается браузеру в виде содержимого
Заголовки ответа
HTTP/1.1 200 OK
Date: Fri, 02 Aug 2013 10:09:32 GMT
Server: Apache/2.2.22 (Win32) mod_ssl/2.2.22 OpenSSL/1.0.1c PHP/5.3.13
X-Powered-By: PHP/5.3.13
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-reval > Pragma: no-cache
Content-Disposition: attachment; filename=test.html
Content-Length: 1297
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8

ОТВЕТ

.
и так далее содержимое файла

а мне надо чтоб запрос на сохранение выскочил

герр M:)ller

_Дмитрий_

Новичок

_Дмитрий_

Новичок

загаловки:
из строки браузера
HTTP/1.1 200 OK
Date: Fri, 02 Aug 2013 10:16:24 GMT
Server: Apache/2.2.22 (Win32) mod_ssl/2.2.22 OpenSSL/1.0.1c PHP/5.3.13
X-Powered-By: PHP/5.3.13
Accept-Ranges: bytes
Content-Length: 31
Content-Disposition: attachment; filename=my_super_file.txt
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/octet-stream

аяксом
Apache/2.2.22 (Win32) mod_ssl/2.2.22 OpenSSL/1.0.1c PHP/5.3.13
PHP/5.3.13
Thu, 19 Nov 1981 08:52:00 GMT
no-store, no-cache, must-reval > no-cache
Accept-Ranges bytes
Content-Length 31
Content-Disposition attachment; filename=my_super_file.txt
Keep-Alive timeout=5, max=100
Connection Keep-Alive
Content-Type application/octet-stream

c0dex

web.dev 2002-.

_Дмитрий_

Новичок

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

если я по ссылке укажу например href=»download.php?url=/fkjdfkfdjfdlk/test.html» тот перейду на эту download.php и потеряю сгенеренные ссылки

можно конечно ссылки сохранять где нибудь, но это другой разговор
мне нужно в текущих условиях понять почему ответ сервера с этими заголовками так себя ведет

c0dex

web.dev 2002-.

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

Контролируемая отдача файла.

Пробовали делать через хендлеры: файл записывали на диск и из хендлера читается в выходной поток.

1) Сессия в хендлерах пустая. Неизвестно, кто пытается качать

If your handler will access session state values, it must implement the IRequiresSessionState interface (a marker interface with no methods).

2) Они сразу выдают весь файл в поток и освобождают ресурсы.

Отдача файлов c помощью Nginx

Nginx часто используется только для отдачи файлов. Например, файловый хостинг. На что следует обратить внимание для оптимальной настройки Nginx под отдачу крупных файлов?

Чуть подробнее о характеристиках нашей задачи:

  • Нужно параллельно отдавать много файлов
  • Большинство файлов крупные (более 5 Мб)
  • Мы отдаем потоковое видео и нам необходимо обеспечить комфортный просмотр
  • Мы отдаем звуковые файлы и необходимо обеспечить комфортное прослушивание

Что следует настроить?

sendfile

Как обычно работает Web сервер, при передаче файла:

  1. открывается исходный файл (на диске)
  2. открывается файл назначения (сетевое соединение)
  3. читается блок данных, копируется в буфер и передается по назначению, пока не достигнут конец файла
  4. закрываются оба файла

Это означает, что происходит дополнительное копирование, которое вынужден делать Web сервер. В этом случае сервер делает системные вызовы read и write. Системный вызов sendfile служит как раз для того, чтобы избежать излишнего копирования и обеспечить прямую передачу файла. Включайте эту опцию (всегда):

tcp_nopush

Директива разрешает или запрещает использовать опции TCP_NOPUSH во FreeBSD или TCP_CORK в Linux. «tcp_nopush on» полезно для sendfile(), nginx в этом случае выводит данные полными пакетами. После того, как весь запрос обработан, TCP_CORK/TCP_NOPUSH выключается, что приводит в сбросу последнего неполного пакета.

tcp_nodelay

Директива разрешает или запрещает использовать опцию TCP_NODELAY (при переходе соединения в состояние keep-alive). Перед переходом соединения в keepalive nginx выводит данные вызовами writev() достаточно большими порциями для заполнения пакета («postpone_output 1460»), поэтому данные должны уходить без задержек и TCP_NODELAY не нужен. А вот с последним неполным пакетом может случится небольшая задержка, если соединение не закрывается. Для этого и нужно включить TCP_NODELAY:

directio

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

# Файлы более 10Мб Nginx будет читать с диска минуя операционный кеш

expires

Поскольку мы имеем дело со статическими файлами, и есть вероятность того, что один и тот же пользователь сможет несколько раз запросить один и тот же файл, следует включить клиентское кеширование. Это достигается установкой опции «expires max», которая отправит браузеру нужные заголовки:

limit_rate

Это достаточно важная опция. Она позволяет ограничить скорость отдачи файлов. В том случае, если Вы отдаете потоковое видео, либо музыку, Вам следует установить ограничения на скорость отдачи. Это позволит сэкономить канал и обслужить больше клиентов:

# Ограничиваем скорость отдачи до 196Кб/с

Эта опция работает только в рамках одного запроса, а не клиента. Если Вы хотите поставить ограничение на клиента, следует использовать переменную:

В Nginx также есть возможность установить порог отдачи, после которой ограничение войдет в силу. Также имеет смысл для потокового медиа (в этом случае первая указанная часть будет отдаваться без ограничений):

# Ограничение скорости отдачи будет накладываться после 1Мб

Файлы, которые запрашиваются очень часто имеет смысл кешировать в памяти.

Самое важное

Небольшая настройка Nginx специально для отдачи файлов позволит лучше использовать ресурсы каждого сервера.

Основы оптимизации работы Web сервера

Примеры использования Lua в Nginx для решения стандартных задач

Использование Nginx, как кэширующего сервера

Кэширование динамических страниц с помощью SSI

Распространенные ошибки конфигурации Nginx, подводные камни и лучшие практики

Как настроить Nginx на максимальную эффективность

Как собирать статистику Nginx при помощи встроенного модуля и Zabbix

Методы улучшения производительности TLS/SSL

Примеры применения Javascript в Nginx’e

Кэширование динамических сайтов с помощью ESI

Как использовать Varnish для кэширования HTTP запросов

Настраиваем Apache на максимальную производительность

4 шага и 9 инструментов для анализа нагрузки на сервер

Как настроить веб-сервер Nginx для работы с Magento

Правильное использование ngx.req.get_body_data() для чтения тела запроса

Эффективный механизм записи данных из Nginx’a прямо в Clickhouse минуя промежуточные узлы

Главные возможности нового протокола HTTP/2

Работа приложения с несколькими бэкендами при помощи Nginx

Где находится nginx.conf и пример настроек

Причины возникновения ошибки Ошибка 502 bad gateway в Nginx и методы исправления

Причины и методы исправления ошибки Gateway Timeout, Nginx

Как исправить ошибку Primary script unknown в Nginx

Как исправить ошибку 405 Not Allowed в Nginx

Включение и использование log-файлов для проверки работы Nginx

Контроль доступа к файлам с использованием Apache mod_rewrite и эмуляции сессий в PHP.

О том, как сделать доступ к большим файлам только после авторизации посетителя на сайте.

Введение.

Для контролируемого доступа посетителей на сайт обычно используют авторизацию при помощи PHP, с использованием сессий, или Web-сервера Apache, с использованием .htpasswd.

Авторизация средствами PHP позволяет гибко управлять правами доступа пользователей и их учётными записями.

PHP авторизация.

Когда посетитель пытается зайти на страницу, интерпретатор PHP проводит авторизацию и по её результатам решает выводить или не выводить информацию

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

Скрипт должен быть прослойкой между файлом и посетителем, т.к. если у посетителя будет прямая ссылка на файл, то он обратится к нему в обход PHP. Чтобы этого не произошло, скрипт считывает содержимое файла и «через себя» отправляет его на вывод. Такое может работать только на маленьких файлах, которые PHP может обработать, не затратив много серверных ресурсов и не использовав лимит времени. Если требуется отдавать посетителю большие файлы, то этот способ авторизации не будет функционировать.

Контроль доступа к файлам с использованием Apache mod_rewrite и эмуляции сессий в PHP.

Этот способ авторизации предназначен для контроля доступа к файлам для скачивания. Для контроля доступа пользователей к страницам сайта используйте PHP авторизацию при помощи сессий.

Чтобы контролировать доступ посетителей к файлам и при этом сохранить гибкость управления эти процессом, можно разделить обязанности между Apache и PHP. Apache будет отдавать файл, а PHP управлять правами доступа, но надо создать механизм связи между ними.

Авторизация пользователя проходит в два этапа: эмуляция сессии и определение mod_rewrite прав доступа к файлу. Сессии эмулируются для того, чтобы mod_rewrite мог получить статус авторизации посетителя.

Подготовка.

Файлы, для доступа к которым требуется авторизация, разместим в папке «download».
Для технологических файлов авторизации создадим папку «auth_files».

Первый этап — эмуляция сессии.

После аутентификации пользователя скрипт записывает код доступа в cookie посетителю и создаёт на стороне сервера файл, с именем, равным коду доступа.

setcookie(«fileaccess», «QweRty123», time()+86400);
fopen(«/site_folder/auth_files /QweRty123», «w+»);
//header(«Location: downloadFile.avi»);

QweRty123 — произвольный код доступа

Второй этап — mod_rewrite и доступ к файлу

Модуль Apache — mod_rewrite может получить cookie пользователя с использованием %.

Для этого надо создать файл .htaccess который будет содержать следующие строки:

Options -Indexes
RewriteEngine on
RewriteBase /
# Получение кода доступа из cookie
RewriteCond % !fileaccess=([a-zA-Z0-9]+)
# Код доступа хранится в переменной %1.
# Строка ниже означает: Если требуемого файла не существует, то…
RewriteCond /site_folder/auth_files /%1 !-f
# То запретить доступ по URL, которые содержат строку «download/» (Ответ 403)
RewriteRule download/.* — [F]

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

В результате, скачивание файла происходит напрямую с сервера, а не через PHP скрипт. Так же пользователь имеет возможность докачивать файл.

Недостатком данного метода является необходимость очищать папку для технологических файлов «auth_files/» .

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

Ниже приведена схема работы авторизации:

Похожее

Контроль доступа к файлам с использованием Apache mod_rewrite и эмуляции сессий в PHP.: 21 комментарий

Интересная схема. Я бы даже сказал MR-надстройка.

Как видится, полезна она для проектов, связанных с хранением и раздачей файлов.

А вот если cookie отключены? =)

Если отключены cookie, тогда пользователю не повезло… (Причём, ему не повезёт на многих сайтах.)

Подругому отдавать большие файлы используя PHP+Apache не получится.

PS: можно это сделать используя nginx, т.к. там есть нужный функционал для доступа пользователей к большим файлам.

PPS: Можно попробовать обойти проблему с cookie использовав обычные сессии (идентификатор сессии — это код доступа), но т.к. идентификатор сессии может передаваться как с использованием GET, так и с использованием cookie, то такое разнообразие может увеличить количество возможных багов.

Без кукисов вообще делать нечего в современном Интернете, так что нормально ��

Может быть кто-то поделится другими способами контроля доступа к очень большим файлам? Будет интересно!

Хорошая работа!
+ в закладки.

«Подготовка.
Фалы, для доступа к которым требуется авторизация, положим в папку «download».»

Очепятка в тексте: «фалы»(В качестве фалов используются металлические, синтетические или пеньковые тросы. Фал испытывает большую нагрузку при работе. Относится к бегучему такелажу)

И «положим в папку» как то коряво звучит, «разместим в папке» думаю грамотнее будет.

«И “положим в папку” как то коряво звучит, “разместим в папке” думаю грамотнее будет.»
Спасибо, исправил.

«Очепятка в тексте: “фалы”» — я заметил, что у меня Й на клавиатуре не всегда нажимается ��

Спасибо большое за инфу! Очень помогло!

Раздавать статику апачем — зло. Раз уж раздаете, значит по ресурсам еще и близко к потолку не подобрались. Значит можно в отдельной ветке апача и php-скрипт держать, который будет читать из файла порциями по 8К (объем tcp-окна) и отдавать юзеру. Особо не убудет ��

Если уж так нравится mod_rewrite, то

1) Файлов в папке auth_files прибывает очень быстро (это кол-во файлов в раздаче умножить на кол-во скачивающих юзеров). Когда их станет много, вы увидите затыки перед началом скачки — по 8, 20 секунд, а то и до таймаута недалеко..

2) В доке по мод_рерайту есть отличный пример про Dynamic RewriteMap. Там правда пример на перле, но я думаю разберетесь — это именно ваш случай и именно в официальной доке.

Но повторюсь, раздавать файлы апачем, да еще и с включенным мод_рерайтом — слегка странно. Если это для вас работает, то можно еще 10 разных способов понапридумывать, примерно таких же по эффективности. ��

«Раздавать статику апачем — зло.»
а раздавать модулем апача — это хорошо? Интересно. Или вы предлагаете написать сервер на пхп?

«1) Файлов в папке auth_files прибывает очень быстро (это кол-во файлов в раздаче умножить на кол-во скачивающих юзеров).»
Вы плохо читали. Файлов столько, сколько на данный момент активных пользователей (и ни какого умножения).

«2) В доке по мод_рерайту есть отличный пример про Dynamic RewriteMap.»
Я не хочу, чтобы mod_rewrite что-то парсил и искал строки в файле. Я не хочу записывать эти строки в этот файл и удалять устаревшие записи. Я думаю, что описанный мной метод минимизирует затраты.

Не приспособлен апач для раздачи больших файлов. Лучше всех приспособлен nginx, но и он не без ложки дегтя — все процессы одновременно юзают все винты, разделить не получается и raid в данном случае не помощник. Но даже несмотря на это, все равно — на всех больших системах апач ставится как бекенд, а собственно веб-серверством занимается nginx, или lighttpd, или своя самописная приблуда, хоть бы даже и на перле. И уж что-что, а заставлять громадный апач форкаться, чтобы раздавать статические файлы, загружая при этом все модули, расширения, PHP, модреврайт (еще тот тормоз!), дергать локаль каждый раз..

Вот по этому я и говорю — раздавайте скриптом и не мучайтесь. Юзайте cgi, форкайте его отдельным процессом — и пусть себе висит. На двухгиговом серваке таким образом легко можно держать под 1000 коннектов — просто, удобно и без заморочек. С нормальной авторизацией, с realtime статистикой и своим распределением нагрузки. Справится ли с таким количеством коннектов апач, если заставлять раздавать его с модреврайтом, как вы предлагаете — честно говоря не знаю. Но главное — зачем пробовать? ��

Признайтесь, вы наверное php запускали как модуль?

Как ни странно, но я с fogx соглашусь. Дельные вещи об Apache говорит. Для 1000 коннектов он действительно тяжеловесен.

fogx, а можно купить себе 100 серверов, на каждый положить по одному файлу и будет совсем хорошо. Не впадайте в крайности.

«Справится ли с таким количеством коннектов апач, если заставлять раздавать его с модреврайтом» — что значит раздавать с mod_rewrite? Модуль отработал один раз, проверил, можно ли пускать пользователя к файлу или нет, к процессу раздачи он ни какого отношения не имеет.
Если бы я мог, то использовал бы nginx, но такой возможности у меня нет (думаю, как у большинства).

«с realtime статистикой» — ну вот, ещё и статистику в общую кучу.

А вот на счёт cgi наверно соглашусь. Звучит неплохо… Конечно хз, сейчас н могу всё понять, т.к. болею ��

Дело в том, что простая строка в хтаксесе RewriteEngine ON уже заставляет апач делать кучу проверок при каждом GET (а на 700 меговый файл у вас будет не один GET, а много). Так что к процессу раздачи отношение самое непосредственное. Почему именно модрерайт настолько сказывается на производительности, я и сам не понимаю. Но факт..

А realtime статистику в кучу я приплел просто потому, что раздавать скриптом — крайне УДОБНО. Можно самому дропать медленные конекшены (или бесплатных юзеров �� )
можно динамически выделять память под буфер в зависимости от скорости, ограничивать эту скорость, распределять нагрузку на винты и т.д. Короче плюсов масса.

И кстати собственный веб-сервер — это далеко не так страшно, как многим почему-то кажется. Вам же не надо повторять функционал апача, наоборот, вы его как раз и делаете, чтобы он был маленький и имел только самое необходимое. Любой двадцатикилобайтный троян имеет на борту встроенный веб-сервер, который умеет раздавать файлы, делать директори листинг, авторизацию и еще всякое по мелочи. Это действительно просто. Добавить сюда обработки If-Modified-Since, Range, убрать директори листинг, ибо он нафиг не нужен, ну и собственно все.. Дальше смотреть по задачам. Но главное — у вас полная свобода действий! А чуть какой нестандартный запрос пришел — так вот он апач под боком, пусть разбирается ��

Свой сервер — это не страшно, это очень удобно, но его нет :).

Индивидуальные настройки и окружение это просто супер, но, по факту, у меня есть apache и php на почти стандартном хостинге.

Для меня, использованеи mod_rewrite наиболее приемлимый способ.

Что будет более ресурсоёмким, apache или cgi:php-скрпит, я не знаю… И я не особо представляю, как можно это проверить.

Сегодня прпотестировал на Winnows.

1) Если запускать PHP как CGI, то он занимает

4,5Мб памяти.
2) Каждое обращение к CGI:PHP будет провоцировать создание нового процесса.

fogx писал, что 1000 коннектов уронят Apache, и что надо использовать CGI скрипт.

Но 1000 коннектов, это 1000 одновременно запущеных процессов PHP, что в итоге вытекает в

Но я о таких нагрузках не говорил, т.к. если есть такая посещаемость, то проблему надо решать совершенно другими способами!

Но даже при 100 одновременных скачивания файлов будет занято

450Мб памяти, что мне кажетсо слишком. Уж лучше пусть висит один apache.

На смом деле такую задачу, можно реализовать еще проще ��
Вы создаете символические ссылки на большие файлы, запоминая их имена в базе и выдаете авторизированным пользователям, ну и конечно заменяете их, скажем, раз в сутки. Это конечно даст возможность качать по символической ссылке, но не более, чем период «переименования» ��

Дядя Ваня: Отдачу файлов на основе тухлых ссылок можно использовать как часть защиты от хотлинкинга, но не как авторизацию, т.к. она не обеспечивает возможность скачивания файла исключительно авторизованными пользователями.

Andrey Shatrov: А почему нельзя выдавать ссылки, хранящиеся в базе только авторизированным пользователям?

Дядя Ваня: Авторизованный пользователь может опубликовать ссылку (например в своём твиттере) и файл сможет качать кто угодно.

А как сделать то-же, но чтоб отдать файл по прямой ссылке только при наличии в PHP пользовательской переменной и с определенным значением?

Добавить комментарий