Использование X-Accel-Redirect с Nginx для реализации контролируемых скачиваний

Posted by Alexey Kovyrin under Development · english

Иногда вам может быть нужно реализовать т.н. контролируемое скачивание, когда все запросы на скачивание файлов передаются скрипту, который решает, как поступить: отправить пользователю какой-либо файл, или показать стриницу access denied, или, может быть, сделать что-то еще. При использовании сервера lighttpd это может быть реализовано при помощи заголовка X-Sendfile, возвращаемого из скрипта. Nginx имеет свою союственную реализацию описанной идеи с использованием заголовка X-Accel-Redirect. В этой короткой статье я попытаюсь описать, как использовать эту возможность из приложений на PHP или Rails.

Представим, что у вас есть какой-либо сайт, работающий на Apache с PHP или Rails для генерации нинамического контента. Если вы будете использовать nginx в качестве reverse-proxy перед вашим сервером Apache, вы получите сразу две положительных возможности:

  1. Вы сможете освободить больше ресурсов вашего сервера для обслуживания клиентов, т.к. nginx возьмет на себя работу с медленными клиентами (детальнее – здесь).
  2. Вы сможете реализовать контролируемое скачивание статических файлов с вашего сайта.

В этой статье я предпологаю, что сайт расположен в каталоге /var/www и статические файлы (например, фильмы, музыка или что-то еще) расположены в каталоге /var/www/files. Apache слушает на порту http://127.0.0.1:8080.

Для начала, давайте рассмотрим нашу конфигурацию сервера nginx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
http {
    ....
    server {
        listen       80;
        server_name  your-domain.com;

        location / {
            rewrite ^/download/(.*) /down.php?path=$1 last;

            proxy_pass         http://127.0.0.1:8080/;
            proxy_redirect     off;

            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

            client_max_body_size       10m;
            client_body_buffer_size    128k;

            proxy_connect_timeout      90;
            proxy_send_timeout         90;
            proxy_read_timeout         90;

            proxy_buffer_size          4k;
            proxy_buffers              4 32k;
            proxy_busy_buffers_size    64k;
            proxy_temp_file_write_size 64k;

        }

        location /files {
            root /var/www;
            internal;
        }
    }
}

Как вы видите, у нас есть дополнительная “internal” секция location /files. Это ключевое влово “internal” позволяет нам иметь секции location, которые будут доступны для польщователя только в случае внутренних редиректов внутри nginx и при использование заголовка X-Accel-Redirect в ответах от сриптов backend-сервера. Итак, мы можем использовать простой скрипт на PHP или код на Rails для реализации контролируемых скачиваний с поддержкой заголовков Ranges (докачка) и всех остальных возможностей, предоставляемых при прямом скачивании статического контента с серверов под управлением nginx.

Вот пример очень простого скрипта down.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
// Get requested file name
$path = $_GET["path"];

//...
// Perform any required security checks, validation
// and/or stats accounting
//...

// And redirect user to internal location
header("X-Accel-Redirect: /files/" . $path);

?>

В приложениях Rails вы можете использовать следубщий код в вашем controller’е:

1
2
3
4
5
6
7
8
9
10
// Get requested file name
path = @params["path"]

# ...
# Perform any required security checks, validation
# and/or stats accounting
# ...

# And redirect user to internal location
@response.headers['X-Accel-Redirect'] = "/files/" + path

Вот и все! При помощи описанного подхода вы сможете реализовать очень гибкую и удивительно эффективную систему раздачи любого статического контента!


Related posts:

  1. Nginx-Fu: X-Accel-Redirect From Remote Servers
  2. Обзор Типичных Конфигураций Для Nginx
  3. Сбор Статиcтики О Работе Сервера nginx При Помощи rrdtool
  4. Использование Nginx Как Reverse-Proxy Сервера На Загруженных Сайтах
  5. Nginx – Маленький, Но Очень Мощный И Эффективный Web-Сервер

41 Responses to this entry

nir0 says:

привет!
я реализовал контролируемые скачивания, но есть одна проблема – nginx со всеми файлами передает заголовок text/html, и браузер его соответственно открывает. не подскажете, с чем это может быть связано, и как испраить?

Denis says:

$r->send_http_header posulaet eto,
4to-bu zapretit – powli pustoj $r->send_http_header();

nir0 says:

да я затупил, короче… там ошибка была, типа, нет такого темплейта. она и заменяла заголовок на text/html, а тело документа менялось на содержимое файла.

peroksid says:

Здорово. Часто под контролируемым скачиванием понимают еще и отметку, когда скачивание файла было закончено. Тут nginx помочь может?

Scoundrel says:

2pereksid: Можно и такое. Называлось вроде post_action – нгинкс умеет дергать нужный урл когда юзер все докачает.

Andrey Popov says:

Можно по подробнее, из-за чего nginx выдает на .exe файлы, text/html вместо application/octet-stream?

