Thanks Ben, One more question, How does this work in a dev environment without unicorn / nginx ? I'm just running the default "rails s" WEBrick server on dev.
I'm guessing the answer will be "dev & production should run an identical stack" so you can test / debug these things before deploy. Cheers, On Nov 4, 9:22 am, Ben Hoskings <[email protected]> wrote: > You should move the files outside of the public/ directory, into something > like data/ or assets/ at the top level of your app, and serve them through > authenticated requests with nginx. > > The way it works is that instead of rails sending the data itself, it adds a > header to the response containing the filesystem path to the file. The header > is one that nginx recognises, and it inserts the file data into the response. > > This is far better than serving the file with rails because serving static > content is what nginx is built to do, extremely fast; doing it with rails is > really inefficient because you have to load the contents of the file into > ruby land. It's slow and your rails process' resident size will balloon. When > nginx serves the content, however, it streams the file from disk in the > fastest way possible on your OS, using a fixed amount of memory no matter how > large the file is. > > You serve them from an internal location in nginx. This is a location that > isn't accessible publicly, but that nginx will serve from if asked to by the > upstream. Add something like this to your vhost config (replace /assets with > the path within your app (outside public/) that you chose for the images): > > location /assets { > internal; > root /srv/http/your.app/current; # don't include public/ > > } > > Then, to serve a file within the action on one of your routes, you just set > the right headers for nginx, and render an empty response: > > def photo > # The model and paths are totally made up cause I haven't seen your app :) > photo = Photo.find(params[:id]) > x_accel_redirect "/assets/photos/#{photo.id}/#{photo.style}.jpg", > :disposition => 'attachment', > :filename => photo.filename, > :type => photo.content_type > end > > private > > # Maybe put this private method in application_controller or > # in a module if you want to use it in a few different places. > def x_accel_redirect path, opts > send_file_headers!({ > :disposition => 'attachment' > }.merge(opts)) > response.headers['X-Accel-Redirect'] = path > render :nothing => true > end > > There's a way to do this by instructing rails to, using > "config.action_dispatch.x_sendfile_header", but I can't remember it offhand. > I prefer to just explicitly set the headers anyway, it's easy and there are > no shenanigans that way. > > - Ben > > On 03/11/2011, at 10:28 PM, markbrown4 wrote: > > > > > > > > > Yes, the routes were setup so photo's & documents are only accessible > > to logged in users. > > The files are in a /private/ directory. > > > I'm new to nginx and rails so I need to take baby steps through this > > one. > > If I delete the routes how else can I authenticate and serve the > > images with the headers? > > > Here's the current config > > -- > > server_name <%= var :domain %> <%= var :extra_domains, :default => '' > > %> <%= var :www_aliases %>; > > root <%= var(:app_root) / 'public' %>; > > > if ($host !~ ^<%= var(:domain).gsub('.', '\.') %>$) { > > rewrite ^(.*)$ http://<%= var(:domain) %>$1 permanent; > > } > > > location ~* \.(js|css|jpe?g|png|gif|ico|eot|woff|ttf|swf)$ { > > if ($request_uri ~ "\?[0-9]+$") { > > expires max; # cache timestamped assets forever... > > break; > > } > > if ($request_uri !~ "\?[0-9]+$") { > > expires 1d; # ...and others for 1 day > > break; > > } > > } > > > error_page 503 /system/maintenance.html; > > location /system/maintenance.html { return 503; } > > > try_files /system/maintenance.html $uri/index.html $uri.html $uri > > @app; > > > location @app { > > proxy_pass http://<%= upstream_name %>; > > proxy_redirect off; > > > proxy_buffer_size 64k; > > proxy_buffers 32 16k; > > client_max_body_size 128m; > > > proxy_set_header Host $host; > > proxy_set_header X-Real-IP $remote_addr; > > proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; > > } > > -- > > > Here's an example of the current action. > > > class ResourceController < ActionController::Base > > > def resume > > head(:not_found) and return if (user = > > Person.find_by_id(params[:id])).nil? > > head(:forbidden) and return unless user.downloadable? > > (current_person) > > > path = user.resume.path(params[:style]) > > > if File.exist? path > > send_file(path, :type => user.resume.content_type) > > end > > end > > > end > > > -- > > > Thanks, > > > On Nov 3, 5:48 pm, Ben Hoskings <[email protected]> wrote: > >> Do you need to serve those assets through the stack? Serving them with > >> nginx will be orders of magnitude more efficient (i.e. webscale). > > >> Anything that would otherwise 404 will be passed to your app. If nginx is > >> serving them then they're present on disk, so I'd delete the routes and > >> let nginx take care of it :) > > >> If you want authentication in front of those routes, then you should move > >> them outside public/, and use the 'X-Accel-Redirect' header to serve them > >> with nginx on authenticated requests. > > >> - Ben > > >> On 03/11/2011, at 11:02 AM, markbrown4 wrote: > > >>> Hi, > > >>> We have routes setup for downloading images / documents > > >>> get 'photo/:id/:style.:format', :controller => 'resource', :action > >>> => 'photo' > >>> get 'photos/thumb/missing.png', :controller => 'resource', :action > >>> => 'photo_missing' > >>> get 'resume/:id/:style.:format', :controller => 'resource', :action > >>> => 'resume' > >>> get 'file/:id/:style.:format', :controller => 'resource', :action > >>> => 'file' > > >>> Nginx config is skipping the routing though. > > >>> Do I need to prevent all images from being served directly to resolve > >>> it or is there a better way? > >>> location ~* \.(js|css|jpe?g|png|gif|ico|eot|woff|ttf|swf)$ > > >>> Thanks, > > >>> -- > >>> You received this message because you are subscribed to the Google Groups > >>> "Ruby or Rails Oceania" group. > >>> To post to this group, send email to [email protected]. > >>> To unsubscribe from this group, send email to > >>> [email protected]. > >>> For more options, visit this group > >>> athttp://groups.google.com/group/rails-oceania?hl=en. > > > -- > > You received this message because you are subscribed to the Google Groups > > "Ruby or Rails Oceania" group. > > To post to this group, send email to [email protected]. > > To unsubscribe from this group, send email to > > [email protected]. > > For more options, visit this group > > athttp://groups.google.com/group/rails-oceania?hl=en. -- You received this message because you are subscribed to the Google Groups "Ruby or Rails Oceania" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/rails-oceania?hl=en.
