Dystrybucja plików z Dysku Google za pomocą Nginx

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ł to, ale niestety natknąłem się na pliki do 2 GB, a nie znalazłem jeszcze metody ani modułu do wklejania odpowiedzi z góry, a pisanie jakiegoś wrappera do tego projektu jest nieracjonalnie pracochłonne.

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

Dodaj komentarz