batch_api2 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +30 -0
- data/changelog.md +74 -0
- data/lib/batch_api.rb +28 -0
- data/lib/batch_api/batch_error.rb +41 -0
- data/lib/batch_api/configuration.rb +36 -0
- data/lib/batch_api/error_wrapper.rb +44 -0
- data/lib/batch_api/internal_middleware.rb +87 -0
- data/lib/batch_api/internal_middleware/decode_json_body.rb +28 -0
- data/lib/batch_api/internal_middleware/response_filter.rb +27 -0
- data/lib/batch_api/operation.rb +2 -0
- data/lib/batch_api/operation/rack.rb +76 -0
- data/lib/batch_api/operation/rails.rb +42 -0
- data/lib/batch_api/processor.rb +113 -0
- data/lib/batch_api/processor/executor.rb +18 -0
- data/lib/batch_api/processor/sequential.rb +29 -0
- data/lib/batch_api/rack_middleware.rb +37 -0
- data/lib/batch_api/response.rb +38 -0
- data/lib/batch_api/utils.rb +17 -0
- data/lib/batch_api/version.rb +3 -0
- data/lib/tasks/batch_api_tasks.rake +4 -0
- data/readme.md +243 -0
- data/spec/dummy/Gemfile +1 -0
- data/spec/dummy/Gemfile.lock +8 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +15 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/javascripts/endpoints.js +2 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/assets/stylesheets/endpoints.css +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/endpoints_controller.rb +36 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/helpers/endpoints_helper.rb +2 -0
- data/spec/dummy/app/views/endpoints/get.html.erb +2 -0
- data/spec/dummy/app/views/endpoints/post.html.erb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +3 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +64 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/test/functional/endpoints_controller_test.rb +14 -0
- data/spec/dummy/test/unit/helpers/endpoints_helper_test.rb +4 -0
- data/spec/lib/batch_api_spec.rb +20 -0
- data/spec/lib/batch_error_spec.rb +23 -0
- data/spec/lib/configuration_spec.rb +30 -0
- data/spec/lib/error_wrapper_spec.rb +68 -0
- data/spec/lib/internal_middleware/decode_json_body_spec.rb +44 -0
- data/spec/lib/internal_middleware/response_filter_spec.rb +61 -0
- data/spec/lib/internal_middleware_spec.rb +93 -0
- data/spec/lib/operation/rack_spec.rb +246 -0
- data/spec/lib/operation/rails_spec.rb +100 -0
- data/spec/lib/processor/executor_spec.rb +22 -0
- data/spec/lib/processor/sequential_spec.rb +39 -0
- data/spec/lib/processor_spec.rb +136 -0
- data/spec/lib/rack_middleware_spec.rb +103 -0
- data/spec/lib/response_spec.rb +53 -0
- data/spec/rack-integration/rails_spec.rb +10 -0
- data/spec/rack-integration/shared_examples.rb +273 -0
- data/spec/rack-integration/sinatra_integration_spec.rb +19 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/sinatra_app.rb +54 -0
- data/spec/support/sinatra_xhr.rb +13 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5555904a32bd830b6afdb63a282135a2a33cd995
|
4
|
+
data.tar.gz: 81cd01446df080d2ce53b865eaa8e24ceedeff71
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ebdd0d77a56b9d44b1e3872d95fc017c72fbaa8ec3b4daaee9bfbcdb1a6ea78e88ed98c6989c7a5c57c9446ad6637c1386c8f8487e97e258bf6e37c295dae0ae
|
7
|
+
data.tar.gz: a74c28e3dc240b0c1ed2baa01fa274a86fef87100ca0793d5357135f09b5f6ef40345c9c5da9ba805a2ebe29a63328337a8656636eb8b0e48afc9478b5511a49
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'BatchApi'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
Bundler::GemHelper.install_tasks
|
24
|
+
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
RSpec::Core::RakeTask.new do |t|
|
27
|
+
t.rspec_opts = ["--color", '--format doc', '--order rand']
|
28
|
+
end
|
29
|
+
|
30
|
+
task :default => :spec
|
data/changelog.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
v0.3.0
|
2
|
+
|
3
|
+
New features:
|
4
|
+
|
5
|
+
* guard against nil REQUEST_URI (thanks, pbendersky and dlackty!)
|
6
|
+
* don't parse empty bodies as JSON (thanks, trungpham and dlackty!)
|
7
|
+
|
8
|
+
Testing improvements:
|
9
|
+
|
10
|
+
* modernize test infrastructure and test against newer Ruby versions (thanks, dwaller, pmq20 and dlackty!)
|
11
|
+
* update test Rails app version to 4.2
|
12
|
+
* modernize RSpec syntax using transpec
|
13
|
+
|
14
|
+
|
15
|
+
v0.2.2
|
16
|
+
* Update documentation to remove old options and add installation section
|
17
|
+
* Update gems
|
18
|
+
|
19
|
+
v0.2.1
|
20
|
+
* Default the method to GET if no method is specified
|
21
|
+
|
22
|
+
v0.2.0
|
23
|
+
* Refactor app to use internal middlewares for handling operations
|
24
|
+
* Refactor JSON decoding to a middleware
|
25
|
+
* Remove timestamp option
|
26
|
+
|
27
|
+
v0.1.4
|
28
|
+
* Refactor errors into ErrorWrapper/BatchError
|
29
|
+
* Allow specification of custom status codes raised for errors
|
30
|
+
|
31
|
+
v0.1.3
|
32
|
+
* Refactor config to use a struct
|
33
|
+
* Update readme to cover HTTP pipelining
|
34
|
+
|
35
|
+
v0.1.2
|
36
|
+
* Rewrite the readme
|
37
|
+
* Add travis icon
|
38
|
+
|
39
|
+
v0.1.1
|
40
|
+
* Fix dumb error
|
41
|
+
|
42
|
+
v0.1.0
|
43
|
+
* Add direct support for Rails for path params
|
44
|
+
* Fix spec bugs
|
45
|
+
|
46
|
+
v0.0.8
|
47
|
+
* Return the results wrapped in a hash, rather than a raw array
|
48
|
+
* Add process_start timestamp option
|
49
|
+
|
50
|
+
v0.0.7
|
51
|
+
* Return more specific error codes to alert clients to param errors
|
52
|
+
|
53
|
+
v0.0.6
|
54
|
+
* Refactor Rack middleware to be Sinatra-compatible
|
55
|
+
|
56
|
+
v0.0.5
|
57
|
+
* Add setting to decode JSON responses before sending batch results
|
58
|
+
|
59
|
+
v0.0.4
|
60
|
+
* Switch from Rails-based process to a Rack middleware
|
61
|
+
* Improve tests
|
62
|
+
|
63
|
+
v0.0.3
|
64
|
+
* Encapsulate processing into a Processor module
|
65
|
+
* Prepare for parallel processing in the future
|
66
|
+
* Add specific errors
|
67
|
+
* Allow controlling the routing target
|
68
|
+
|
69
|
+
v0.0.2
|
70
|
+
* Add config module
|
71
|
+
* Add options for operation limit, endpoint, and verb
|
72
|
+
|
73
|
+
v0.0.1
|
74
|
+
* Initial build
|
data/lib/batch_api.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'batch_api/configuration'
|
2
|
+
require 'batch_api/version'
|
3
|
+
require 'batch_api/utils'
|
4
|
+
require 'batch_api/processor'
|
5
|
+
|
6
|
+
require 'batch_api/internal_middleware'
|
7
|
+
require 'batch_api/rack_middleware'
|
8
|
+
|
9
|
+
require 'batch_api/error_wrapper'
|
10
|
+
require 'batch_api/batch_error'
|
11
|
+
|
12
|
+
module BatchApi
|
13
|
+
|
14
|
+
# Public: access the main Batch API configuration object.
|
15
|
+
#
|
16
|
+
# Returns a BatchApi::Configuration instance
|
17
|
+
def self.config
|
18
|
+
@config ||= Configuration.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: are we in Rails? This partly exists just so that you
|
22
|
+
# can stub it in the tests.
|
23
|
+
#
|
24
|
+
# Returns true if Rails is a defined constant, false otherwise.
|
25
|
+
def self.rails?
|
26
|
+
defined?(Rails)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module BatchApi
|
2
|
+
module Errors
|
3
|
+
# Public: a module that tags Batch API errors and provides a default
|
4
|
+
# status.
|
5
|
+
module BatchError
|
6
|
+
# Public: the status code for this type of error.
|
7
|
+
# Subclasses can change this as desired.
|
8
|
+
def status_code; 500; end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Public: an error thrown if an invalid option is
|
12
|
+
# specificed.
|
13
|
+
class BadOptionError < ArgumentError
|
14
|
+
include BatchError
|
15
|
+
# Public: the status code for this error.
|
16
|
+
def status_code; 422; end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: an error thrown if too many operations are provided.
|
20
|
+
class OperationLimitExceeded < ArgumentError
|
21
|
+
include BatchError
|
22
|
+
# Public: the status code for this error.
|
23
|
+
def status_code; 422; end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: an error thrown if no operations are provided.
|
27
|
+
class NoOperationsError < ArgumentError
|
28
|
+
include BatchError
|
29
|
+
# Public: the status code for this error.
|
30
|
+
def status_code; 422; end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: an error thrown if one of the batch operations
|
34
|
+
# is somehow invalid (missing key parameters, etc.).
|
35
|
+
class MalformedOperationError < ArgumentError
|
36
|
+
include BatchError
|
37
|
+
# Public: the status code for this error.
|
38
|
+
def status_code; 422; end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'batch_api/internal_middleware'
|
2
|
+
|
3
|
+
module BatchApi
|
4
|
+
# Public: configuration options.
|
5
|
+
# Currently, you can set:
|
6
|
+
# - endpoint: (URL) through which the Batch API will be exposed (default
|
7
|
+
# "/batch)
|
8
|
+
# - verb: through which it's accessed (default "POST")
|
9
|
+
# - limit: how many requests can be processed in a single request
|
10
|
+
# (default 50)
|
11
|
+
#
|
12
|
+
# There are also two middleware-related options -- check out middleware.rb
|
13
|
+
# for more information.
|
14
|
+
# - global_middleware: any middlewares to use round the entire batch request
|
15
|
+
# (such as authentication, etc.)
|
16
|
+
# - per_op_middleware: any middlewares to run around each individual request
|
17
|
+
# (adding headers, decoding JSON, etc.)
|
18
|
+
CONFIGURATION_OPTIONS = {
|
19
|
+
verb: :post,
|
20
|
+
endpoint: "/batch",
|
21
|
+
limit: 50,
|
22
|
+
batch_middleware: InternalMiddleware::DEFAULT_BATCH_MIDDLEWARE,
|
23
|
+
operation_middleware: InternalMiddleware::DEFAULT_OPERATION_MIDDLEWARE
|
24
|
+
}
|
25
|
+
|
26
|
+
# Batch API Configuration
|
27
|
+
class Configuration < Struct.new(*CONFIGURATION_OPTIONS.keys)
|
28
|
+
# Public: initialize a new configuration option and apply the defaults.
|
29
|
+
def initialize
|
30
|
+
super
|
31
|
+
CONFIGURATION_OPTIONS.each {|k, v| self[k] = v}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module BatchApi
|
2
|
+
# Public: wrap an error thrown during a batch operation.
|
3
|
+
# This has a body class and a cookies accessor and can
|
4
|
+
# function in place of a regular BatchResponse object.
|
5
|
+
class ErrorWrapper
|
6
|
+
# Public: create a new ErrorWrapper from an error object.
|
7
|
+
def initialize(error)
|
8
|
+
@error = error
|
9
|
+
@status_code = error.status_code if error.respond_to?(:status_code)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: the error details as a hash, which can be returned
|
13
|
+
# to clients as JSON.
|
14
|
+
def body
|
15
|
+
message = if self.class.expose_backtrace?
|
16
|
+
{
|
17
|
+
message: @error.message,
|
18
|
+
backtrace: @error.backtrace
|
19
|
+
}
|
20
|
+
else
|
21
|
+
{ message: @error.message }
|
22
|
+
end
|
23
|
+
{ error: message }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: turn the error body into a Rack-compatible body component.
|
27
|
+
#
|
28
|
+
# Returns: an Array with the error body represented as JSON.
|
29
|
+
def render
|
30
|
+
[status_code, RackMiddleware.content_type, [body.to_json]]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: the status code to return for the given error.
|
34
|
+
def status_code
|
35
|
+
@status_code || 500
|
36
|
+
end
|
37
|
+
|
38
|
+
# Internal: whether the backtrace should be exposed in the response.
|
39
|
+
# Currently Rails-specific, needs to be generalized (to ENV["RACK_ENV"])?
|
40
|
+
def self.expose_backtrace?
|
41
|
+
!Rails.env.production?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'middleware'
|
2
|
+
require 'batch_api/processor/sequential'
|
3
|
+
require 'batch_api/processor/executor'
|
4
|
+
require 'batch_api/internal_middleware/decode_json_body'
|
5
|
+
require 'batch_api/internal_middleware/response_filter'
|
6
|
+
|
7
|
+
module BatchApi
|
8
|
+
# Public: the internal middleware system used to process batch requests.
|
9
|
+
# (Not to be confused with RackMiddleware, which handles incoming Rack
|
10
|
+
# request.) Based on Mitchel Hashimoto's middleware gem.
|
11
|
+
#
|
12
|
+
# There are actually two internal middleware stacks, one run around the
|
13
|
+
# entire request and the other run around each individual operation.
|
14
|
+
#
|
15
|
+
# The batch_middleware stack consists of two parts:
|
16
|
+
# 1) Custom middlewares - these middlewares will be run around the entire
|
17
|
+
# sequence of batch requests. Useful for global processing on a batch
|
18
|
+
# request; for instance, the timestamp middleware adds a timestamp based on
|
19
|
+
# when the original request was started. This should return an array of
|
20
|
+
# BatchApi::Response objects.
|
21
|
+
#
|
22
|
+
# 2) Processor - this automatically-provided middleware will execute all
|
23
|
+
# batch requests either sequentially (currently the only option) or in
|
24
|
+
# parallel (in the future). This will return an array of
|
25
|
+
# BatchApi::Response objects.
|
26
|
+
#
|
27
|
+
# The operation_middleware stack also consists of two parts:
|
28
|
+
# 1) Operation - these middlewares will run once per each operation, giving
|
29
|
+
# you a chance to alter the results or details of an op -- for instance,
|
30
|
+
# decoding the body if it's JSON. This should return an individual
|
31
|
+
# BatchApi::Response object.
|
32
|
+
#
|
33
|
+
# 2) Executor - this automatically-provided middleware actually executes
|
34
|
+
# the individual Rack request, and returns a BatchApi::Response object.
|
35
|
+
#
|
36
|
+
# Symetry, right?
|
37
|
+
#
|
38
|
+
# All middlewares#call will receive the following as an env hash:
|
39
|
+
# {
|
40
|
+
# # for batch middlewares
|
41
|
+
# ops: [], # the total set of operations
|
42
|
+
# # for operation middlewares
|
43
|
+
# op: obj, # the specific operation being executed
|
44
|
+
# # all middlewares get the following:
|
45
|
+
# rack_env: {}, # the Rack environment
|
46
|
+
# rack_app: app # the Rack application
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
# All middlewares should return the result of their individual operation or
|
50
|
+
# the array of operation results, depending on where they are in the chain.
|
51
|
+
# (See above.)
|
52
|
+
module InternalMiddleware
|
53
|
+
# Public: the default internal middlewares to be run around the entire
|
54
|
+
# operation.
|
55
|
+
DEFAULT_BATCH_MIDDLEWARE = Proc.new {}
|
56
|
+
|
57
|
+
# Public: the default internal middlewares to be run around each batch
|
58
|
+
# operation.
|
59
|
+
DEFAULT_OPERATION_MIDDLEWARE = Proc.new do
|
60
|
+
# Decode JSON response bodies, so they're not double-encoded.
|
61
|
+
use InternalMiddleware::ResponseFilter
|
62
|
+
use InternalMiddleware::DecodeJsonBody
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: the middleware stack to use for processing the batch request as a
|
66
|
+
# whole..
|
67
|
+
def self.batch_stack(processor)
|
68
|
+
Middleware::Builder.new do
|
69
|
+
# evaluate these in the context of the middleware stack
|
70
|
+
self.instance_eval &BatchApi.config.batch_middleware
|
71
|
+
# for now, everything's sequential, but that will change
|
72
|
+
use processor.strategy
|
73
|
+
end
|
74
|
+
end
|
75
|
+
#
|
76
|
+
# Public: the middleware stack to use for processing each batch operation.
|
77
|
+
def self.operation_stack
|
78
|
+
Middleware::Builder.new do
|
79
|
+
# evaluate the operation middleware in the context of the middleware
|
80
|
+
# stack
|
81
|
+
self.instance_eval &BatchApi.config.operation_middleware
|
82
|
+
# and end with actually executing the batch request
|
83
|
+
use Processor::Executor
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BatchApi
|
2
|
+
module InternalMiddleware
|
3
|
+
# Public: a middleware that decodes the body of any individual batch
|
4
|
+
# operation if it's JSON.
|
5
|
+
class DecodeJsonBody
|
6
|
+
# Public: initialize the middleware.
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
@app.call(env).tap do |result|
|
13
|
+
if should_decode?(result)
|
14
|
+
result.body = MultiJson.load(result.body)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def should_decode?(result)
|
22
|
+
result.headers["Content-Type"] =~ /^application\/json/ &&
|
23
|
+
# don't try to decode an empty response
|
24
|
+
result.body.present?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BatchApi
|
2
|
+
module InternalMiddleware
|
3
|
+
# Public: a batch middleware which surpresses the response from a call. If you
|
4
|
+
# know you don't need a response (for instance, for a POST or PUT), you can
|
5
|
+
# add silent: true (or any truthy value, like 1) to your operation to
|
6
|
+
# surpress all output for successful requests. Failed requests (status !=
|
7
|
+
# 2xx) will still return information.
|
8
|
+
class ResponseFilter
|
9
|
+
# Public: init the middleware.
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: execute the call. If env[:op].options[:silent] is true, it'll
|
15
|
+
# remove any output for a successful response.
|
16
|
+
def call(env)
|
17
|
+
@app.call(env).tap do |result|
|
18
|
+
if env[:op].options["silent"] && (200...299).include?(result.status)
|
19
|
+
# we have success and a request for silence
|
20
|
+
# so remove all the content before proceeding
|
21
|
+
result.status = result.body = result.headers = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'batch_api/response'
|
2
|
+
|
3
|
+
module BatchApi
|
4
|
+
# Public: an individual batch operation.
|
5
|
+
module Operation
|
6
|
+
class Rack
|
7
|
+
attr_accessor :method, :url, :params, :headers
|
8
|
+
attr_accessor :env, :app, :result, :options
|
9
|
+
|
10
|
+
# Public: create a new Batch Operation given the specifications for a batch
|
11
|
+
# operation (as defined above) and the request environment for the main
|
12
|
+
# batch request.
|
13
|
+
def initialize(op, base_env, app)
|
14
|
+
@op = op
|
15
|
+
|
16
|
+
@method = op["method"] || "get"
|
17
|
+
@url = op["url"]
|
18
|
+
@params = op["params"] || {}
|
19
|
+
@headers = op["headers"] || {}
|
20
|
+
@options = op
|
21
|
+
|
22
|
+
raise Errors::MalformedOperationError,
|
23
|
+
"BatchAPI operation must include method (received #{@method.inspect}) " +
|
24
|
+
"and url (received #{@url.inspect})" unless @method && @url
|
25
|
+
|
26
|
+
@app = app
|
27
|
+
# deep_dup to avoid unwanted changes across requests
|
28
|
+
@env = BatchApi::Utils.deep_dup(base_env)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Execute a batch request, returning a BatchResponse object. If an error
|
32
|
+
# occurs, it returns the same results as Rails would.
|
33
|
+
def execute
|
34
|
+
process_env
|
35
|
+
begin
|
36
|
+
response = @app.call(@env)
|
37
|
+
rescue => err
|
38
|
+
response = BatchApi::ErrorWrapper.new(err).render
|
39
|
+
end
|
40
|
+
BatchApi::Response.new(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Internal: customize the request environment. This is currently done
|
44
|
+
# manually and feels clunky and brittle, but is mostly likely fine, though
|
45
|
+
# there are one or two environment parameters not yet adjusted.
|
46
|
+
def process_env
|
47
|
+
path, qs = @url.split("?")
|
48
|
+
|
49
|
+
# Headers
|
50
|
+
headrs = (@headers || {}).inject({}) do |heads, (k, v)|
|
51
|
+
heads.tap {|h| h["HTTP_" + k.gsub(/\-/, "_").upcase] = v}
|
52
|
+
end
|
53
|
+
# preserve original headers unless explicitly overridden
|
54
|
+
@env.merge!(headrs)
|
55
|
+
|
56
|
+
# method
|
57
|
+
@env["REQUEST_METHOD"] = @method.upcase
|
58
|
+
|
59
|
+
# path and query string
|
60
|
+
if @env["REQUEST_URI"]
|
61
|
+
# not all servers provide REQUEST_URI -- Pow, for instance, doesn't
|
62
|
+
@env["REQUEST_URI"] = @env["REQUEST_URI"].gsub(/#{BatchApi.config.endpoint}.*/, @url)
|
63
|
+
end
|
64
|
+
@env["REQUEST_PATH"] = path
|
65
|
+
@env["ORIGINAL_FULLPATH"] = @env["PATH_INFO"] = @url
|
66
|
+
|
67
|
+
@env["rack.request.query_string"] = qs
|
68
|
+
@env["QUERY_STRING"] = qs
|
69
|
+
|
70
|
+
# parameters
|
71
|
+
@env["rack.request.form_hash"] = @params
|
72
|
+
@env["rack.request.query_hash"] = @method == "get" ? @params : nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|