Nginx Upload Module and Rails

I thought it would be super complicated to get the Nginx Upload Module to work with Rails. Turns out, it is not so bad. It took me eight hours over three days. If you know anything at all about configuring nginx (which I did not) then it will probably not take you nearly as long.

A disclaimer: it looks like nginx clientbodyinfileonly might actually be a better solution than nginx upload module, because it is based on nginx’s built-in functionality. I may try to switch over later. But for now, I am using the upload module.

I followed the instructions and used the exact download urls for the source code as shown on Ray’s Blog. Here are some additional notes.

Regular expressions

If you app has uploads going to multiple controllers, or if you have ids in your urls like /resource_name/:id/upload, then you need regular expressions in your nginx.conf, and you need to add this when you compile nginx. So in addition to getting the upload module, you should also download Perl Compatible Regular Expressions from www.pcre.org

Then, add --with-pcre=path/to/pcre/directory to your ./configure statement.

Upload store

Note that the path in the following code:

  # in nginx.conf
  upload_store /tmp/uploads 1;

is relative to the root of your filesystem or some other weird place, not to the root of your app. Also, this assumes that you want a separate folder for each file, numerically incrementing starting from 1. But then, you must have those numerically incremented folders premade in order to use them. If you have a ton of uploads, then this doesn’t seem reasonable, or I must be missing something. I replaced that line with:

  # in nginx.conf
  upload_store /home/teddy/app/uploads;

Note the absolute path into the rails app root directory, and the fact that there is no ‘1’, so all files will be stored in the same directory. Usually, there is no risk of file names colliding, because nginx renames the files numerically incrementing. I am not certain, but it seems that these auto-incrementing numbers might be reset when restarting Nginx (again, I am not sure), so after restarting nginx, there seems to be some danger of files being overwritten. To be safe, I have a background job that moves the files to a safer, permanent location after they are saved.

Parameters passed to Rails

About these lines:

  # in nginx.conf
  set $upload_field_name "file";
  upload_set_form_field $upload_field_name[original_filename] "$upload_file_name";
  upload_set_form_field $upload_field_name[tempfile] "$upload_tmp_path";
  upload_set_form_field $upload_field_name[content_type] "$upload_content_type";
  upload_aggregate_form_field $upload_field_name[size] "$upload_file_size";

This will result in a parameters hash being passed to Rails like this:

  {
    "file" => {
                "original_filename" => "upload_file_name",
                "tempfile"          => "upload_tmp_path",
                "content_type"      => "upload_content_type",
                "size"              => "upload_file_size"
              }
  }

So, make sure that you set the $upload_field_name to the name of the Rails model that you are using for uploads. So if you have the following line:

  # in nginx.conf
  set $upload_field_name "file";

Then that means that you probably have a model like this:

class File < ActiveRecord::Base
...
end

And a controller like this:

class FilesController < ActiveRecord::Base
  ...
  private
    def file_params
      params.require(:file).permit(:original_filename, :tempfile, :content_type, :size)
    end
end

Location

Looking at this line in the configuration:

  # in nginx.conf
  location = /upload_file {

the equals sign means match this EXACT url and use the upload module for it. But what I wanted was to match urls based on regular expressions. So I changed it to:

  # in nginx.conf
  location ~ "^/(items|remarks)/.*/report_photos$" {

This means any url like /items/12312313/report_photos or /remarks/8888/report_photos will use the upload module.

405 Not Allowed

The railsy URLs: POST /report_photos and GET /report_photos both match the regexp, and Nginx will try to use the upload module in both cases. But the latter returns 405 Not Allowed because the module only supports POST requests. The Docs recommend using an “Error Page” directive to solve this, but for me the only thing I could get to work is this conditional that checks if the request is a POST request:

# In nginx.conf
        location ~ ^/(items|remarks)/.*/report_photos$ {
          if ($request_method = POST) {
            upload_pass @passenger;
            upload_store /home/intouchsys/app/uploads/tmp;
            upload_store_access user:rw group:rw all:r;
            upload_set_form_field       "report_photo[file]"      "$upload_file_name";
            upload_set_form_field       "report_photo[path]"      "$upload_tmp_path";
            upload_aggregate_form_field "report_photo[file_size]" "$upload_file_size";
            upload_pass_form_field "^token$|^authenticity_token$";
            upload_cleanup 400 404 499 500-505;
          }
          passenger_enabled on;
        }

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s