nginxを使用してGoogleドライブからファイルを配布する

背景

たまたま、1.5 TB を超えるデータをどこかに保存し、一般のユーザーが直接リンク経由でデータをダウンロードできる機能を提供する必要がありました。 従来、そのような量のメモリは VDS に送られるため、「何もしない」カテゴリのプロジェクト予算にはあまり含まれていないレンタル費用が発生します。また、ソース データからは、VPS 400GB SSD を使用していました。したかったのですが、可逆圧縮なしでは 1.5TB の画像を置くことができませんでしたが、成功します。

そして、Windows XP でのみ動作するプログラムや、インターネットがそれほど高速ではなかった時代からあるデバイスから別のデバイスに移動し続けてきたその他のものなど、不要なものを Google ドライブから削除すると、まったく無制限ではないことを思い出しました。たとえば、仮想ボックスの 10 ~ 20 バージョンにはノスタルジック以外の価値がある可能性は低いです)、すべてが非常によく適合するはずです。 否や言うほどない。 そして、API へのリクエスト数の制限を突破し (ちなみに、テクニカル サポートは問題なく、ユーザーあたりのリクエストの割り当てを 100 秒で 10 件に増やしました)、データはすぐに次のデプロイメントの場所に流れました。 。

すべてがうまくいっているように見えますが、今度はそれをエンドユーザーに伝える必要があります。 さらに、他のリソースへのリダイレクトはなく、「ダウンロード」ボタンを押すだけで、大切なファイルの幸せな所有者になります。

ここで、神様のおかげで、私はあらゆる種類の困難に遭遇しました。 最初は AmPHP のスクリプトでしたが、作成される負荷に満足できませんでした (開始時に急激にコア消費量が 100% に達しました)。 次に、ReactPHP のカール ラッパーが登場しました。これは、消費される CPU サイクル数の点で私の希望に非常に一致しましたが、速度は私が望んでいたほどではありませんでした (単純に呼び出し間隔を短縮できることが判明しました) curl_multi_select ですが、最初のオプションと同様に大食いになります)。 Rust で小さなサービスを作成しようとしたこともありましたが、非常にすぐに機能しました (私の知識を考えると、機能したのは驚くべきことです)。しかし、さらに多くのことが必要で、カスタマイズするのがどういうわけか難しかったです。 さらに、これらのソリューションはすべて、どういうわけか奇妙なことに応答をバッファリングするため、ファイルのダウンロードが終了した瞬間を最も正確に追跡したいと考えていました。

全体的に、しばらく曲がっていましたが、うまくいきました。 ある日、私はその狂気の点で注目に値するアイデアを思いつきました。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));

結果

一般に、この方法を使用すると、クラウド ストレージからユーザーへのファイルの配布を非常に簡単に整理できます。 はい、テレグラムまたは VK からでも可能です (ファイル サイズがこのストレージの許容サイズを超えない場合)。 似たようなアイデアがありました この、しかし、残念ながら、最大 2 GB のファイルに遭遇し、上流からの応答を接着するためのメソッドやモジュールがまだ見つかっていないため、このプロジェクト用に何らかのラッパーを作成するのは不当に労働集約的です。

ご清聴ありがとうございました。 私の話が少しでも興味を持ったり、役に立ったりすることを願っています。

出所: habr.com

コメントを追加します