使用 nginx 從 Google Drive 分發文件

恰好我需要在某個地方存儲超過 1.5 TB 的數據,並且還提供普通用戶透過直接鏈接下載的能力。 由於傳統上如此數量的記憶體會流向 VDS,因此租賃成本並沒有太多包含在「無事可做」類別的專案預算中,並且從來源資料來看,我有一個 VPS 400GB SSD,即使我我想,我不能把1.5TB 的影像不進行無損壓縮就成功了。

然後我想起來,如果我從Google Drive 中刪除垃圾,例如只能在Windows XP 上運行的程序,以及其他自從互聯網還沒有那麼快而不是無限的時代以來一直從一台設備轉移到另一台設備的東西(例如,那些10-20個版本的虛擬盒子除了懷舊之外不太可能有任何價值),那麼一切都應該非常合適。 說到做到。 就這樣,突破了api的請求數量限制(順便說一句,技術支援沒有任何問題,在100秒內將每個用戶的請求配額提高到10個),數據迅速流向其進一步部署的地方。

一切看起來都很好,但現在需要傳達給最終用戶。 此外,沒有任何重定向到其他資源,而是讓一個人只需按下「下載」按鈕即可成為珍貴文件的快樂所有者。

上帝啊,我在這裡遇到了各種各樣的麻煩。 起初它是 AmPHP 中的一個腳本,但我對它創建的負載並不滿意(一開始就急劇上升到 100% 核心消耗)。 然後ReactPHP的curl包裝器開始發揮作用,就消耗的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));

結果

一般來說,這種方法使得從任何雲端儲存組織向使用者分發文件變得非常容易。 是的,即使是來自 telegram 或 VK(前提是檔案大小不超過此儲存允許的大小)。 我有一個類似的想法 ,但不幸的是我遇到了高達 2GB 的文件,而且我還沒有找到一種方法或模組來粘合來自上游的響應,並且為這個項目編寫某種包裝器是不合理的勞動密集型。

感謝您的關注。 我希望我的故事至少對您來說有點有趣或有用。

來源: www.habr.com

添加評論