nginx를 사용하여 Google 드라이브에서 파일 배포

선사 시대

마침 1.5TB가 넘는 데이터를 어딘가에 저장하고 일반 사용자가 직접 링크를 통해 다운로드할 수 있는 기능도 제공해야 했습니다. 전통적으로 이러한 양의 메모리는 VDS로 이동하기 때문에 임대 비용은 "할 일 없음" 범주의 프로젝트 예산에 크게 포함되지 않으며 소스 데이터에서 VPS 400GB SSD를 가지고 있었습니다. 싶었지만 무손실 압축 없이는 1.5TB의 이미지를 넣을 수 없었습니다. 성공할 것입니다.

그러다가 Windows XP에서만 실행되는 프로그램과 인터넷이 전혀 빠르지 않았던 시절부터 한 장치에서 다른 장치로 이동했던 기타 항목과 같은 정크를 Google 드라이브에서 삭제하면 무제한이 아니라는 것을 기억했습니다. 예를 들어, 가상 상자의 10-20개 버전은 향수 이외의 가치를 가질 가능성이 거의 없습니다. 그렇다면 모든 것이 매우 잘 맞아야 합니다. 말하자마자 실행되었습니다. 그래서 API에 대한 요청 수 제한을 돌파하고 (그런데 문제없이 기술 지원을 통해 사용자 당 요청 할당량이 100 초에 10으로 증가했습니다) 데이터가 추가 배포 장소로 빠르게 흘러갔습니다. .

모든 것이 좋아 보이지만 이제는 최종 사용자에게 전달해야 합니다. 또한 다른 리소스로 리디렉션되지 않고 "다운로드" 버튼만 누르면 소중한 파일의 행복한 소유자가 됩니다.

여기서 나는 하나님에 의해 온갖 어려움을 겪었습니다. 처음에는 AmPHP의 스크립트였지만 생성된 로드(처음에 100% 코어 소비로 급격히 증가)에 만족하지 못했습니다. 그런 다음 ReactPHP용 컬 래퍼가 작동하게 되었는데, 이는 소비된 CPU 사이클 수 측면에서 내 희망 사항에 매우 적합했지만 내가 원하는 속도를 전혀 제공하지 못했습니다(간단히 호출 간격을 줄일 수 있다는 것이 밝혀졌습니다). cur_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에서도 가능합니다(단, 파일 크기가 이 저장소의 허용 크기를 초과하지 않는 경우). 나는 비슷한 생각을 가지고 있었다 , 하지만 안타깝게도 최대 2GB의 파일을 발견했는데 아직 업스트림의 응답을 접착하는 방법이나 모듈을 찾지 못했고 이 프로젝트에 대한 일종의 래퍼를 작성하는 것은 비합리적으로 노동 집약적입니다.

관심을 가져주셔서 감사합니다. 제 이야기가 여러분에게 조금이라도 흥미롭거나 도움이 되었기를 바랍니다.

출처 : habr.com

코멘트를 추가