Distribuir archivos de Google Drive usando nginx

Prehistoria

Dio la casualidad de que necesitaba almacenar más de 1.5 TB de datos en algún lugar y también ofrecer a los usuarios normales la posibilidad de descargarlos a través de un enlace directo. Dado que tradicionalmente tales cantidades de memoria van a VDS, el costo de alquiler no está muy incluido en el presupuesto del proyecto de la categoría "nada que hacer", y según los datos originales, tenía un VPS SSD de 400 GB, donde, incluso si Quería, no podía poner 1.5 TB de imágenes sin compresión sin pérdidas, lo logrará.

Y luego recordé que si elimino basura de Google Drive, como programas que solo se ejecutan en Windows XP y otras cosas que se han estado moviendo de un dispositivo a otro desde los días en que Internet no era tan rápido ni ilimitado ( por ejemplo, era poco probable que esas 10-20 versiones de la caja virtual tuvieran algún valor más que nostálgico), entonces todo debería encajar muy bien. Dicho y hecho. Y así, superando el límite en el número de solicitudes a la API (por cierto, el soporte técnico aumentó sin problemas la cuota de solicitudes por usuario a 100 en 10 segundos), los datos fluyeron rápidamente al lugar de su posterior implementación. .

Todo parece ir bien, pero ahora hay que transmitirlo al usuario final. Además, sin redirecciones a otros recursos, sino que una persona simplemente presiona el botón "Descargar" y se convierte en el feliz propietario del archivo preciado.

Aquí, por Dios, me metí en todo tipo de problemas. Al principio era un script en AmPHP, pero no estaba satisfecho con la carga que creaba (un fuerte salto al principio hasta el 100% del consumo de núcleo). Luego entró en juego el contenedor curl para ReactPHP, que se ajustaba bastante a mis deseos en términos de la cantidad de ciclos de CPU consumidos, pero no dio la velocidad que quería (resultó que simplemente puede reducir el intervalo para llamar curl_multi_select, pero luego tenemos la misma gula que la primera opción). Incluso intenté escribir un pequeño servicio en Rust y funcionó bastante rápido (es sorprendente que haya funcionado, dados mis conocimientos), pero quería más y de alguna manera fue difícil personalizarlo. Además, todas estas soluciones de alguna manera amortiguaron extrañamente la respuesta y quería rastrear el momento en que finalizó la descarga del archivo con la mayor precisión.

En general estuvo torcido por un tiempo, pero funcionó. Hasta que un día se me ocurrió una idea que destacaba por su locura: nginx, en teoría, puede hacer lo que quiero, trabajar rápidamente e incluso permitir todo tipo de perversiones con la configuración. Tenemos que intentarlo, ¿y si funciona? Y después de medio día de búsqueda persistente, nació una solución que funcionaba de manera estable durante varios meses y cumplía con todos mis requisitos.

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

Debajo del spoiler se puede ver una versión corta sin comentarios.

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

Estamos escribiendo un guión para gestionar toda esta felicidad.

El ejemplo estará en PHP y escrito deliberadamente con un mínimo de kit. Creo que cualquiera que tenga experiencia con cualquier otro idioma podrá integrar esta sección usando mi ejemplo.

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

resultados

En general, este método facilita bastante la organización de la distribución de archivos a los usuarios desde cualquier almacenamiento en la nube. Sí, incluso desde Telegram o VK (siempre que el tamaño del archivo no exceda el tamaño permitido de este almacenamiento). Tuve una idea similar a este, pero desafortunadamente me encuentro con archivos de hasta 2 GB y todavía no he encontrado un método o módulo para pegar respuestas desde el principio, y escribir algún tipo de envoltorios para este proyecto requiere una mano de obra excesiva.

Gracias por su atención. Espero que mi historia te haya resultado al menos un poco interesante o útil.

Fuente: habr.com

Añadir un comentario