Διανομή αρχείων από το Google Drive χρησιμοποιώντας nginx

Ιστορικό

Απλώς, χρειάστηκε να αποθηκεύσω κάπου περισσότερα από 1.5 TB δεδομένων και επίσης να παράσχω τη δυνατότητα στους απλούς χρήστες να τα κατεβάσουν μέσω απευθείας συνδέσμου. Δεδομένου ότι παραδοσιακά τέτοια ποσά μνήμης πηγαίνουν σε VDS, το κόστος ενοικίασης το οποίο δεν περιλαμβάνεται πολύ στον προϋπολογισμό του έργου από την κατηγορία "nothing to do" και από τα δεδομένα πηγής είχα έναν VPS 400GB SSD, όπου, ακόμα κι αν ήθελα, δεν μπορούσα να βάλω 1.5 TB εικόνων χωρίς συμπίεση χωρίς απώλειες θα πετύχει.

Και μετά θυμήθηκα ότι αν διαγράψω ανεπιθύμητα από το Google Drive, όπως προγράμματα που θα τρέχουν μόνο στα Windows XP, και άλλα πράγματα που μετακινούνταν από τη μια συσκευή στην άλλη από την εποχή που το Διαδίκτυο δεν ήταν καθόλου τόσο γρήγορο, δεν ήταν απεριόριστο ( για παράδειγμα, αυτές οι 10-20 εκδόσεις του εικονικού κουτιού ήταν απίθανο να έχουν άλλη αξία εκτός από νοσταλγική), τότε όλα θα έπρεπε να ταιριάζουν πολύ καλά. Όχι νωρίτερα. Και έτσι, ξεπερνώντας το όριο στον αριθμό των αιτημάτων στο api (παρεμπιπτόντως, η τεχνική υποστήριξη αύξησε χωρίς προβλήματα το όριο των αιτημάτων ανά χρήστη σε 100 σε 10 δευτερόλεπτα), τα δεδομένα έρεαν γρήγορα στον τόπο της περαιτέρω ανάπτυξής του .

Όλα φαίνονται να είναι καλά, αλλά τώρα πρέπει να μεταφερθούν στον τελικό χρήστη. Επιπλέον, χωρίς ανακατευθύνσεις σε άλλους πόρους, αλλά έτσι ώστε ένα άτομο απλά να πατήσει το κουμπί "Λήψη" και να γίνει ο ευτυχής κάτοχος του πολύτιμου αρχείου.

Εδώ, προς Θεού, μπήκα σε κάθε είδους προβλήματα. Στην αρχή ήταν ένα σενάριο στο AmPHP, αλλά δεν ήμουν ικανοποιημένος με το φορτίο που δημιούργησε (ένα απότομο άλμα στην αρχή στο 100% κατανάλωση πυρήνα). Στη συνέχεια μπήκε στο παιχνίδι το curl wrapper για το 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;
}

Μια σύντομη έκδοση χωρίς σχόλια μπορείτε να δείτε κάτω από το spoiler

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

Αποτελέσματα της

Γενικά, αυτή η μέθοδος καθιστά αρκετά εύκολη την οργάνωση της διανομής αρχείων στους χρήστες από οποιοδήποτε χώρο αποθήκευσης στο cloud. Ναι, ακόμη και από τηλεγράφημα ή VK, (με την προϋπόθεση ότι το μέγεθος του αρχείου δεν υπερβαίνει το επιτρεπόμενο μέγεθος αυτής της αποθήκευσης). Είχα μια ιδέα παρόμοια με αυτό, αλλά δυστυχώς συναντώ αρχεία έως 2 GB και δεν έχω βρει ακόμη μέθοδο ή μονάδα για να κολλήσω απαντήσεις από το upstream και η σύνταξη κάποιου είδους περιτυλίγματος για αυτό το έργο είναι αδικαιολόγητα εντατική.

Σας ευχαριστώ για την προσοχή σας. Ελπίζω η ιστορία μου να ήταν τουλάχιστον λίγο ενδιαφέρουσα ή χρήσιμη για εσάς.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο