使用 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 的文件,而且我还没有找到一种方法或模块来粘合来自上游的响应,并且为这个项目编写某种包装器是不合理的劳动密集型。

感谢您的关注。 我希望我的故事至少对您来说有点有趣或有用。

来源: habr.com

添加评论