nir0 says:

> Можно по подробнее, из-за чего nginx выдает на .exe файлы, text/html вместо application/octet-stream?
Это может быть потому, что процесс выдает ошибку “500 No template for action download”
так как ошибка выводится в html, то она подменяет заголовок на text/html.
проверьте логи ruby

jackaXs says:

DOES NGINX IPV6 CAPABLE ???????!!!!!!!!!!!!!??????????????????!!!!!!!!!!!!!???????????????????!!!!!!!!!!!!!!!!!!!!

znikus says:

нет, это статья не все. если использовать пхп как модуль апача, то сервер ляжет под нагрузкой, когда обратятся несколько юзверов при помощи флашгета с 10 потоками. Лучшим вариантом будет запуск проверяющего скрипта на фастCGI.

si-rus says:

Как вы смотрите на то, чтобы для уменьшения нагрузки дописать к nginx модуль или фильтр какой нить для реализации антилича.

square says:

Ошибочка маленькая в rewrite, должно быть так, видимо:
rewrite ^/download/(.*)$ /down.php?path=$1 last;

2 Andrey Popov:
Попробуй перед редиректом (X-Accel-Redirect) отдавать заголовок руками т.е.:
Content-type: application/octet-stream

Alex Egg says:

What about the rest of the code for the rails controller.

This confuses me because you have to render something….?

I have nginx setup and I am setting the header, but then what? render :text => “”???

WIll says:

For explorer 6 users downloading pdfs you also need to set the cache-control header, or you get a ‘document not found’ error message from the acrobat reader. It seems this is because the explorer regards the file as temporary and deletes it before acrobat can see it.

anyway, i use this to make pdf downloads work:

1
2
3
4
5
6
7
   location /download_control/ {
       root ...;
       default_type  application/pdf;
       expires 1h;
       add_header  Cache-Control  private;
       internal;
   }

Otherwise, I’ve been very happy with nginx and this download control mechanism (and I’ve written a radiant plugin to make use of it, which should appear soon). thank you.

jsmirnoff says:

При таком подходе в заголовке Content-Type всегда оказывается text/html. Соответственно, бинарный файл загружается браузером в окно а не предлагается для скачивания.

Как оказалось, это заголовок по умолчанию, который выдает сам PHP скрипт, и nginx его уже не трогает.

Выход довольно прост, надо запретить PHP выдавать этот заголовок, например, так:

ini_set(‘default_mimetype’, ”);

Если кто подскажет, как это сделать более элегантно – буду благодарен.

Goga says:

перед X-Accel-Redirect вставьте:

header(“Content-Disposition: attachment; filename=\”" . $filename . “\”");

где $filename – имя файла (только имя, путь – отсутствует).
После этого все браузеры должны открывать окно с приглашением сохранить файл или открыть. Ну у MIME типы уже сам nginx отдаст если в его конфиге добавлено include conf/mime.types

Ivan says:

Добрый день. А никто не сталкивался со следующей ситуацией: есть файл который отдается через X-Accel-Redirect. При попытке его скачать с двух различных машин – качается только на одной. Проблема имеет место только при скачивании через браузер (тестировался ie6-7 и ff2), выдается невозможность отобразить страницу. При использовании менеджера закачек – все качается нормально.

Ivan says:

в тему предыдущего коммента – вопрос закрыт. проблема была в размере файла – больше 4 гб. Интернет експлорер побоялся что на машине фат32 и не стал качать.

Vladimir says:

При попытке использовать описанную схему контролируемого скачивания (nginx+apache+php) столкнулся со след. проблемой: иногда вместо предложения сохранить файл в браузер выдается ошибка :
502 Bad Gateway
nginx/0.5.33
При этом в PHP скрипт передается $path с запорченным окончанием – последние несколько символов заменены на 0×0. В error.log nginx’а при этом выдается сообщение:
upstream prematurely closed connection while reading response header from
upstream …
Как с этим бороться?

Morales says:

>What about the rest of the code for the rails controller.
>This confuses me because you have to render something….?
>I have nginx setup and I am setting the header, but then what? >render :text => “”???

render :nothing => true

алексей says:

Про использование в lighttpd контролируемых скачек с помощью скриптов можно поподробнее?

Stas says:

Спасибо за статью.

С заголовками таки что-то не то – FF и IE отрабатывают нормально, а вот Опера упорно предлагает сохранить в html …

А. Александров says:

Чтобы правильно отдавать тип и имя файла:
<?php
header(“Content-type: video/mpeg”);
header(“Content-Disposition: attachment; filename=\”file.mpeg\”");
//или подставить свой
Чтобы не задалбывали потоками reget, dmaster etc..
в nginx.conf
http {
limit_zone one $binary_remote_addr 10m;

location /files {
root /home/www/…/files;
internal;
limit_conn one 1;
}
где one-имя зоны, 1-количество подкл. с одного ip