File upload progress with Rails and nginx
At letsannotate.com users upload .pdf files and we convert them to images so that they work on every device. We wanted to show our users not only how far their upload had progressed, we also wanted to inform them about the status of the .pdf conversion.
We use Rails on nginx via Passenger, but there are solutions for both Apache and lighttpd.
nginx
First step: Install and configure nginx’s upload progress module.
Rails
Now we need to update our Rails controllers and views.
We use assaf’s great uuid gem to generate different identifiers for each upload.
def new
@document = Document.new
@uuid = UUID.generate :compact
end
When creating our form we use the hidden_field ‘progress_token’ to pass the job’s uuid back to the server. Now once the user clicks on submit some browsers will block intermittent AJAX requests. To work around that we need an iframe (:target => "uploadframe")
<% form_for(@document, :url => "/documents/?X-Progress-ID=#{@uuid}", :html => {:multipart => true, :target => "uploadframe", :onsubmit => "startUpload();"}) do |f| %>
<%= f.hidden_field 'progress_token', :value => @uuid %>
<%= f.file_field :pdf_file %>
<%= f.submit 'Upload' %>
<% end %>
<div id="progress" style="width: 400px; background-color: silver;">
<div id="progressbar" style="width: 1px; height: 10px; background-color: black;"></div>
</div>
<div id="message"></div>
<iframe id="uploadframe" name="uploadframe" width="100%" height="100%" frameborder="0" border="0" src="about:blank"></iframe>
Javascript
During the upload we query nginx every second how far along our file upload is. Once our file has been transfered completely we start querying our application how far the post-processing has come along.
You can find this code - which is a modified version of the nginx example at this gist where you’ll also find expanded example code.
Now once the upload and post-processing has finished we need a way to redirect not only the iframe but also its parent. We’ll need to use a hack since parent.document.location is read only.
<!-- iframe -->
<script>parent.changeParentUrl('<%= new_url %>');</script>
// parent frame
function changeParentUrl(url) { document.location = url; }
Enter redis
One thing’s left: While Rails (or a Resque/delayed_job worker) post-processes our document we have no way to get at its status. Redis to the rescue. We’ll simply update a key (e.g. pdf_progress:1234) every time the document’s status changes.
I hope this explains in detail how to create a seamless experience for file uploads. If there’s anything you’d like to know or if you believe I should create a complete example app, just leave a comment.