prehistoria
Tak się złożyło, że musiałem gdzieś przechować ponad 1.5 TB danych, a jednocześnie zapewnić zwykłym użytkownikom możliwość pobrania ich poprzez bezpośredni link. Ponieważ tradycyjnie takie ilości pamięci trafiają do VDS, koszt wynajmu który nie jest zbyt mocno uwzględniony w budżecie projektu z kategorii „nic do zrobienia”, a z danych źródłowych miałem dysk SSD VPS 400 GB, na którym nawet gdybym chciałem, nie mogłem zmieścić 1.5 TB obrazów bez kompresji bezstratnej, uda się.
A potem przypomniałem sobie, że jeśli usunę śmieci z Dysku Google, takie jak programy, które będą działać tylko w systemie Windows XP i inne rzeczy, które przenoszą się z jednego urządzenia na drugie od czasów, gdy Internet wcale nie był tak szybki i nieograniczony ( na przykład te 10-20 wersji wirtualnego pudełka raczej nie miało żadnej wartości poza nostalgiczną), wtedy wszystko powinno pasować bardzo dobrze. Nie wcześniej powiedziane, niż zrobione. I tak przełamując limit liczby żądań do api (swoją drogą pomoc techniczna bez problemu zwiększyła limit żądań na użytkownika do 100 10 w 000 sekund), dane szybko popłynęły do miejsca jego dalszego wdrożenia .
Wszystko wydaje się być w porządku, ale teraz trzeba to przekazać końcowemu użytkownikowi. Co więcej, bez żadnych przekierowań do innych zasobów, ale tak, aby osoba po prostu nacisnęła przycisk „Pobierz” i stała się szczęśliwym posiadaczem cennego pliku.
Tutaj, na Boga, wpadłem w najróżniejsze kłopoty. Na początku był to skrypt w AmPHP, ale nie byłem zadowolony z obciążenia, jakie powodował (gwałtowny skok na początku do 100% zużycia rdzenia). Potem wszedł w grę wrapper curl dla ReactPHP, który całkiem wpasował się w moje życzenia pod względem liczby zużywanych cykli procesora, ale wcale nie dawał takiej prędkości, jakiej chciałem (okazało się, że można po prostu zmniejszyć interwał wywołań curl_multi_select, ale wtedy mamy obżarstwo podobne do pierwszej opcji). Próbowałem nawet napisać małą usługę w Rust i zadziałało dość szybko (zaskakujące, że zadziałało, biorąc pod uwagę moją wiedzę), ale chciałem więcej i jakoś trudno było to dostosować. Poza tym wszystkie te rozwiązania w jakiś dziwny sposób buforowały odpowiedź, a chciałem z największą dokładnością prześledzić moment, w którym pobieranie pliku zakończyło się.
Ogólnie przez jakiś czas było krzywo, ale zadziałało. Aż pewnego dnia wpadłem na pomysł niezwykły w swoim szaleństwie: nginx w teorii może robić, co chcę, działać szybko, a nawet pozwalać na wszelkiego rodzaju perwersje w konfiguracji. Musimy spróbować – a co jeśli zadziała? I po pół dnia uporczywych poszukiwań powstało rozwiązanie, które działało stabilnie przez kilka miesięcy i spełniało wszystkie moje wymagania.
Konfigurowanie 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;
}
Krótką wersję bez komentarzy można zobaczyć pod spoilerem
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;
}
Piszemy scenariusz, aby zarządzać całym tym szczęściem
Przykład będzie w PHP i celowo napisany przy użyciu minimalnej ilości zestawu. Myślę, że każdy, kto ma doświadczenie z jakimkolwiek innym językiem, będzie w stanie zintegrować tę sekcję na moim przykładzie.
<?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));
Wyniki
Ogólnie rzecz biorąc, ta metoda ułatwia organizację dystrybucji plików do użytkowników z dowolnego magazynu w chmurze. Tak, nawet z telegramu lub VK (pod warunkiem, że rozmiar pliku nie przekracza dopuszczalnego rozmiaru tego magazynu). Miałem podobny pomysł
Dziękuję za uwagę. Mam nadzieję, że moja historia była dla Ciebie choć trochę interesująca lub przydatna.
Źródło: www.habr.com