From a7d0667498c311a2814588cafbeda051ebb1c0aa Mon Sep 17 00:00:00 2001 From: Martin Kleppmann Date: Tue, 9 Aug 2011 16:35:50 -0700 Subject: [PATCH 1/8] Missing rack-test gem dependency --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 396302e..c300903 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,5 @@ gemspec group :test do gem 'shoulda' + gem 'rack-test' end From 915e49a5e73404e5d0c9396d250313a79618f5cc Mon Sep 17 00:00:00 2001 From: Martin Kleppmann Date: Tue, 9 Aug 2011 17:59:03 -0700 Subject: [PATCH 2/8] Support for adding CORS headers to asynchonous Rack responses --- lib/rack/cors.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index 0b940c8..42ad0a6 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -48,12 +48,30 @@ def call(env) cors_headers = process_cors(env) end end - status, headers, body = @app.call env - headers = headers.merge(cors_headers) if cors_headers - [status, headers, body] + process_request(env, cors_headers) end protected + def process_request(env, cors_headers) + async = true + status, headers, body = catch :async do + @app.call(env).tap { async = false } + end + + if async + original_callback = env['async.callback'] + env['async.callback'] = proc do |response| + status, headers, body = response + headers = headers.merge(cors_headers) if cors_headers + original_callback.call([status, headers, body]) + end + throw :async + else + headers = headers.merge(cors_headers) if cors_headers + [status, headers, body] + end + end + def debug(env, message = nil, &block) logger = @logger || env['rack.logger'] || begin @logger = ::Logger.new(STDOUT).tap {|logger| logger.level = ::Logger::Severity::INFO} From 92a3f8b948b036e8de877090b47a0733cd2c60b9 Mon Sep 17 00:00:00 2001 From: Sam Stokes Date: Wed, 10 Aug 2011 11:51:42 -0700 Subject: [PATCH 3/8] More concise and idiomatic response destructuring --- lib/rack/cors.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index 42ad0a6..d1cde0e 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -60,8 +60,7 @@ def process_request(env, cors_headers) if async original_callback = env['async.callback'] - env['async.callback'] = proc do |response| - status, headers, body = response + env['async.callback'] = proc do |status, headers, body| headers = headers.merge(cors_headers) if cors_headers original_callback.call([status, headers, body]) end From e3c0e5743a10f4d4d85fea0908ee623f9428865c Mon Sep 17 00:00:00 2001 From: Sam Stokes Date: Wed, 10 Aug 2011 11:54:04 -0700 Subject: [PATCH 4/8] Comment explaining the purpose and approach of the :async handling --- lib/rack/cors.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index d1cde0e..f44ca90 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -53,6 +53,16 @@ def call(env) protected def process_request(env, cors_headers) + # The thin web server allows apps to signal async request processing by + # throwing the symbol :async, then signalling completion later by + # invoking env['async.callback']. + # + # Here we ensure compatibility with that protocol: if @app.call throws + # :async, we catch it, wrap async.callback to insert the appropriate + # CORS response headers, then rethrow :async up to thin. If @app.call + # completes without throwing, we just add our headers immediately in + # the normal synchronous fashion. + async = true status, headers, body = catch :async do @app.call(env).tap { async = false } From c7591bfe512e98bbc01b84a3b63b36f33774f7e7 Mon Sep 17 00:00:00 2001 From: Sam Stokes Date: Wed, 10 Aug 2011 11:54:46 -0700 Subject: [PATCH 5/8] Comment clarifying subtle control flow --- lib/rack/cors.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index f44ca90..d010e9f 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -65,7 +65,10 @@ def process_request(env, cors_headers) async = true status, headers, body = catch :async do - @app.call(env).tap { async = false } + @app.call(env).tap do + # if we got this far, then @app.call completed without throwing. + async = false + end end if async From 28f67698fc5d42c5f426bedd90c49933dd81f11d Mon Sep 17 00:00:00 2001 From: Sam Stokes Date: Wed, 10 Aug 2011 11:59:55 -0700 Subject: [PATCH 6/8] Restructure catch(:async) control flow to be a bit more obvious --- lib/rack/cors.rb | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index d010e9f..100945b 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -63,25 +63,20 @@ def process_request(env, cors_headers) # completes without throwing, we just add our headers immediately in # the normal synchronous fashion. - async = true status, headers, body = catch :async do - @app.call(env).tap do - # if we got this far, then @app.call completed without throwing. - async = false - end + status, headers, body = @app.call(env) + # if we got this far, then @app.call completed without throwing. + headers = headers.merge(cors_headers) if cors_headers + return [status, headers, body] end - if async - original_callback = env['async.callback'] - env['async.callback'] = proc do |status, headers, body| - headers = headers.merge(cors_headers) if cors_headers - original_callback.call([status, headers, body]) - end - throw :async - else + # if we ended up here, must have caught :async (skipping the 'return') + original_callback = env['async.callback'] + env['async.callback'] = proc do |status, headers, body| headers = headers.merge(cors_headers) if cors_headers - [status, headers, body] + original_callback.call([status, headers, body]) end + throw :async end def debug(env, message = nil, &block) From dd45f4575d6330922337c60d32ccd12f19b79806 Mon Sep 17 00:00:00 2001 From: Sam Stokes Date: Wed, 10 Aug 2011 19:55:27 -0700 Subject: [PATCH 7/8] Remove unused and wrong assignment --- lib/rack/cors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index 100945b..5613122 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -63,7 +63,7 @@ def process_request(env, cors_headers) # completes without throwing, we just add our headers immediately in # the normal synchronous fashion. - status, headers, body = catch :async do + catch :async do status, headers, body = @app.call(env) # if we got this far, then @app.call completed without throwing. headers = headers.merge(cors_headers) if cors_headers From 225b4c48c8a6533dd97248bad4a4ede824bbe622 Mon Sep 17 00:00:00 2001 From: Sam Stokes Date: Wed, 10 Aug 2011 19:56:05 -0700 Subject: [PATCH 8/8] Revert 92a3f8b, it was completely wrong --- lib/rack/cors.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rack/cors.rb b/lib/rack/cors.rb index 5613122..5efdc2f 100644 --- a/lib/rack/cors.rb +++ b/lib/rack/cors.rb @@ -72,7 +72,8 @@ def process_request(env, cors_headers) # if we ended up here, must have caught :async (skipping the 'return') original_callback = env['async.callback'] - env['async.callback'] = proc do |status, headers, body| + env['async.callback'] = proc do |response| + status, headers, body = response headers = headers.merge(cors_headers) if cors_headers original_callback.call([status, headers, body]) end