Rails XSendfile via Nginx

Hi,

I’m trying to configure nginx to serve a video file on behalf of my
rails 3 backend using xsendfile. I want rails to redirect the request
for 应用宝官网-全网最新最热手机应用游戏下载 to nginx to handle. Unfortunately,
my current configuration results in a 404 (page not found) error. I’ve
pasted my nginx configuration at http://pastebin.com/VA4QFM35

Does anyone know what could be wrong?

Note: I have activated X-Accel-Redirect in rails by:

Rails configuration (e.g. config/environments/production.rb)

config.action_dispatch.x_sendfile_header = ‘X-Accel-Redirect’

Thanks.

Ari

On 7 May 2012 23:53, Ari K. [email protected] wrote:

Note: I have activated X-Accel-Redirect in rails by:

Rails configuration (e.g. config/environments/production.rb)

config.action_dispatch.x_sendfile_header = ‘X-Accel-Redirect’

Steps you could take / info you could provide:

  • hit the app direct with a curl invocation and examine the headers it
    creates. I bet you’ll find the problem here.
  • re-read the alias docs. Your location{/recipe/…} looks a bit
    screwy, if the examples you’ve given are precise.

J

Jonathan M.
Oxford, London, UK
http://www.jpluscplusm.com/contact.html

On 8 May 2012 00:16, Jonathan M. [email protected] wrote:

screwy, if the examples you’ve given are precise.
I forget step #0: examine the error log! It’ll tell you where nginx is
failing to find your file, hence what the config problem might be …

J

Jonathan M.
Oxford, London, UK
http://www.jpluscplusm.com/contact.html

On 9 May 2012 03:58, Ari K. [email protected] wrote:

Prior to asking my original question, I checked my error.log and found
no errors whatsoever. I also tried using curl, but the content returned
is a Nginx 404 error page. Any other ideas? I also doubled checked my
alias configuration; as per nginx docs, location & alias take regex
expressions and captures. My hunch is that the url is the problem
(/recipes/1/video) since it does NOT contain the filename.

Yes, that’s what I meant about your use of “alias” being screwy. You
haven’t indicated that you’ve tried hitting the app directly,
without nginx in the way, and examining the headers. This is always
really useful. In this case, however, I think the fault lies
elsewhere.

Here’s what you’ve got in your config:

location /recipes/(.*)/video {
alias /var/www/app/current/uploads/videos/$1/original/;
}

To me, this looks like when you request

http://foo.bar.com/recipes/123/video

you’re asking nginx to serve the “file”

/var/www/app/current/uploads/videos/123/original/

But this isn’t a file, it’s a directory. So
http://wiki.nginx.org/HttpIndexModule#index kicks in and (unless
you’ve changed it) tries to serve
/var/www/app/current/uploads/videos/123/original/index.html.
Hence the 404.

I think your taxonomy is a bit fucked here, TBH, and you’re reaping
the rewards of trying to implement the wrong public-facing data
structure.
If you have the scope to change it, I’d be going with something where
(a) “video” is on the left of the video ID and possibly (b) where
/recipes/ is out of the picture all together. Something like

http://foo.bar.com/video/123 or, if you anticipate getting to any scale
http://foo.bar.com/video/000/1/123 or, if “recipe” is still important
http://foo.bar.com/video/recipes/123 to allow you to have non-recipe
based videos in the future.

… and where the file on disk is named according to the ID - not
where the ID is just part of the filesystem path leading up to the
video.

HTH,
Jonathan

Jonathan M.
Oxford, London, UK
http://www.jpluscplusm.com/contact.html

Jonathan M. wrote in post #1059929:

On 8 May 2012 00:16, Jonathan M. [email protected] wrote:

screwy, if the examples you’ve given are precise.
I forget step #0: examine the error log! It’ll tell you where nginx is
failing to find your file, hence what the config problem might be …

J

Jonathan M.
Oxford, London, UK
http://www.jpluscplusm.com/contact.html

Prior to asking my original question, I checked my error.log and found
no errors whatsoever. I also tried using curl, but the content returned
is a Nginx 404 error page. Any other ideas? I also doubled checked my
alias configuration; as per nginx docs, location & alias take regex
expressions and captures. My hunch is that the url is the problem
(/recipes/1/video) since it does NOT contain the filename.

Thanks.

-Ari

On Wed, May 09, 2012 at 04:58:55AM +0200, Ari K. wrote:

Jonathan M. wrote in post #1059929:

Hi there,

