Upload progress with NGINX

Damien Tanner

This post was originally published on the New Bamboo blog, before New Bamboo joined thoughtbot in London.


Last month Brice Figureau released a little NGINX module which tracks the progress of uploads going through NGINX. Like Lighttpd’s mod_uploadprogress it’s very nice to have the proxy take responsibility for reporting the progress.

The NGINX upload progress module adds track_uploads and report_uploads directives which do what they say on the tin. When the user submits a form to the location supporting track_uploads, we also send an extra X-Progress-ID parameter with a unique id for that upload. Requests made to the location supporting report_uploads (typically /uploads) will return JSON with the current progress for an upload. The X-Progress-ID header is added to the AJAX requests to identify which upload we want to know the status of.

Having NGINX track the progress gets around an issue with using the Merb upload progress method. NGINX (un)fortunately does not pass along the upload to its destination until it has fully completed uploading. So when you upload a file, your Merb application won’t know about it until it has been upload to NGINX and passed along. The only gripe with this whole process is the possibility of a small delay as Merb handles the request once the upload is complete. If you’re using Rails however this can be an advantage, as it won’t be tied up by the upload as it happens in realtime.

To get this all going you’ll first want to download the NGINX upload progress module and recompile NGINX with the following configure option: --add-module=path/to/nginx_uploadprogress_module.

Locate and open your NGINX configuration file and add in the upload_progress, track_uploads and report_uploads directives.

If you’re going to upload files of any considerable size you’ll also want to the max_body_side to something reasonable.

http {
    upload_progress proxied 1m;

    server {
        listen 80;
        server_name _ *;
        root /usr/local/www;

        location / {
            proxy_pass http://127.0.0.1:4000;
            track_uploads proxied 30s;
            client_max_body_size 500m;
        }

        location ^~ /progress {
            report_uploads proxied;
        }
    }
}

NGINX is now ready to track uploads! Generate a Merb project and add a new upload controller.

class Uploader < Application
  def index
    render
  end

  def upload
    FileUtils.mv params[:file][:tempfile].path, _ROOT+"/files/#{params[:file][:filename]}";
  end
end

Then add the upload view.

<div id="pandaloader">
  <div id="uploader">
    <form id="upload" enctype="multipart/form-data" action="/uploader/upload" method="post">
      <input name="file" type="file">
      <input type="submit" value="Upload">
    </form>
  </div>

  <div id="uploading">
    <div id="progress" class="bar">
      <div id="progressbar">& </div>
    </div>
  </div>
</div>

In the header of your application’s layout you’ll also need to include the JavaScript and CSS.

<%= css_include_tag 'upload' %>
<%= js_include_tag 'jquery' %>
<%= js_include_tag 'jquery.nginxUploadProgress.js' %>

<script type="text/javascript" charset="utf-8">
  $(function() {
    nginxUploadProgress();
  });
</script>

Aside from jQuery, you’ll notice the jquery.nginxUploadProgress.js plugin which has been included. This is a little jQuery library which will query NGINX for the upload’s progress and update the length of the progress bar accordingly.

Here is some styling for the progress bar.

.bar {
  width: 300px;
}

#progress {
  background: #eee;
  border: 1px solid #222;
}
#progressbar {
  width: 0px;
  height: 24px;
  background: #333;
}

Put all these together and you’ll have a great little progress bar for uploads!