Fun and games
The application I’ve been working on this month uses FancyUpload to provide a snazzy UI for uploading image files that are then processed and stored using Paperclip and ImageMagick. FancyUpload is a JavaScript and Shockwave Flash based solution that gives you all kinds of goodness like multiple file uploads, progress bars and status information. Unfortunately getting FancyUpload, as well as any other form of flash based uploader such as SWFUpload, to play nice with Rails requires some work.
I won’t delve too deeply into the various problems as much has already been written about them, along with possible solutions. Instead here’s some background reading:
- AirBlade Software’s Uploading Files With SWFUpload
- Jim Neath’s SWFUpload, Paperclip and Ruby on Rails
- SWFUpload’s Flash Cookie Bug
Unfortunately the CGI patching approach suggested in the above blogs presented a problem for me: I’m running on edge Rails which over the last couple of weeks has pretty much shed all of its CGI baggage so it’s unlikely the patches would work. What to do?
Bring on the wall middleware!
My first thought * was “surely this is something middleware can help out with” and so a quick Google yielded exactly that, the only problem being it was for Merb.
* actually my first thought was “it must be time for a tea break”, but middleware was a close second.
Here’s the code (by Angel Pizarro):
module Merb
module Rack
class SwfSetSessionCookie < Merb::Rack::Middleware
# :api: private
def initialize(app, session_key = '_session_id')
super(app)
@session_key = session_key
end
# :api: plugin
def call(env)
if env["HTTP_USER_AGENT"] =~ /Adobe Flash/
prms = Merb::Parse.query(env['QUERY_STRING'])
env['HTTP_COOKIE'] = [@session_key,prms[@session_key]].join('=').freeze
end
@app.call(env)
end
end
end
end
What this does is check the request environment for Flash in the HTTP_USER_AGENT header, if a match is found then the request query string is parsed for session data (the session_key is passed to the middleware when it is initialised) which is then forced into the HTTP_COOKIE header so that when the request reaches Merb it treats the session data just like it would in any other request.
I’m pleased to say that converting this into middleware that will work with Rails (and in theory any other Rack application) was extremely simple, especially once I discovered that the Rack::Utils library provides an implementation of the Merb::Parse.query method. The only functional change I needed to make was to the HTTP_USER_AGENT regular expression so that it would work with newer versions of Flash. Here’s the code:
require 'rack/utils'
class FlashSessionCookieMiddleware
def initialize(app, session_key = '_session_id')
@app = app
@session_key = session_key
end
def call(env)
if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
params = ::Rack::Utils.parse_query(env['QUERY_STRING'])
env['HTTP_COOKIE'] = [ @session_key, params[@session_key] ].join('=').freeze unless params[@session_key].nil?
end
@app.call(env)
end
end
Integration with Rails
Getting the middleware to work with Rails involved putting the above code in my newly created RAILS_ROOT/app/middleware folder in a file snappily named flash_session_cookie_middleware.rb. At the moment there doesn’t appear to be a convention for where these files should live, but this makes sense to me as the new Metal endpoints will live in RAILS_ROOT/app/metal. The middleware folder then needs adding to the load path in environment.rb:
config.load_paths += %W( #{RAILS_ROOT}/app/middleware )
Rails provides a new config.middleware.use setting that can be used in environment.rb to specify middleware classes, however because I wanted to pass the session key to the constructor I decided to do the following in my RAILS_ROOT/config/initializers/session_store.rb initializer instead (session store configuration was moved to an initializer in this commit):
ActionController::Base.session = {
:session_key => '_my_app_session',
:secret => '--blah--' # Real key removed to protect the innocent
}
ActionController::Dispatcher.middleware.use FlashSessionCookieMiddleware, ActionController::Base.session_options[:session_key]
The first part of the initializer sets the session key and secret as normal, the next part calls ActionController::Dispatcher.middleware.use to register the FlashSessionCookieMiddleware class passing it the session key as a parameter.
The final step is to pass the session data and authenticity token as URL parameters so that they can be picked up by the middleware. In our application we have an assets controller, so our upload form_for originally looked like this:
form_for(@asset, :multipart => true)
This was changed like so:
form_for(@asset, :url => new_asset_path_with_session_information, :multipart => true)
And in our AssetsHelper module, a new_asset_path_with_session_information helper was added:
def new_asset_path_with_session_information
session_key = ActionController::Base.session_options[:session_key]
new_asset_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token)
end
Our Javascript code then uses the form URL when initialising the FancyUpload component, when a file is uploaded it will use a HTTP POST to a URL like this:
/assets?_my_app_session=BAh7CDoPc2Vzc2lvbl9pZCIlODViNmRjMWU1ODZiMGEwNmUxZGEzNzE0MzkyMGU3NzM6DHVzZXJfaWRpBjoQX2NzcmZfdG9rZW4iMWhWWXJwWU1QeHZyWTVEdG42WVlJeEhEN21GNTdhVVFPUnd3dHJpNGNJYU09--87e9b5a0f7ac3050b0ee61e3a46ae08ce825319d&authenticity_token=hVYrpYMPxvrY5Dtn6YYIxHD7mF57aUQORwwtri4cIaM%3D
The middleware will take the data in the _my_app_session parameter and convert it back into a cookie, and Rails will use the authenticity_token parameter to verify the request. So as far as Rails is concerned this is a regular POST request using the current session with a valid authenticity token: it has no clue that this really came from the FlashUpload component.
This is just the beginning
This has been my first experiment in writing Rack middleware and I think it is a great example of how it can be used to solve problems in a clean, straight-forward way without having to resort to the hackery of days gone by. My middleware shows some really very simple functionality, however this should be enough to make you want to find out what more can be achieved. A good place to start is Ryan Tomayko’s rack-contrib repository on GitHub and the Rack development Google group.
Have fun, and try not to get too carried away!
An update for Rails edge: March 2009
Thanks to Saimon Moore for his comment pointing out that Rails edge has renamed the :session_key option to :key. If you’re running on edge then you’ll need to make the following changes:
-
In the RAILS_ROOT/config/initializers/session_store.rb initializer:
ActionController::Base.session = { :key => '_my_app_session', :secret => '--blah--' # Real key removed to protect the innocent } ActionController::Dispatcher.middleware.use FlashSessionCookieMiddleware, ActionController::Base.session_options[:key] -
And in the
AssetsHelper#new_asset_path_with_session_informationmethod:def new_asset_path_with_session_information session_key = ActionController::Base.session_options[:key] new_asset_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token) end
An update for Rails 2.3.2: June 2009
Following his comment about the move of Rails cookie handling into middleware, David North kindly emailed us with the necessary changes to allow our Flash cookie to work with Rails 2.3.2. Without this change you will likely still get InvalidAuthenticityToken exceptions.
In the RAILS_ROOT/config/initializers/session_store.rb initializer we need to inject our middleware before the new Rails cookie middleware. To do this change the ActionController::Dispatcher.middleware.use call to this:
ActionController::Dispatcher.middleware.insert_before(ActionController::Session::CookieStore, FlashSessionCookieMiddleware, ActionController::Base.session_options[:key])
You can double check your middleware order is correct using the rake middleware task on the command line. The output should look something like this:
use Rack::Lock use ActionController::Failsafe use ActionController::Reloader use FlashSessionCookieMiddleware, "_my_app_session" use ActionController::Session::CookieStore, #<Proc:0x02445eb0@(eval):8> use ActionController::RewindableInput use ActionController::ParamsParser use Rack::MethodOverride use Rack::Head use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache run ActionController::Dispatcher.new
As you can see our FlashSessionCookieMiddleware appears before ActionController::Session::CookieStore which means our cookie fiddling will take place before Rails tries to use it.
If you want to find out more on Rails and Rack integration you should take a look at the new Rails on Rack guide.
A better way of inserting middleware: July 2009
Tung Nguyen commented with a better, more generic way of adding the middleware to your stack, meaning you no longer have to double check where to insert it:
ActionController::Dispatcher.middleware.insert_before(ActionController::Base.session_store, FlashSessionCookieMiddleware, ActionController::Base.session_options[:key])


64 comments
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by yaroslav
February 10th, 2009 @ 17:44 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Yaroslav
February 11th, 2009 @ 13:25 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
February 12th, 2009 @ 08:21 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Saimon Moore
March 3rd, 2009 @ 14:11 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
March 3rd, 2009 @ 17:48 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by lardawge
March 19th, 2009 @ 17:34 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Jakob
April 6th, 2009 @ 12:37 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
April 7th, 2009 @ 08:03 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by David North
April 15th, 2009 @ 14:15 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
April 20th, 2009 @ 08:26 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by shih-gian Lee
May 5th, 2009 @ 16:39 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Shih-gian Lee
May 8th, 2009 @ 16:05 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
May 10th, 2009 @ 16:45 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Tom
May 18th, 2009 @ 13:51 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by David North
May 21st, 2009 @ 20:18 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
June 2nd, 2009 @ 09:06 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Zubin
June 4th, 2009 @ 21:50 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Pina
July 2nd, 2009 @ 03:55 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Tung Nguyen
July 2nd, 2009 @ 20:03 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
July 11th, 2009 @ 23:15 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Evgeniy Dolzhenko
July 22nd, 2009 @ 18:44 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by ebuyc
August 25th, 2009 @ 19:10 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by ebuyc
August 25th, 2009 @ 19:53 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by ebuyc
August 25th, 2009 @ 20:26 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
August 25th, 2009 @ 23:11 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Poster
August 26th, 2009 @ 20:45 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by ebuyc
August 26th, 2009 @ 23:19 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
August 27th, 2009 @ 23:14 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by ebuyc
August 28th, 2009 @ 19:21 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Stephen England
September 3rd, 2009 @ 12:47 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
September 6th, 2009 @ 12:36 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Ben Reubenstein
September 21st, 2009 @ 18:35 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Ben Reubenstein
September 21st, 2009 @ 19:11 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Trevor Rowe
October 7th, 2009 @ 16:30 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Mirko Kirović
November 2nd, 2009 @ 08:02 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Mirko Kirović
November 2nd, 2009 @ 08:06 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
November 2nd, 2009 @ 08:19 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by UVSoft
November 8th, 2009 @ 12:24 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
November 10th, 2009 @ 08:20 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Bert Goethals
November 17th, 2009 @ 18:00 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Bert Goethals
November 18th, 2009 @ 10:52 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by acid
December 8th, 2009 @ 22:07 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Bertgoethals
December 9th, 2009 @ 08:29 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by acid
December 9th, 2009 @ 14:51 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
December 9th, 2009 @ 19:31 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by acid
December 10th, 2009 @ 16:32 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
December 11th, 2009 @ 15:08 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by acid
December 11th, 2009 @ 16:04 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
December 11th, 2009 @ 16:32 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Claudio Poli
December 20th, 2009 @ 19:06 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Claudio Poli
December 20th, 2009 @ 19:22 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
December 21st, 2009 @ 08:53 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by acid
January 13th, 2010 @ 00:58 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
January 13th, 2010 @ 10:17 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Daniel Brown
February 2nd, 2010 @ 18:04 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Evan
March 16th, 2010 @ 13:06 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Evan
March 16th, 2010 @ 15:12 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
March 16th, 2010 @ 18:00 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Michael Hasenstein
March 31st, 2010 @ 09:09 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Patrick Berkeley
April 9th, 2010 @ 20:57 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Rob Anderton
April 12th, 2010 @ 22:15 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Patrick Berkeley
April 14th, 2010 @ 06:08 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by toy
April 20th, 2010 @ 10:20 – permalink
Comment on Flash uploaders, Rails, cookie based sessions and CSRF: Rack Middleware to the rescue! by Lars Pind
April 20th, 2010 @ 22:40 – permalink
Leave a reply
You can use Markdown in your comment as well as plain HTML. You can use
<filter:jscode lang="ruby">and</filter:jscode>tags to surround code blocks (supported languages are css, html, javascript and ruby). Your email address will not be published.If your comment doesn’t appear immediately after posting it could have been marked as spam. Don’t worry: we regularly check for and approve incorrectly filtered comments so you shouldn’t have to wait too long for it to be shown.