Prior to asking my original question, I checked my error.log and found
no errors whatsoever. I also tried using curl, but the content returned
is a Nginx 404 error page. Any other ideas? I also doubled checked my
alias configuration; as per nginx docs, location & alias take regex
expressions and captures. My hunch is that the url is the problem
(/recipes/1/video) since it does NOT contain the filename.

your config has two location{} blocks:

location /
location /recipes/(.*)/video

Probably you want to include “~” in your second location directive:
http://nginx.org/r/location

That might be sufficient to get everything working for you.

It’s not clear to me exactly how you wish things to work. If the above
change gets things working, then it doesn’t matter that it’s not clear
to me. But:

the usual way to use X-Accel-Redirect is that the client accesses
“/public/uri” which nginx sends to an upstream for processing; that
upstream returns X-Accel-Redirect to “/private/uri”; and nginx is
configured to serve “/private/uri” from the filesystem.

So: if the client wishes to get access to the file “test.video”, what
public url will they use to access it? What is the X-Accel-Redirect
header that the backend sends to nginx? And where on the filesystem is
the file “test.video”? If you can answer those questions, then it may
become clear how nginx should be configured to allow it all to happen.

f

Francis D. [email protected]

Jonathan/Francis,

I modified the applications url paths to–

www.foo.bar.com/recipes/:id/video/:filename

Probably you want to include “~” in your second location directive:

Tried adding the (regex) tilde, but that did not solve the issue.

So: if the client wishes to get access to the file “test.video”, what
public url will they use to access it?

www.foo.bar.com/recipes/1/video/example_one.ogg

What is the X-Accel-Redirect header that the backend sends to nginx?

How can I find this out? When I curl directly to the backend server I
get these headers: see http://pastebin.com/CVLjhLRJ

And where on the filesystem is the file “test.video”?

The video files are stored according to their respective ids; note the
paths are different AFTER the “videos” directory –

/var/www/app/current/uploads/videos/1/original/example_one.ogg

/var/www/app/current/uploads/videos/2/original/example_two.ogg

This leads me to believe the issue is with the alias mapping, i.e.

/var/www/app/current/uploads/videos/=/video;

I tried changing it to the following, but that did not work. I’m not
even sure if that type of mapping is allowed. Does anyone know if alias
mapping allows regex? I couldn’t find any thing in the docs.

/lessons/(.*)/video/=/var/www/app/current/uploads/videos/$1/original/;

Appreciate the help.

-Ari

Appreciate the help.

-Ari

I also tried hard-coded values, but that still results in a 404
error.

upstream xxx.xxx.xxx.xxx {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}

location ~ /lessons/1/video/(.*) {
internal;
alias /var/www/app/current/uploads/videos/1/original/$1;
}

location / {
proxy_redirect off;

         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

/lessons/1/video/=/var/www/app/current/uploads/videos/1/original/;

         proxy_connect_timeout 75;
         proxy_send_timeout 60;
         proxy_read_timeout 60;
         root /var/www/app/current/public;

         if (!-f $request_filename) {
             rewrite ^(.*)$ $1 break;
             proxy_pass http://xxx.xxx.xxx.xxx;
             break;
         }
     }

On Wed, May 09, 2012 at 11:21:23PM +0200, Ari K. wrote:

Hi there,

I modified the applications url paths to–

www.foo.bar.com/recipes/:id/video/:filename

Probably you want to include “~” in your second location directive:

Tried adding the (regex) tilde, but that did not solve the issue.

Ok, so we’ll have to try to find exactly what you want/expect to happen.

We can then compare it to what currently happens, and see what to change
to make things match.

So: if the client wishes to get access to the file “test.video”, what
public url will they use to access it?

www.foo.bar.com/recipes/1/video/example_one.ogg

For this request, the “location” is “/recipes/1/video/example_one.ogg”.

If your configuration is:

location / {}
location /recipes/(.*)/video {}

then that will match the first location.

If your configuration is

location / {}
location ~ /recipes/(.*)/video {}

then it will match the second location.

If that request is processed in the first location, I think it will try
to serve the file

/var/www/app/current/recipes/1/video/example_one.ogg

if it exists, or else proxy_pass to

应用宝官网-全网最新最热手机应用游戏下载

If the request is processed in the second location, it will return 404
because that location is marked “internal”.

What is the X-Accel-Redirect header that the backend sends to nginx?

How can I find this out? When I curl directly to the backend server I
get these headers: see http://pastebin.com/CVLjhLRJ

I suspect that your backend (rails?) server pays attention to the
various
headers you have configured using “proxy_set_header”, and will respond
differently if they are present. If you repeat the curl command with
some or all of those, you might see a different response.

