Permalink
Browse files

Merge pull request #12 from arsduo/middleware

Create InternalMiddleware system
  • Loading branch information...
arsduo committed Oct 11, 2012
2 parents a1319f1 + 914ca74 commit cdacb18d4e2e80baea56a297ff9f3facfc336097
View
@@ -5,8 +5,6 @@ source "http://rubygems.org"
# development dependencies will be added by default to the :development group.
gemspec
-# jquery-rails is used by the dummy application
-gem "jquery-rails"
group :development, :test do
# Testing infrastructure
View
@@ -1,7 +1,8 @@
PATH
remote: .
specs:
- batch_api (0.1.1)
+ batch_api (0.2.0)
+ middleware
GEM
remote: http://rubygems.org/
@@ -48,9 +49,6 @@ GEM
hike (1.2.1)
i18n (0.6.0)
journey (1.0.4)
- jquery-rails (2.1.0)
- railties (>= 3.1.0, < 5.0)
- thor (~> 0.14)
json (1.7.4)
listen (0.4.7)
rb-fchange (~> 0.0.5)
@@ -60,6 +58,7 @@ GEM
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
+ middleware (0.1.0)
mime-types (1.19)
multi_json (1.3.6)
polyglot (0.3.3)
@@ -136,7 +135,6 @@ DEPENDENCIES
faker
guard
guard-rspec
- jquery-rails
rack-contrib
rails (~> 3.2)
rb-fsevent
View
@@ -17,10 +17,12 @@ Gem::Specification.new do |s|
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "changelog.md", "readme.md"]
s.test_files = Dir["spec/**/*"]
- s.add_development_dependency "rails", "~> 3.2"
- s.add_development_dependency "sinatra"
- s.add_development_dependency "rspec"
- s.add_development_dependency "rspec-rails"
- s.add_development_dependency "sqlite3"
- s.add_development_dependency "rack-contrib"
+ s.add_runtime_dependency("middleware")
+
+ s.add_development_dependency("rails", "~> 3.2")
+ s.add_development_dependency("sinatra")
+ s.add_development_dependency("rspec")
+ s.add_development_dependency("rspec-rails")
+ s.add_development_dependency("sqlite3")
+ s.add_development_dependency("rack-contrib")
end
View
@@ -1,3 +1,8 @@
+v0.2.0
+* Refactor app to use internal middlewares for handling operations
+* Refactor JSON decoding to a middleware
+* Remove timestamp option
+
v0.1.3
* Refactor config to use a struct
* Update readme to cover HTTP pipelining
View
@@ -2,7 +2,8 @@
require 'batch_api/version'
require 'batch_api/utils'
require 'batch_api/processor'
-require 'batch_api/middleware'
+require 'batch_api/internal_middleware'
+require 'batch_api/rack_middleware'
module BatchApi
@@ -1,3 +1,5 @@
+require 'batch_api/internal_middleware'
+
module BatchApi
# Public: configuration options.
# Currently, you can set:
@@ -9,16 +11,24 @@ module BatchApi
# - decode_json_responses: automatically decode JSON response bodies,
# so they don't get double-decoded (e.g. when you decode the batch
# response, the bodies are already objects).
+ #
+ # There are also two middleware-related options -- check out middleware.rb
+ # for more information.
+ # - global_middleware: any middlewares to use round the entire batch request
+ # (such as authentication, etc.)
+ # - per_op_middleware: any middlewares to run around each individual request
+ # (adding headers, decoding JSON, etc.)
CONFIGURATION_OPTIONS = {
verb: :post,
endpoint: "/batch",
limit: 50,
- decode_json_responses: true
+ batch_middleware: InternalMiddleware::DEFAULT_BATCH_MIDDLEWARE,
+ operation_middleware: InternalMiddleware::DEFAULT_OPERATION_MIDDLEWARE
}
# Batch API Configuration
class Configuration < Struct.new(*CONFIGURATION_OPTIONS.keys)
- # Public; initialize a new configuration option and apply the defaults.
+ # Public: initialize a new configuration option and apply the defaults.
def initialize
super
CONFIGURATION_OPTIONS.each {|k, v| self[k] = v}
@@ -27,7 +27,7 @@ def body
#
# Returns: an Array with the error body represented as JSON.
def render
- [status_code, Middleware.content_type, [MultiJson.dump(body)]]
+ [status_code, RackMiddleware.content_type, [MultiJson.dump(body)]]
end
# Public: the status code to return for the given error.
@@ -0,0 +1,67 @@
+require 'middleware'
+require 'batch_api/processor/sequential'
+require 'batch_api/processor/executor'
+require 'batch_api/internal_middleware/decode_json_body'
+
+module BatchApi
+ # Public: the internal middleware system used to process batch requests.
+ # (Not to be confused with RackMiddleware, which handles incoming Rack
+ # request.) Based on Mitchel Hashimoto's middleware gem.
+ #
+ # The middleware stack is defined in a block, and has four phases:
+ # 1) Batch - these middlewares will be run around the entire sequence of
+ # batch requests. Useful for global processing on a batch request; for
+ # instance, the timestamp middleware adds a timestamp based on when the
+ # original request was started. This should return an array of
+ # BatchApi::Response objects.
+ #
+ # 2) Processor - this automatically-provided middleware will execute all
+ # batch requests either sequentially (currently the only option) or in
+ # parallel (in the future). This will return an array of
+ # BatchApi::Response objects.
+ #
+ # 3) Operation - these middlewares will run once per each operation, giving
+ # you a chance to alter the results or details of an op -- for instance,
+ # decoding the body if it's JSON. This should return an individual
+ # BatchApi::Response object.
+ #
+ # 4) Executor - this automatically-provided middleware actually executes
+ # the individual Rack request, and returns a BatchApi::Response object.
+ #
+ # All middlewares#call will receive the following as an env hash:
+ # {
+ # ops: [], # the total set of operations
+ # op: obj, # the specific operation being executed, if appropriate
+ # rack_env: {}, # the Rack environment
+ # rack_app: app # the Rack application
+ # }
+ #
+ # All middlewares should return the result of their individual operation or
+ # the array of operation results, depending on where they are in the chain.
+ # (See above.)
+ module InternalMiddleware
+ # Public: the default internal middlewares to be run around the entire
+ # operation.
+ DEFAULT_BATCH_MIDDLEWARE = Proc.new {}
+
+ # Public: the default internal middlewares to be run around each batch
+ # operation.
+ DEFAULT_OPERATION_MIDDLEWARE = Proc.new do
+ # Decode JSON response bodies, so they're not double-encoded.
+ use InternalMiddleware::DecodeJsonBody
+ end
+
+ # Public: the middleware stack to use for requests.
+ def self.stack(processor)
+ Middleware::Builder.new do
+ # evaluate these in the context of the middleware object
+ self.instance_eval &BatchApi.config.batch_middleware
+ # for now, everything's sequential, but that will change
+ use processor.strategy
+ self.instance_eval &BatchApi.config.operation_middleware
+ # and end with actually executing the batch request
+ use Processor::Executor
+ end
+ end
+ end
+end
@@ -0,0 +1,24 @@
+module BatchApi
+ module InternalMiddleware
+ # Public: a middleware that decodes the body of any individual batch
+ # operation if the it's JSON.
+ class DecodeJsonBody
+ # Public: initialize the middleware.
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env).tap do |result|
+ result.body = MultiJson.load(result.body) if should_decode?(result)
+ end
+ end
+
+ private
+
+ def should_decode?(result)
+ result.headers["Content-Type"] =~ /^application\/json/
+ end
+ end
+ end
+end
View
@@ -1,4 +1,4 @@
-require 'batch_api/processor/strategies/sequential'
+require 'batch_api/processor/sequential'
require 'batch_api/operation'
module BatchApi
@@ -30,26 +30,34 @@ def initialize(request, app)
@env = request.env
@ops = self.process_ops
@options = self.process_options
-
- @start_time = Time.now.to_i
end
# Public: the processing strategy to use, based on the options
# provided in BatchApi setup and the request.
# Currently only Sequential is supported.
def strategy
- BatchApi::Processor::Strategies::Sequential
+ BatchApi::Processor::Sequential
end
# Public: run the batch operations according to the appropriate strategy.
#
# Returns a set of BatchResponses
def execute!
- format_response(strategy.execute!(@ops, @options))
+ stack = InternalMiddleware.stack(self)
+ format_response(stack.call(middleware_env))
end
protected
+ def middleware_env
+ {
+ ops: @ops,
+ rack_env: @env,
+ rack_app: @app,
+ options: @options
+ }
+ end
+
# Internal: format the result of the operations, and include
# any other appropriate information (such as timestamp).
#
@@ -0,0 +1,18 @@
+module BatchApi
+ class Processor
+ # Public: a simple middleware that lives at the end of the internal chain
+ # and simply executes each batch operation.
+ class Executor
+
+ # Public: initialize the middleware.
+ def initialize(app)
+ @app = app
+ end
+
+ # Public: execute the batch operation.
+ def call(env)
+ env[:op].execute
+ end
+ end
+ end
+end
@@ -0,0 +1,28 @@
+module BatchApi
+ class Processor
+ class Sequential
+
+ # Public: initialize with the app.
+ def initialize(app)
+ @app = app
+ end
+
+ # Public: execute all operations sequentially.
+ #
+ # ops - a set of BatchApi::Operations
+ # options - a set of options
+ #
+ # Returns an array of BatchApi::Response objects.
+ def call(env)
+ env[:ops].collect do |op|
+ # set the current op
+ env[:op] = op
+ # execute the individual request
+ # then clear out the current op afterward
+ @app.call(env).tap {|r| env.delete(:op) }
+ end
+ end
+ end
+ end
+end
+
@@ -1,18 +0,0 @@
-module BatchApi
- class Processor
- module Strategies
- module Sequential
- # Public: execute all operations sequentially.
- #
- # ops - a set of BatchApi::Operations
- # options - a set of options
- #
- # Returns an array of BatchApi::Response objects.
- def self.execute!(ops, options = {})
- ops.map(&:execute)
- end
- end
- end
- end
-end
-
@@ -1,5 +1,5 @@
module BatchApi
- class Middleware
+ class RackMiddleware
def initialize(app, &block)
@app = app
yield BatchApi.config if block
@@ -24,12 +24,7 @@ def process_body(body_pieces)
# so turn it into a string
base_body = ""
body_pieces.each {|str| base_body << str}
- should_decode? ? MultiJson.load(base_body) : base_body
- end
-
- def should_decode?
- @headers["Content-Type"] =~ /^application\/json/ &&
- BatchApi.config.decode_json_responses
+ base_body
end
end
end
View
@@ -1,3 +1,3 @@
module BatchApi
- VERSION = "0.1.3"
+ VERSION = "0.2.0"
end
@@ -55,7 +55,7 @@ class Application < Rails::Application
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
- config.middleware.use BatchApi::Middleware do |batch|
+ config.middleware.use BatchApi::RackMiddleware do |batch|
batch.limit = 25
end
end
Oops, something went wrong.

0 comments on commit cdacb18

Please sign in to comment.