properly handle large file uploads
authorLunar <lunar@anargeek.net>
Sun, 26 Feb 2012 19:44:43 +0000 (20:44 +0100)
committerLunar <lunar@anargeek.net>
Thu, 14 Mar 2013 09:12:08 +0000 (10:12 +0100)
commit96a8d29895f23dffc53b765dea85cb19239b8ba5
treec79d906d03aa7a1a978e249db8919cf5dabf3382
parent333a3b21323ce3cfff312b51b57a28b0a577bee9
properly handle large file uploads

Previously we were using Sinatra::Request to process file uploads. This class
derives from Rack::Request which creates a temporary file for each file
appearing in a POST request. This can be seen as a privacy breach, as it means
uploaded files were first written in clear text before being stored encrypted.
This can be mitigated by storing the tempfile on a "ramdisk", but then, memory
can pretty quick be a limit to the maximum uploaded file size.

But wait, there's more: Rack specify that `rack.input` must be a
seakable/rewindable IO-like object. In order to implement that, Rack webserver
will either buffer the input in memory (Webrick) or in a temporary file
(Thin, Passenger or Mongrel). So in most cases we had not one, but at least two
temporary files for each uploads.

In order to properly process uploaded file content as it arrives, we 1. switch
to use the "Rainbows!" webserver and 2. handle the POST request directly.

Rainbows! has a unique feature of being able to provide a non-buffered input.
While this breaks Rack specification, our own dedicated handler is written
specifically with this in mind.

Handling the POST request as its input flows requires to be careful with the
order in which fields appear in the `<form/>` tag (HTML specification specify
that they will be sent in that particular order). As we want to know all
options before writing the StoredFile, we need to have the `<input
type="file"/>` field at the end of our form. Along the same lines, we ensure
in `coquelicot.js` that hidden fields for authentication values are laid at the
verify begining of the upload `<form/>`.

Coquelicot::Rack::MultipartParser offers a generic interface to parse
`multipart/form-data` requests. It offers a simple DSL to specify which field
is expected, and to run specific block when they shows up.

Coquelicot::Rack::Upload replaces our old `post '/upload'` method. It handles
the request as a bare Rack middleware to be laid on top of the stack. Its code
borrows part of Sinatra's internals in order to get consistent coding
interface.

Huge kudos to Eric Wong for Rainbows! and Daniel Abrahamsson for
multipart-parser which both made this possible.
12 files changed:
Gemfile
Gemfile.lock
README
lib/coquelicot.rb
lib/coquelicot/app.rb
lib/coquelicot/rack/multipart_parser.rb [new file with mode: 0644]
lib/coquelicot/rack/upload.rb [new file with mode: 0644]
public/javascripts/coquelicot.js
spec/coquelicot/rack/multipart_parser_spec.rb [new file with mode: 0644]
spec/coquelicot/rack/upload_spec.rb [new file with mode: 0644]
spec/coquelicot_spec.rb
views/index.haml