I’d probably look in the debug log to find what nginx thinks is going
on;
either that or use something like tcpdump to see what is happening.

And where on the filesystem is the file “test.video”?

The video files are stored according to their respective ids; note the
paths are different AFTER the “videos” directory –

/var/www/app/current/uploads/videos/1/original/example_one.ogg

/var/www/app/current/uploads/videos/2/original/example_two.ogg

Ok, so it’s a file path which can be derived from components of the url.

This leads me to believe the issue is with the alias mapping, i.e.

/var/www/app/current/uploads/videos/=/video;

What’s that?

I see it in your

  proxy_set_header  X-Accel-Mapping 

/var/www/app/current/uploads/videos/=/video;

directive, but that’s nothing to do with nginx (unless I’m missing
something).

I tried changing it to the following, but that did not work. I’m not
even sure if that type of mapping is allowed. Does anyone know if alias
mapping allows regex? I couldn’t find any thing in the docs.

I’m not aware of alias mapping.

The docs for the nginx alias directive are at Module ngx_http_core_module

You can use regex captures in the alias directive.

/lessons/(.*)/video/=/var/www/app/current/uploads/videos/$1/original/;

I’m still unclear on what it is that you want.

When someone requests the url

www.foo.bar.com/recipes/1/video/example_one.ogg

do you want nginx to directly send them the contents of the file

/var/www/app/current/uploads/videos/1/original/example_one.ogg

or do you want your rails backend server to do some processing and
decide whether or not they should be sent the contents of that file,
or do you want something else to happen? (If “something else”, then
describe what else.)

If we can start with a clear understanding of the answer to that
question,
then the rest of the configuration may become obvious.

f

Francis D. [email protected]

On Thu, May 10, 2012 at 12:17:14AM +0200, Ari K. wrote:

I also tried hard-coded values, but that still results in a 404
error.

location ~ /lessons/1/video/(.*) {
internal;

“internal” means “return 404 if this is requested directly”.

http://nginx.org/r/internal

          alias /var/www/app/current/uploads/videos/1/original/$1;
      }

f

Francis D. [email protected]

or do you want your rails backend server to do some processing and
decide whether or not they should be sent the contents of that file,
or do you want something else to happen? (If “something else”, then
describe what else.)

I want rails to authenticate/authorize access to the content in question
and then have nginx serve that file. The following articles
explain/demonstrate what I’m trying to do:

http://rack.rubyforge.org/doc/Rack/Sendfile.html

Thanks.

-Ari

On Thu, May 10, 2012 at 10:46:41PM +0200, Ari K. wrote:

or do you want your rails backend server to do some processing and
decide whether or not they should be sent the contents of that file,

I want rails to authenticate/authorize access to the content in question
and then have nginx serve that file. The following articles
explain/demonstrate what I’m trying to do:

http://rack.rubyforge.org/doc/Rack/Sendfile.html

That seems pretty clear, and seems to match what was mentioned earlier
in the thread.

You must configure the nginx location that corresponds to “/public/url”,
to send the request to rails.

You must configure the rails side to send back a response with
“X-Accel-Redirect: /private/url”.

You must configure the nginx location that corresponds to “/private/url”
to serve the correct file.

It is probably simplest (and matches the examples on those two pages
most closely) if the /private/url that rails returns maps directly to
a file on the filesystem.

On the nginx side, you want two location{}s – one which matches
/public/url and one which matches /private/url.

On the rails side, do whatever it takes to get it to turn /public/url
into /private/url. That will probably involve the alias mapping thing
you mentioned earlier – but that’s a rails thing, so you’ll get better
help on a rails list for that.

Your thedataasylum.com link does seem to give a strong hint what it is
about, though – in rails code you call send_file() with the actual
filename you wish to have sent, and then the X-Accel-Mapping value
replaces the first bit of that filename with the private url prefix.

It appears that what you have right now is the location that matches
/public/url not sending the request to rails. And you don’t appear to
have any specific location matching the private url, which seems to be
of the form “/video/1/original/example_one.ogg”.

So, probably, get rid of your “location /recipes/” thing, and add a
“location /video/” with a suitable alias or root directive.

If you’re still having difficulties, I suggest you show your nginx
config,
and show the curl request and response when you make the request of the
rails server directly, including “-i” for the headers, and five “-H”
arguments, one for each “proxy_set_header” value that you have in your
nginx config.

(And if that doesn’t make it clear where the problem is, probably also
show the line in your rails code which does “send_file()”. But that bit
will probably be more interesting to a rails list.)

Good luck with it,

f

Francis D. [email protected]