[lang_en]Using X-Accel-Redirect in Nginx to Implement Controlled Downloads[/lang_en][lang_ru]Использование X-Accel-Redirect с Nginx для реализации контролируемых скачиваний[/lang_ru]
1 Nov2006

[lang_en]

Sometimes you may need to implement controlled downloads when all downloads requests being sent to your script and then this script decides what to do: to send some file to the user or to show some access denied page or, maybe, do something else. In lighttpd server it can be done by returning X-Sendfile header from script. Nginx have its own implementation of such idea using X-Accel-Redirect header. In this short post I will try to describe how to use this feature from PHP and Rails applications.

[/lang_en]

[lang_ru]

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

[/lang_ru]

[lang_en]

Lets assume, that you have some site and using Apache with PHP or Rails for dynamic content on this site. If you will use nginx as reverse-proxy in front of your Apache server, you will reach two goals:

  1. You would be able free some resources on server while nginx will handle all slow requests to dynamic content (details are here).
  2. You would be able to implement controlled downloads of static files from site.

In this article I will assume, that site is located in /var/www directory and there are some static files (like movies or song or something else) in /var/www/files directory. Apache is listening on http://127.0.0.1:8080.

First of all, lets take a look at our nginx configuration:

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

As you can see, we have additional “internal” location /files there. This keyword “internal” allow us to have some locations, that will be available for user only in internal redirects and X-Accel-Redirect responses from backend scripts. So, we can use simple PHP script or Rails code on backend server to implement controlled downloads that will support Ranges header and all other features supported by direct static downloads from nginx servers.

Here is down.php script:

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

?>

In Rails applications you can use following code in your 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

That’s it! With described approach we are able to create very flexible and extremely performance systems for file distribution!

[/lang_en]

[lang_ru]

Представим, что у вас есть какой-либо сайт, работающий на 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

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

[/lang_ru]