Привет, мои маленькие прогродрузья. Сегодня я расскажу вам то, о чем, как выяснилось, не знают многие веб-прогроммисты. Совсем недавно я переписал свой storage, потому что предыдущий не устраивал меня по нескольким причинам: он был написан мною давным давно на РНР, и по началу вполне устраивал меня, но времена меняются, про РНР я давно забыл, а память, отжираемая apache, стала слишком дефицитным ресурсом, потому что стала требоваться еще многим более важным процессам на сервере. Потому примерно в то же время я перешел на nginx и с тех пор жил счастливо. Пришлось, правда, поебаться со сборкой php-fpm, по крайней мере тогда (2 года назад) под debian даже готовых пакетов с этим добром не было (либо были под древние версии, под etch и sarge). Потому как работает PHP на моем сервере мне всегда не нравилось и я начал избавляться от таких проектов. После глобальной чистки оставалось всего 2 необходимых мне сервиса - это тот самый storage и phpmyadmin. Теперь остался один, да и тот в основном для работы.
Так к чему это я веду. В первой версии сторадж представлял из себя просто файл index.php, который сканировал папку, в которой лежал, разбивал все файлы по категориям, и выводил списком. Скачивались файлы, естественно, по давней традиции тупо по хотлинку. От этой практики я решил тоже отказаться, что уж говорить, 4 года назад я был настолько туп, что первые пару дней даже не фильтровал заливку туда файлов с расширением *.php, а никакого chroot там в помине не было. В общем во второй версии я решил завести для всего этого дела небольшую БД (sqlite вполне подошел) и переписать-таки все на питоне.
Теперь при загрузке файла и всяких очевидных проверок на коллизии имен, он клался в специальную папочку, а запись о нем, сгенерированный ID и еще куча полезных параметров, клались в sqlite. Одной фичей, подсмотренной в одном платном сервисе, был просмотр истории скачиваний, его я тоже захотел себе. Поэтому когда пользователь хочет скачать файл: делается запрос в БД на существование записи и изъятие полного имени файла (файл при заливке немного переименовывается, а при коллизиях - тем более), если все удачно - запрос к диску на проверку существования файла (ну мало ли что бывает), когда и тут все хорошо - запись в историю доступа и, собственно, отдача файла по http.
Стандартно, прямолинейно, ничего нового. Любой, кто работал с файлообменниками, переживал еще большую анальную боль при попытке скачать файл. Сюда можно добавить еще таймеры, капчи, генерацию "временных эксклюзивных url", кнопку "БАБЛО", etc. У нас случай простейший и написать такое под силу даже Студенту АВТФ, успешно сдавшему лабораторную работу по курсу ООП и БД. Но остается один вопрос, описанный в теме поста: как отдать файл пользователю? Я расскажу несколько способов со смешными кодовыми названиями, которые я придумал только что.
Открыть папку с файлами в паблик через nginx. После всех этих обработок просто редиректнуть юзера на хотлинк на файл.
Плюсы:
Минусы:
Собственно, этого минуса нам уже достаточно.
Похож на предыдущий способ открывания папки в паблик, но с добавлением проверки по рефералу. Если пришел по хотлинку - файл не давать.
Плюсы:
Минусы:
А что если про рефералу не банить, а перестроить систему так, чтобы nginx редиректил при неправильном реферале на скрипт, который затем опять редиректил на прямой линк.
Плюсы:
Минусы:
Да ну вас нахуй, давайте читать файл и отдавать его сразу из скрипта. Я по-другому не умею. Я буду читать гигабайтный файл в RAM через open() и затем отдавать как обычный Response(), да еще и заюзаю костыль для определения правильного mimetype.
Плюсы:
Минусы:
Ладно, забудем про рефералы, будем помнить про способ прострелить ногу через file.open и обратимся к профессионалам. Сразу несколько опрошенных мною человек рекомендуют написать модуль для nginx, который будет обрабатывать такие запросы и... WHUT? МОДУЛЬ ДЛЯ NGINX?
Плюсы:
Минусы:
Сюда можете придумать еще несколько смешных способов прострелить себе ногу.
Плюсы:
Минусы:
Не буду дальше утомлять вас несмешными шутками и скажу волшебное слово: X-Accel-Redirect. Для тех, кому лень открывать ссылку, процитирую:
X-accel allows for internal redirection to a location determined by a header returned from a backend. This allows you to handle authentication, logging or whatever else you please in your backend and then have Nginx handle serving the contents from redirected location to the end user, thus freeing up the backend to handle other requests. This feature is commonly known as X-Sendfile.
Да тут же английским по белому написано как раз то, что нам нужно. Да оно еще и встроено в nginx. Еще, кстати, для пользователей lighttpd (может быть и других серверов), упомянуто, что это аналог их X-Sendfile.
Для самых маленьких и тупых я покажу как это работает.
Структура приложения. Стандартная однофайловая херня.
Для начала сам скрипт, обрабатывающий запросы на скачивание файла. Новый storage написан на werkzeug, но это должно вас мало интересовать, потому что основную часть я все-таки вырежу:
def on_download(self, request, file_id):
# получить имя файла из бд
file = ...
# проверить существование
if file:
# сделать запись в хистори
...
# редиректнуть на прямую скачку файла
response = Response()
response.headers["X-Accel-Redirect"] = "/path_to_files/%s" % file.name
return response
else:
# вернуть ошибку
Простой метод из которого вырезано все не нужное. Мы делаем ровно то, что описывали в начале, а затем, если все хорошо, делаем... да-да, редирект. Вот только обычный редирект, как мы помним, делается через статус-код 3xx хедер Location, а тут через обычный статус 2хх и хедер X-Accel-Redirect. То же на РНР:
header("X-Accel-Redirect: /path_to_file/" . $filename);
Поле данных Response можно оставлять пустым, насрать. Когда запрос обрабатывается, он отдается впередистоящему nginx, который в свою очередь отлавливает заголовок X-Accel-Redirect, удаляет его и дальше делает все сам. Не подменяя URL он заменяет ответ своим. Всякие add_header и gzip_static тоже работают. Кому нужно, можете насильно прописать content_type и отдавать, например, картинки и текст как файлы. А скрипт работу закончил и освободил воркера, как уже отдать файл пользователю решит nginx. У него это получается лучше всего. Как поговаривают в этих ваших интернетах nginx в таком случае даже сам умеет управлять докачкой. Для него это обычный статик-файл.
А как настроить сам nginx? Нужно осилить написать всего 1 лишнюю строчку в location. Как-то так:
location /path_to_files {
root /full/path/to/files;
internal; # <---- вот эту
}
Этот location очень похож на обычный, только вот если вы попробуете обратиться к нему напрямую, то получите 404. Слово internal говорит, что эти файлы являются внутренними и доступны только для самого сервера.
И чо, это работает? Ага:
Ну, какбэ так и ожидалось. По похапэ у меня вообще работает 2006 года библиотека с этим X-Accel-Redirect не моя и норм отдает через нжингс 3-4 гб файлы (ну я давал ссылку в твиттере). А так - окок. P.S. Как "Request Headers" можно было перевести как "Верхние колонтитулы"? >.<
И да... http://home.progger.ru/internets/NotBad.png
ВОТ ВЕДЬ ХУЙНЯ!
Nks, > ВОТ ВЕДЬ ХУЙНЯ! Да-да, у меня в разрешенных тегах нет картинок :) > Как "Request Headers" можно было перевести как "Верхние колонтитулы"? >.< Литературно!
V@s3K, >Да-да, у меня в разрешенных тегах нет картинок :) И прально. А то щас бы въебашил бы тебе тут 2000х2000 и срал бы кирпичами :)
300гет, congratulations!
а, в твиттере уже сказали v_v
что-то любой файл отображается как текст, что не так?
ReDetection, у тебя или у меня? Не так - это косяк с заголовком ContentType. Посмотри какой он.
ReDetection, ага, увидел, у меня. В номинации "IE 6 года" в этом году точно победит firefox. Я уже выучил все методы создания костылей для этого браузера.
Ухты, посмотрите, полевная штука.
А по каким соображениям не заопенсорсил?
vadipp, плохой код. Но конкретно часть с учетом и раздачей файлов перекочевала в i.vas3k.ru, а он на гитхабе есть.