Видача файлу з PHP через nginx (Accel-Redirect) докачка деякі тонкощі, Персональний блог
Як контролювати завантаження великих файлів, перевіряючи права доступу або враховуючи кількість завантажень? Як зробити, щоб під час проксування на Apache працювала докачка? Як взагалі працює докачка, чому вона не працює з nginx в IE 9 і як вона працює в інших браузерах?
1. Ми хочемо контролювати завантаження великих файлів, перевіряючи права доступу або враховуючи кількість завантажень. Ми використовуємо nginx та PHP (або іншу серверну мову).
Це реалізується просто через заголовок Accel-Redirect. nginx отримує запит, передає його скрипту, скрипт у заголовки відповіді видає Accel-Redirect із посиланням на файл, який потрібно видати користувачеві. Якщо файл видавати не можна (наприклад, немає прав), скрипт просто видасть помилку (наприклад, 403).
В даному випадку не важливо, працює ваш PHP через проксіювання з nginx на Apache, або на *CGI. Припустимо, що ви хочете віддавати файли, які знаходяться в каталозі /var/files. Тоді в секції server в nginx додайте:
У скрипті, який контролює закачування, у тому місці, де ми хочемо віддати користувачеві файл, має бути наступний код:
Жодної видачі (echo) робити не треба, після віддачі заголовка можна завершити роботу скрипта. У цьому випадку буде завантажено файл /var/files/filename.zip. Замість filename.zip може бути будь-який довгий шлях з каталогами, який повинен повторювати шлях до файлу /var/files/.
У принципі, цього достатньо, щоб перекласти видачу файлу на плечі nginx. Якщо потрібно обов'язково показати діалог завантаження (навіть якщо файл такого типу, який браузер може показати сам), потрібно додати видачу заголовка:
Зверніть увагу на те, що у разі проксування на Apache останнійобов'язково встановить Content-Type (за замовчуванням зазвичай text/html) у разі PHP-скриптів, і цей тип відправиться користувачеві, оскільки nginx його не перепише при виконанні Accel-Redirect. Тобто. у цьому випадку цей рядок потрібно писати обов'язково. Як буде у випадках з CGI, не знаю — не перевіряв.
2. У такій схемі під час проксування на Apache не працює докачка. Виправляємо.
Докачування в HTTP працює через HTTP-заголовок Range. У ньому вказується з якого байта розпочати передачу. Можна також вказати, скільки байт потрібно передати. Це передається у запиті. Наприклад:
Перший варіант вимагає віддати весь файл, починаючи з 33 байти (включно). Другий варіант просить віддати з 33 байт по 100. Щоб браузер зрозумів, що докачка підтримується, сервер віддає у відповіді заголовок:
Докачування не працює через те, що HTTP-заголовок Range потрапляє в Apache після просування і останній думає, що його просять віддати шматок PHP-файлу. А він цього робити не хоче і дає помилку: HTTP/1.1 416 Requested Range Not Satisfiable.
Виправляється це досить легко – просто занулюйте при проксуванні Range (це потрібно додати туди, де у вас описано проксіювання на Apache у конфізі nginx):
Тоді Apache (або PHP-скрипт) нічого не знатимуть про нього і спокійно зроблять свою справу. А nginx після Accel-Redirect віддасть відповідь відповідно до запитаного Range.
Якщо життєво необхідно бачити в скрипті запитаний Range, його можна або передати через додатковий заголовок при проксуванні (proxy_add_header), або поради звідси.
3. Навіть так не працює докачування в IE 9.
Так. Наскільки я зрозумів методом простого тику, IE вимагає підтримки сервера ETag для роботи докачки. Ігор Сисоєв (розробник nginx) відмовився впроваджувати в nginxпідтримку ETag. Тому з коробки працюватиме докачка в IE 9 не буде точно. Якщо це життєво необхідно, спробуйте дивитися сюди (Ctrl+F Etag).
4. А як працює докачування в інших браузерах?
Скрізь є свої особливості. Загалом випадок докачування виглядає так. Запит/відповідь на скачку (початок скачки):
Запит/відповідь на докачування:
Насправді жоден браузер не обмежується таким набором заголовків. Усі хочуть захистити користувача від скачки битого файлу. Це може статися так: почав закачування, поставив на паузу; в цей час на сервері файл оновився; продовжив завантаження з потрібного місця і отримав шматок оновленого файлу. В результаті завантажується "битий" файл - половина стара версія, половина - нова.
Opera. Вона шле вказівку на те, що «якщо файл не змінювався з часу Last-Modified у першій відповіді, дай мені вказаний шматок; інакше – віддай мені весь файл заново». Робиться це за допомогою заголовка If-Range у запиті:
Firefox. Він робить трохи інакше. Використовується заголовок If-Unmodified-Since із зазначенням того ж часу із Last-Modified. У цьому випадку сервер або поверне запитаний шматок (якщо файл не змінювався), або помилка 412 Precondition Failed. Виглядає це так:
Chrome. Цей «пильмень» - найхитріший. Він взагалі не підтримує докачування, хоча вдає, що підтримує. При постановці закачування на паузу він легко шле деякі TCP-пакети підтримки з'єднання. Коли сервер розуміє, що його дурять, він розриває з'єднання. Якщо дочекатися цього моменту і натиснути в Хромі "Продовжити", він зробить розумний вигляд, ніби файл повністю завантажився і все ОК, але при цьому він залишиться в тому розмірі, в якому встиг скачатися до паузи.
Вся ця інформація отримана шляхом спостереження. Можливо в якій-то інший ситуації Хром повів би себе інакше, але через WireShark спостерігав саме таку картину.
IE. Про версію 9 вже сказано вище. Вона потребує роботи Etag для підтримки докачування. У цьому контексті ETag – це просто контрольна сума файлу. Коли файл Etag передається в першій відповіді від сервера, IE дасть можливість перервати скачку. При спробі докачування він віддасть заголовки:
На додаток до If-Range він передає нестадартний заголовок Unless-Modified-Since - ймовірно, IIS його обробляє (тільки не зрозуміло, навіщо він, якщо є стандартний If-Unmodified-Since; може для підтримки старих версій IIS?).
Власне, у першій відповіді Etag виглядає приблизно так (якщо він підтримується сервером, наприклад, Apache):
Додаткова інформація :