about summary refs log tree commit homepage
path: root/site/src/docs/upload_progress.page
diff options
context:
space:
mode:
Diffstat (limited to 'site/src/docs/upload_progress.page')
-rw-r--r--site/src/docs/upload_progress.page136
1 files changed, 136 insertions, 0 deletions
diff --git a/site/src/docs/upload_progress.page b/site/src/docs/upload_progress.page
new file mode 100644
index 0000000..05dcc52
--- /dev/null
+++ b/site/src/docs/upload_progress.page
@@ -0,0 +1,136 @@
+---
+title: Upload Progress
+inMenu: true
+directoryName: Upload Progress
+---
+
+h1. Mongrel Upload Progress Plugin
+
+One of the really nice things about Mongrel is its simplicity.  It's very easy
+for someone to take and extend for their own needs.  The Mongrel Upload
+Progress plugin is an example of how I'm able to extend the Mongrel HTTP
+Request object and provide near-realtime progress updates.
+
+The reason why this is a challenge, is because web servers usually gather the
+HTTP request, send it on to the web framework, and wait on a response.  This is
+fine for most requests, because they're too small to cause an issue.  For large
+file uploads this is a usability nightmare. The user is left wondering what
+whether their upload is going through or not.  
+
+To do this, I've written a Mongrel handler that hooks into some basic Request
+callbacks.  To use it, you need to install the gem, and create a small config
+file for it:
+
+<pre><code>gem install mongrel_upload_progress
+
+# config/mongrel_upload_progress.conf
+uri "/",
+  :handler => plugin("/handlers/upload", :path_info => '/files/upload'),
+  :in_front => true
+
+# start mongrel
+mongrel_rails -d -p 3000 -S config/mongrel_upload_progress.conf
+</code></pre>
+
+That config file tells mongrel to load the Upload handler in front of all other
+handlers.  <code>:path_info</code> is passed to it, telling upload_progress to
+only watch the /files/upload action.  There are two more parameters that I'll
+get into later: <code>:frequency</code> and <code>:drb</code>.  I'm using Rails
+as an example, but this should work with any Ruby framework, such as Camping or
+Nitro.
+
+Now that Mongrel is set up, let's create a "basic upload
+form":/docs/upload_progress_form.rhtml.  If you
+look closely you'll notice a few things:
+
+* A unique <code>:upload_id</code> parameter must be sent to the upload_progress handler.  This is so requests don't get mixed up, and the client page has an ID to query with.
+* The &lt;form> tag is targetted to an iFrame to do the uploading.  Certain browsers (like Safari) won't execute javascript while a request is taken place, so this step is necessary.
+* There is a little "javascript library":/docs/upload_progress_javascript.js being used.  This handles the polling and status bar updates.  
+* Notice the form's action is file/upload, just like the upload_progress handler.
+
+The "Rails controller
+actions":/docs/upload_progress_rails.rb for this are very
+simple.  The upload form itself needs no custom code.  The upload action only
+renders javascript to be executed in the iFrame, to modify the contents of the
+parent page.  The progress action is a basic RJS action that updates the
+current status.  Most of the guts of this are implemented in the javascript
+library.  
+
+Here's what happens when you submit the form:
+
+* The UploadProgress class creates a PeriodicalExecuter and gets ready to poll.
+* The browser initiates the upload.
+* Every 3 seconds, the PeriodicalExecuter calls the RJS #progress action and gets back the current status of the file.  
+* Once finished, the iFrame calls <code>window.parent.UploadProgress.finish()</code>, which  removes the status bar and performs any other finishing actions.
+
+How's this work with a single Mongrel process if "Mongrel synchronizes Rails
+requests":http://david.planetargon.us/articles/2006/08/08/why-you-need-multiple-mongrel-instances-with-rails?
+It's actually very careful about locking, synchronizing only the bare minimum.
+The whole time that Mongrel is receiving the request and updating the progress
+is spent _in_ Mongrel, so it can happily serve other requests.  This is how the
+RJS action is able poll while it's uploading.
+
+This is fine and dandy, but not too many sites run on a single Mongrel.  You'll
+quickly run into problems with multiple mongrels since only one Mongrel process
+knows about the upload.  You'll either have to specify a specific mongrel port
+to communicate with, or set up a dedicated mongrel upload process.  The third
+option, is use DRb.
+
+<pre><code># config/mongrel_upload_progress.conf
+uri "/",
+  :handler => plugin("/handlers/upload",
+    :path_info => '/files/upload',
+    :drb => 'druby://0.0.0.0:2999'),
+  :in_front => true
+
+# lib/upload.rb, the upload drb server
+require 'rubygems'
+require 'drb'
+require 'gem_plugin'
+GemPlugin::Manager.instance.load 'mongrel' => GemPlugin::INCLUDE
+DRb.start_service 'druby://0.0.0.0:2999', Mongrel::UploadProgress.new
+DRb.thread.join</code></pre>
+
+Now in addition to starting mongrel, you'll need to start the DRb service too:
+
+<pre><code>ruby lib/upload.rb</code></pre>
+
+The Rails app should work the same as before, but now it is using a shared DRb
+instance to store the updates.  This gives us one other advantage: a console
+interface to the current uploads.
+
+<pre><code># lib/upload_client.rb, a simple upload drb client
+require 'drb'
+DRb.start_service
+
+def get_status
+  DRbObject.new nil, 'druby://0.0.0.0:2999'
+end
+
+# typical console session
+$ irb -r lib/upload_client.rb
+>> uploads = get_status
+>> uploads.list
+=> []
+# start uploading in the browser
+>> uploads.list
+=> ["1157399821"]
+>> uploads.check "1157399821"
+=> {:size=>863467686, :received=>0}</code></pre>
+
+Using DRb gives you a simple way to monitor the status of current uploads in
+progress.  You could also write a simple web frontend for this too, accessing
+the DRb client with Mongrel::Uploads.
+
+One final note is the use of the <code>:frequency</code> option.  By default,
+    the upload progress is marked every three seconds.  This can be modified
+    through the mongrel config file:
+
+<pre><code>uri "/",
+  :handler => plugin("/handlers/upload",
+    :path_info => '/files/upload',
+    :frequency => 1,
+    :drb => 'druby://0.0.0.0:2999'),
+  :in_front => true</code></pre>
+
+