Хабрахабр

[Из песочницы] Раздаем файлы с Google Drive посредством nginx

Предыстория

5тб данных, да еще и обеспечить возможность скачивания их обычными пользователями по прямой ссылке. Так уж случилось, что нужно мне было где-то хранить более 1. 5тб картинок без loseless сжатия поместить не удастся. Поскольку традиционно такие объемы памяти идут уже на VDS, стоимость аренды которых не слишком вкладывается в бюджет проекта из категории «от нечего делать», а из исходных данных у меня был VPS 400GB SSD, куда при всем желании 1.

Сказано — сделано. И тут я вспомнил про то, что если удалить с гугл-диска хлам, вроде программ, которые запустятся только на Windows XP, и прочие вещи, которые кочуют у меня с носителя на носитель с тех пор, когда интернет был не таким быстрым и вовсе не безлимитным (например, те 10-20 версий virtual box вряд ли имели какую-то ценность, кроме ностальгической), то все должно очень даже хорошо вместиться. И вот, пробиваясь через лимит на количество запросов к api (кстати, техподдержка без проблем увеличила квоту запросов на пользователя за 100 секунд до 10 000) данные резво потекли в место своей дальнейшей дислокации.

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

Сначала это был скрипт на AmPHP, но меня не устроила создаваемая им нагрузка (резкий скачек на старте до 100% потребления ядра). Тут я, ей-богу, пустился во все тяжкие. Пробовал даже написать маленький сервис на Rust, и работал он довольно резво (удивительно даже то, что он работал, с моими то познаниями), но хотелось большего, да и кастомизировать его было как-то непросто. Потом в дело пошел враппер curl для ReactPHP, который вполне вписывался в мои пожелания по поедаемому количеству тактов CPU, но давал скорость вовсе не такую, как мне хотелось (оказалось, что можно просто уменьшить интервал вызова curl_multi_select, но тогда мы имеем аналогичную первому варианту прожорливость). Кроме того все эти решения как-то странно буфферизировали ответ, а мне хотелось отслеживать момент когда закончилась загрузка файла с наибольшей точностью.

Пока однажды мне не пришла в голову замечательная по своей бредовости идея: nginx в теории может то, что я хочу, работает резво, да еще позволяет всякие извращения с конфигурированием. В общем какое-то время это косо-криво, но работало. И спустя пол дня настойчивых поисков родилось стабильно работающее уже несколько месяцев решение, которое отвечало всем моим требованиям. Надо попробовать — вдруг получится?

Настраиваем NGINX

# Первым делом создадим в конфигах нашего сайта отдельную локацию.
location ~* ^/google_drive/(.+)$ { # И закроем её от посторонних глаз (рук, ног и прочих частей тела). internal; # Ограничим пользователям скорость до разумных пределов (я за равноправие). limit_rate 1m; # А чтоб nginx мог найти сервера google drive укажем ему адрес резолвера. resolver 8.8.8.8; # Cоберем путь к нашему файлу (мы потом передадим его заголовками). set $download_url https://www.googleapis.com/drive/v3/files/$upstream_http_file_id?alt=media; # А так же Content-Disposition заголовок, имя файла мы передадим опять же в заголовках. set $content_disposition 'attachment; filename="$upstream_http_filename"'; # Запретим буфферизировать ответ на диск. proxy_max_temp_file_size 0; # И, что немаловажно, передадим заголовок с токеном (не знаю почему, но в заголовках из $http_upstream токен передать не получилось. Вернее передать получилось, но скорей всего его где-то нужно экранировать, потому что гугл отдает ошибку авторизации). proxy_set_header Authorization 'Bearer $1'; # И все, осталось отправить запрос гуглу по ранее собранному нами адресу. proxy_pass $download_url; # А чтоб у пользователя при скачивании отобразилось правильное имя файла мы добавим соответствующий заголовок. add_header Content-Disposition $content_disposition; # Опционально можно поубирать ненужные нам заголовки от гугла. proxy_hide_header Content-Disposition; proxy_hide_header Alt-Svc; proxy_hide_header Expires; proxy_hide_header Cache-Control; proxy_hide_header Vary; proxy_hide_header X-Goog-Hash; proxy_hide_header X-GUploader-UploadID;
}

Краткую версию без комментариев можно увидеть под спойлером

location ~* ^/google_drive/(.+)$ { internal; limit_rate 1m; resolver 8.8.8.8; set $download_url https://www.googleapis.com/drive/v3/files/$upstream_http_file_id?alt=media; set $content_disposition 'attachment; filename="$upstream_http_filename"'; proxy_max_temp_file_size 0; proxy_set_header Authorization 'Bearer $1'; proxy_pass $download_url; add_header Content-Disposition $content_disposition; proxy_hide_header Content-Disposition; proxy_hide_header Alt-Svc; proxy_hide_header Expires; proxy_hide_header Cache-Control; proxy_hide_header Vary; proxy_hide_header X-Goog-Hash; proxy_hide_header X-GUploader-UploadID;
}

Пишем скрипт для управления всем этим счастьем

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

<?php # Токен для Google Drive Api.
define('TOKEN', '*****'); # ID файла на гугл диске
$fileId = 'abcdefghijklmnopqrstuvwxyz1234567890'; # Опционально, но так как мы не передаем никаких данных - почему бы и нет?
http_response_code(204); # Зададим заголовок c ID файла (в конфигах nginx мы потом получим его как $upstream_http_file_id).
header('File-Id: ' . $fileId);
# И заголовок с именем файла (соответственно $upstream_http_filename).
header('Filename: ' . 'test.zip');
# Внутренний редирект. А еще в адресе мы передадим токен, тот самый, что мы получаем из $1 в nginx.
header('X-Accel-Redirect: ' . rawurlencode('/google_drive/' . TOKEN));

Итоги

Да хоть из telegram или VK, (при условии, что размер файла не привышает допустимый размер у этого хранилища). В целом данный способ позволяет довольно легко организовать раздачу файлов пользователям с любого облачного хранилища. У меня была идея, подобная этой, но к сожалению у меня попадаются файлы вплоть до 2гб, а способа или модуля для склейки ответов из upstream я пока не нашел, писать же какие-то врапперы для этого проекта неоправданно трудозатратно.

Надеюсь, моя история была хоть немного вам интересна или полезна. Спасибо за внимание.

Теги
Показать больше

Похожие статьи

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть