Permalink
Browse files

Added Error and Session generic middleware for rack applications.

Details:
* Junkfood::Rack::ErrorHandler is a middleware to catch exceptions from
  rack applications and return formatted JSON responses.
* Junkfood::Rack::TransientSession is a middleware that simply sets the
  'rack.session' environment variable for other middleware and application.
  Main use case is for 'warden' authentication using per request api tokens.
  • Loading branch information...
1 parent f758837 commit 3623e8dfb93d29339fc53c7213d3cae47e0a0fdd @byu committed Nov 15, 2010
Showing with 322 additions and 32 deletions.
  1. +1 −0 Gemfile
  2. +38 −32 Gemfile.lock
  3. +17 −0 README.md
  4. +1 −0 Rakefile
  5. +1 −0 lib/junkfood.rb
  6. +26 −0 lib/junkfood/rack.rb
  7. +92 −0 lib/junkfood/rack/error.rb
  8. +60 −0 lib/junkfood/rack/sessions.rb
  9. +66 −0 spec/junkfood/rack/error_spec.rb
  10. +20 −0 spec/junkfood/rack/sessions_spec.rb
View
@@ -12,6 +12,7 @@ group :development do
# Dependencies that this library actually uses during runtime.
gem 'activesupport', :require => 'active_support/all'
+ gem 'json'
gem 'mongoid', '~> 2.0.0.beta.18'
gem 'wrong'
end
View
@@ -7,59 +7,64 @@ GEM
RubyInline (3.8.6)
ZenTest (~> 4.3)
ZenTest (4.4.0)
- activemodel (3.0.0)
- activesupport (= 3.0.0)
+ activemodel (3.0.2)
+ activesupport (= 3.0.2)
builder (~> 2.1.2)
i18n (~> 0.4.1)
- activesupport (3.0.0)
+ activesupport (3.0.2)
bluecloth (2.0.9)
- bson (1.0.4)
+ bson (1.1.2)
builder (2.1.2)
- diff (0.3.6)
diff-lcs (1.1.2)
+ file-tail (1.0.5)
+ spruz (>= 0.1.0)
git (1.2.5)
- i18n (0.4.1)
- jeweler (1.5.0.pre3)
+ i18n (0.4.2)
+ jeweler (1.5.1)
bundler (~> 1.0.0)
git (>= 1.2.5)
rake
- mongo (1.0.7)
- bson (>= 1.0.4)
- mongoid (2.0.0.beta.18)
- activemodel (~> 3.0.0)
- bson (= 1.0.4)
- mongo (= 1.0.7)
+ json (1.4.6)
+ mongo (1.1.2)
+ bson (>= 1.1.1)
+ mongoid (2.0.0.beta.20)
+ activemodel (~> 3.0)
+ mongo (~> 1.1)
tzinfo (~> 0.3.22)
will_paginate (~> 3.0.pre)
- predicated (0.2.1)
+ predicated (0.2.2)
rake (0.8.7)
rcov (0.9.9)
- rspec (2.0.0.beta.22)
- rspec-core (= 2.0.0.beta.22)
- rspec-expectations (= 2.0.0.beta.22)
- rspec-mocks (= 2.0.0.beta.22)
- rspec-core (2.0.0.beta.22)
- rspec-expectations (2.0.0.beta.22)
- diff-lcs (>= 1.1.2)
- rspec-mocks (2.0.0.beta.22)
- rspec-core (= 2.0.0.beta.22)
- rspec-expectations (= 2.0.0.beta.22)
+ rspec (2.1.0)
+ rspec-core (~> 2.1.0)
+ rspec-expectations (~> 2.1.0)
+ rspec-mocks (~> 2.1.0)
+ rspec-core (2.1.0)
+ rspec-expectations (2.1.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.1.0)
ruby2ruby (1.2.5)
ruby_parser (~> 2.0)
sexp_processor (~> 3.0)
ruby_parser (2.0.5)
sexp_processor (~> 3.0)
sexp_processor (3.0.5)
+ sourcify (0.4.0)
+ ruby2ruby (>= 1.2.5)
+ sexp_processor (>= 3.0.5)
+ spruz (0.2.2)
tzinfo (0.3.23)
will_paginate (3.0.pre2)
- wrong (0.3.3)
- ParseTree (>= 3.0.5)
- diff (>= 0.3.6)
- diff-lcs (>= 1.1.2)
- predicated (>= 0.2.1)
- ruby2ruby (>= 1.2.4)
- ruby_parser (>= 2.0.4)
- yard (0.6.1)
+ wrong (0.4.5)
+ ParseTree (~> 3.0)
+ diff-lcs (~> 1.1.2)
+ file-tail (~> 1.0)
+ predicated (>= 0.2.2)
+ ruby2ruby (~> 1.2)
+ ruby_parser (~> 2.0.4)
+ sexp_processor (~> 3.0)
+ sourcify (>= 0.3.0)
+ yard (0.6.2)
PLATFORMS
ruby
@@ -69,6 +74,7 @@ DEPENDENCIES
bluecloth
bundler (~> 1.0.0)
jeweler (~> 1.5.0.pre3)
+ json
mongoid (~> 2.0.0.beta.18)
rcov
rspec (>= 2.0.0.beta.22)
View
@@ -15,6 +15,7 @@ Components
* CEB - "Command-Event Busing" for Command-Query Responsibility Separation
* OneTime - HMAC One Time Passwords
* PaperclipStringIO
+* Rack - Generic middleware for rack applications.
* Settings
Adler32
@@ -325,6 +326,22 @@ Example:
:content_type => 'application/pdf'))
end
+Rack
+====
+
+The Rack namespace provides simple generic utilities or middleware
+that may be helpful to applications.
+
+Visit the yardocs for examples and use cases.
+
+The Middleware:
+
+* Junkfood::Rack::ErrorHandler
+* Junkfood::Rack::TransientSession
+
+Requires:
+
+* json
Settings
========
View
@@ -29,6 +29,7 @@ Jeweler::Tasks.new do |gem|
gem.add_development_dependency "rcov", ">= 0"
gem.add_runtime_dependency 'activesupport'
+ gem.add_runtime_dependency 'json'
gem.add_runtime_dependency 'mongoid', '~> 2.0.0.beta.18'
gem.add_runtime_dependency 'wrong'
end
View
@@ -26,4 +26,5 @@ module Junkfood
require 'junkfood/ceb'
require 'junkfood/one_time'
require 'junkfood/paperclip_string_io'
+require 'junkfood/rack'
require 'junkfood/settings'
View
@@ -0,0 +1,26 @@
+# encoding: utf-8
+# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module Junkfood
+
+ ##
+ # The Junkfood::Rack module is the namespace for generic Rack
+ # utilities and middleware.
+ module Rack
+ end
+end
+
+require 'junkfood/rack/error'
+require 'junkfood/rack/sessions'
View
@@ -0,0 +1,92 @@
+# encoding: utf-8
+# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'json'
+
+module Junkfood
+ module Rack
+
+ ##
+ # Rack Middleware to catch exceptions, then transform the exceptions
+ # into serialized JSON objects to be returned to the caller.
+ #
+ # If this middleware catches an Exception, a rack request would be
+ # returned with an HTTP status code, a json mime-type in the headers,
+ # and JSON string as the response body.
+ #
+ # The HTTP status code will be 500 by default.
+ #
+ # The JSON object returned would have 'type', 'code' and 'message'
+ # fields.
+ # * 'type' will always be 'Error'.
+ # * 'code' will be the class name of the caught exception, or
+ # a customized value.
+ # * 'message' will be the message attribute of the caught exception, or
+ # a specifically set customized message.
+ #
+ # The developer can customize the HTTP status code, error code and
+ # error message via the constructor. Individually, or all at once.
+ #
+ # For example:
+ # error_map = {
+ # 'UnauthorizedException' => {
+ # 'status_code' => 401,
+ # 'code' => 'Unauthorized',
+ # 'message' => 'The request did not have the required authorization'
+ # }
+ # }
+ # use Junkfood::Rack::ErrorHandler, error_map
+ #
+ # When the 'UnauthorizedException' error is caught, the middleware
+ # will return a rack response with a 401 HTTP status code, a code with
+ # 'Unauthorized', and the given custom error message.
+ #
+ class ErrorHandler
+ ##
+ # The default HTTP status code for caught errors.
+ DEFAULT_STATUS_CODE = 500
+
+ ##
+ # @param app the rest of the rack stack
+ # @param mapping the mapping of Exceptions to Error codes and messages.
+ #
+ def initialize(app, mapping={})
+ @app = app
+ @mapping = mapping
+ end
+
+ ##
+ # @param env the rack environment.
+ # @return a received rack response, or a generated JSON error
+ # in the response body with a custom status code.
+ #
+ def call(env)
+ return @app.call(env)
+ rescue Exception => e
+ map = @mapping[e.class.name] || {}
+ error = {
+ 'type' => 'Error',
+ 'code' => map['code'] || e.class.name,
+ 'message' => map['message'] || e.message
+ }
+
+ return [
+ map['status_code'] ? map['status_code'].to_i : DEFAULT_STATUS_CODE,
+ { 'Content-Type' => 'application/json' },
+ [error.to_json]]
+ end
+ end
+ end
+end
@@ -0,0 +1,60 @@
+# encoding: utf-8
+# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module Junkfood
+ module Rack
+
+ ##
+ # Rack Middleware that sets the 'rack.session' environment variable
+ # with a new Hash object.
+ #
+ # This is specifically useful for when an application, or other middleware,
+ # assumes that a rack session is set yet the developer doesn't want
+ # to save an actual Cookie or Database session.
+ #
+ # For example, one may want to use API tokens for authentication
+ # in a rack application protected by `warden`. Each request requires
+ # the API token for each call and we don't want the authenticated user
+ # to be saved in a Cookie session.
+ #
+ # Simply:
+ #
+ # use Junkfood::Rack::TransientSession
+ # use Warden::Manager do |manager|
+ # manager.default_strategies :web_api_access_token
+ # manager.failure_app = Junkfood::WebApi::UnauthenticatedHandler.new
+ # end
+ # use Junkfood::WebApi::AccessTokenAuthentication
+ #
+ class TransientSession
+
+ ##
+ # @param app the rest of the Rack stack.
+ #
+ def initialize(app)
+ @app = app
+ end
+
+ ##
+ # @param env the Rack environment.
+ # @return a Rack response from the application.
+ #
+ def call(env)
+ env['rack.session'] = {}
+ @app.call(env)
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 3623e8d

Please sign in to comment.