Bestanden distribueren vanuit Google Drive met nginx

prehistorie

Het gebeurde zo dat ik ergens meer dan 1.5 TB aan gegevens moest opslaan en ook de mogelijkheid moest bieden aan gewone gebruikers om deze via een directe link te downloaden. Omdat traditioneel dergelijke hoeveelheden geheugen naar VDS gaan, zijn de kosten van het huren, die niet erg veel zijn opgenomen in het projectbudget, uit de categorie 'niets te doen', en op basis van de brongegevens had ik een VPS 400GB SSD, waar ik, zelfs als ik wilde, ik kon geen 1.5 TB aan afbeeldingen plaatsen zonder verliesvrije compressie, het zal lukken.

En toen herinnerde ik me dat als ik rommel uit Google Drive verwijder, zoals programma's die alleen op Windows XP draaien, en andere dingen die van het ene apparaat naar het andere zijn verhuisd sinds de tijd dat internet helemaal niet zo snel was en niet onbeperkt ( het was bijvoorbeeld onwaarschijnlijk dat die 10-20 versies van de virtuele doos enige andere waarde hadden dan nostalgisch), dan zou alles heel goed moeten passen. Zo gezegd zo gedaan. En dus, door de limiet van het aantal verzoeken aan de API te doorbreken (trouwens, technische ondersteuning verhoogde zonder enige problemen het quotum van verzoeken per gebruiker tot 100 in 10 seconden), stroomden de gegevens snel naar de plaats van verdere implementatie .

Alles lijkt goed te zijn, maar nu moet het worden overgebracht naar de eindgebruiker. Bovendien, zonder enige omleiding naar andere bronnen, maar zodat iemand eenvoudigweg op de knop "Downloaden" drukt en de gelukkige eigenaar wordt van het dierbare bestand.

Hier, bij God, kwam ik in allerlei problemen terecht. In eerste instantie was het een script in AmPHP, maar ik was niet tevreden met de belasting die het creëerde (een scherpe sprong bij de start naar 100% core-verbruik). Toen kwam de curl-wrapper voor ReactPHP in het spel, die goed paste bij mijn wensen in termen van het aantal verbruikte CPU-cycli, maar de snelheid helemaal niet opleverde wat ik wilde (het bleek dat je eenvoudigweg het belinterval kunt verkorten curl_multi_select, maar dan hebben we een gulzigheid vergelijkbaar met de eerste optie). Ik heb zelfs geprobeerd een kleine service in Rust te schrijven, en het werkte vrij snel (het is verrassend dat het werkte, gezien mijn kennis), maar ik wilde meer, en het was op de een of andere manier moeilijk om het aan te passen. Bovendien bufferden al deze oplossingen op de een of andere manier vreemd genoeg het antwoord, en ik wilde het moment waarop het downloaden van het bestand eindigde met de grootste nauwkeurigheid volgen.

Over het algemeen was het een tijdje krom, maar het werkte. Totdat ik op een dag op een idee kwam dat opmerkelijk was in zijn waanzin: nginx kan in theorie doen wat ik wil, snel werken en zelfs allerlei perversies toestaan ​​met configuratie. We moeten het proberen: wat als het werkt? En na een halve dag volhoudend zoeken was er een oplossing geboren die al enkele maanden stabiel werkte en aan al mijn eisen voldeed.

NGINX instellen

# Первым делом создадим в конфигах нашего сайта отдельную локацию.
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;
}

Een korte versie zonder commentaar is te zien onder de spoiler

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;
}

We schrijven een script om al dit geluk te beheren

Het voorbeeld zal in PHP zijn en doelbewust geschreven met een minimum aan kit. Ik denk dat iedereen die ervaring heeft met een andere taal deze sectie kan integreren met behulp van mijn voorbeeld.

<?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));

Resultaten van

Over het algemeen maakt deze methode het vrij eenvoudig om de distributie van bestanden naar gebruikers vanuit elke cloudopslag te organiseren. Ja, zelfs vanuit Telegram of VK (op voorwaarde dat de bestandsgrootte de toegestane grootte van deze opslag niet overschrijdt). Ik had een soortgelijk idee als dit, maar helaas kom ik bestanden tegen tot 2 GB, en ik heb nog geen methode of module gevonden om reacties van stroomopwaarts te lijmen, en het schrijven van een soort wrappers voor dit project is onredelijk arbeidsintensief.

Bedankt voor uw aandacht. Ik hoop dat mijn verhaal op zijn minst een beetje interessant of nuttig voor je was.

Bron: www.habr.com

Voeg een reactie