Servir conteúdo estático protegido usando nginx para velocidade e servidor Rails para autenticação

Problema

Um usuário precisa acessar um conteúdo estático protegido (por exemplo, imagem) que deve ser servido em https por um servidor web (para uma velocidade de download muito mais rápida) somente após a solicitação ter sido autenticada com sucesso e autorizada por um servidor Rails.

A configuração

Vamos supor o seguinte:

  • Nginx : nginx está sendo executado como um servidor proxy reverso e da web. Todas as solicitações para o servidor Rails são passadas para a porta 3000 (ou seja, a porta padrão para o servidor Rails)
  • Thin : servidor rails em execução na porta 3000
  • Diretório Rails : o diretório raiz do projeto rails está localizado em/var/rails/rails_app
  • Diretório onde os recursos estáticos protegidos são chamados :/var/rails/rails_app/images_fs_dir
  • URL para acessar os recursos estáticos protegidos :http://myhost.com/images_uri_dir

Solução

Por que usar o nginx para servir o conteúdo estático?

Porque o nginx é 5-20x mais rápido do que o thin quando se trata de servir conteúdo estático.

Fluxo lógico:

  • O pedido é feito para http://myhost.com/images_uri_dir/companyA/bar_chart.png
  • A solicitação é redirecionada para https://myhost.com/images_uri_dir/companyA/bar_chart.png(consulte reescrever ^ https: // $ host $ request_uri permanent)
  • O Nginx recebe a solicitação, combina o caminho da solicitação com suas regras de mapeamento em nginx.conf, adiciona cabeçalhos HTTP X-Accel-Redirect e X-Accel-Mapping e envia a solicitação para http://localhost:3000/images_uri_dir/companyA/bar_chart.png(consulte proxy_pass em nginx.conf)
  • Thin recebe a solicitação, procura routes.rbe encaminha a solicitação paraImagesController#show
  • O método before_filter no controlador realiza a autenticação e autorização apropriadas
  • config / environment / production.rb diz ao servidor Rails para procurar por X-Accel-Redirect e X-Accel-Mapping em uma solicitação
  • O Rails compara o caminho do arquivo que é passado para enviar o arquivo com o que é definido no lado esquerdo do valor do X-Accel-Mapping (ex. / Var / rails / rails app / = / images fs dir /). Se os 2 valores corresponderem, o controlador passa a solicitação de volta ao nginx. Caso contrário, o controlador irá enviar o arquivo de volta para o cliente
  • O Nginx recebe a solicitação e olha para o lado direito do valor do X-Accel-Mapping (ou seja, / var / rails / rails app / = / images fs_dir /). Como a solicitação é interna ao host, o nginx carrega e envia o arquivo de volta ao cliente (consulte interno em nginx.conf)

O diretório raiz do projeto Rails:

$ cd /var/rails/rails_app
$ ls

images_fs_dir

$ ls
-R # List files recursively
.:
customerA customerB


./customerA
bar_chart
.png
pie_chart
.png

./customerB
bar_chart
.png
pie_chart
.png
...

O arquivo nginx deve ser semelhante a este:

events {
worker_connections
1024;
}

http
{
include
/etc/nginx/mime.types;
index index
.html index.htm;

default_type application
/octet-stream;

upstream rails
{
server localhost
:3000;
}

server
{
listen
80;
server_name localhost
;

# Direct all http requests to https.
location
/ {
rewrite
^ https://$host$request_uri permanent;
}
}

server
{
listen
443 default_server ssl;
server_name localhost
;
root
/var/rails/rails_app;

ssl on
;
ssl_certificate
/etc/ssl/certs/my_cert.crt;
ssl_certificate_key
/etc/ssl/keys/my_key.key;

# Protected directory
# Note: rails will handle /images_uri_dir requests
location
/images_fs_dir/ {
alias /var/rails/rails_app/; # Append the path with /
internal; # Can't access this directory from direct access from the web
}

location
/ {
proxy_redirect off
;

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

proxy_set_header X
-Sendfile-Type X-Accel-Redirect;
proxy_set_header X
-Accel-Mapping /var/rails/rails_app/=/images_fs_dir/;

# Make sure you open and close your X-Accel-Mapping value with /

proxy_pass http
://rails;
}
}
}

config / routes.rb deve ser assim:

# ...

get
'/images_uri_dir/:customer_id/:category.png' => 'images#show'

#...

config / environment / production.rb deve ter a seguinte aparência:

# ...

# This redirects the request back to nginx to load and serve the static content after a success authentication/authorization

RailsApp::Application.configure do
config
.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
config
.middleware.insert(0, Rack::Sendfile, config.action_dispatch.x_sendfile_header)

# ...

app / controllers / images_controller.rb

# ...

before_filter
:validate_user

def validate_user
# Authentication and authorization logic here
end

def show
file_path
= "#{Rails.root}/images_fs_dir/#{customer_id}/#{category}.png"

send_file
(file_path, type: 'image/png', disposition: 'inline')
end

def images_params
params.permit(:customer_id, :category)
end

# ...