middleware-xt 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 33a68628d196f6b7324a85bbe8dab085d542a902
4
+ data.tar.gz: 4710ff4e7313302648c3f9f30d9b1ba3cf8c69d8
5
+ SHA512:
6
+ metadata.gz: 6dc6206bdfccb179da54804e6f1a0ffbf34a89fe77f1cc5082e68965f7b59e80ef77bfaf0b34f7f30a9705203e4562e3345b51df3ef2cd02b6ec177d370c41b3
7
+ data.tar.gz: 8a2c2ec1e358820fcc7cebb34f3266ec93f6da4ea6d348c8b8bf45128ce1ea6fcff7e121ea3aeae086bb1ba2e13b7763642d2663e14c866be235501dd87665fe
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - rbx-18mode
8
+ - rbx-19mode
9
+ - ruby-head
10
+ - jruby-head
11
+ - ree
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ -m markdown
2
+ --files
3
+ user_guide.md
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## 0.2.0 (unreleased)
2
+
3
+
4
+
5
+ ## 0.1.0 (March 16, 2012)
6
+
7
+ - Initial release, almost directly extracted from [Vagrant](http://vagrantup.com)
8
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in middleware.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mitchell Hashimoto
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,2 @@
1
+ benchmark:
2
+ ruby benchmarks/reusable_runner_instance.rb
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Middleware
2
+
3
+ [![Build Status](https://secure.travis-ci.org/mitchellh/middleware.png?branch=master)](http://travis-ci.org/mitchellh/middleware)
4
+
5
+
6
+ -- currently a fork to play with some non-generalizable ideas ---
7
+
8
+
9
+ This is a generalized library for using middleware patterns within
10
+ your Ruby projects.
11
+
12
+ To get started, the best place to look is [the user guide](https://github.com/mitchellh/middleware/blob/master/user_guide.md).
13
+
14
+ ## Installation
15
+
16
+ This project is distributed as a RubyGem:
17
+
18
+ ```console
19
+ $ gem install middleware
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ Once you create a basic middleware, you can use the builder to
25
+ have a nice DSL to build middleware stacks. Calling the middleware
26
+ is simple, as well.
27
+
28
+ ```ruby
29
+ # Basic middleware that just prints the inbound and
30
+ # outbound steps.
31
+ class Trace
32
+ def initialize(app, value)
33
+ @app = app
34
+ @value = value
35
+ end
36
+
37
+ def call(env)
38
+ puts "--> #{@value}"
39
+ @app.call(env)
40
+ puts "<-- #{@value}"
41
+ end
42
+ end
43
+
44
+ # Build the actual middleware stack which runs a sequence
45
+ # of slightly different versions of our middleware.
46
+ stack = Middleware::Builder.new do
47
+ use Trace, "A"
48
+ use Trace, "B"
49
+ use Trace, "C"
50
+ end
51
+
52
+ # Run it!
53
+ stack.call(nil)
54
+ ```
55
+
56
+ And the output:
57
+
58
+ ```
59
+ --> A
60
+ --> B
61
+ --> C
62
+ <-- C
63
+ <-- B
64
+ <-- A
65
+ ```
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create new Pull Request
74
+
75
+
76
+ ## Benchmarks
77
+
78
+ ```
79
+ $ make benchmark
80
+ ```
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ # RSpec test task
6
+ RSpec::Core::RakeTask.new
7
+
8
+ # Make sure the default is to run RSpec
9
+ task :default => "spec"
@@ -0,0 +1,189 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+ require 'middleware'
4
+ require 'benchmark/ips'
5
+
6
+
7
+ class FrontParamsValidator
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ if errors = validate_params(env[:front_params])
14
+ env[:response][:errors] = errors
15
+ else
16
+ @app.call(env)
17
+ end
18
+ end
19
+
20
+ def validate_params(params)
21
+ if false
22
+ return [{msg: 'Bad format for param xyz!'}]
23
+ end
24
+ end
25
+ end
26
+
27
+ class ApiParamsGenerator
28
+ def initialize(app)
29
+ @app = app
30
+ end
31
+
32
+ def call(env)
33
+ (env[:api_params]||={})[:modified] = true
34
+ res = @app.call(env)
35
+ env[:api_params][:after] = true
36
+ env
37
+ end
38
+ end
39
+
40
+
41
+ class ResponseModifier
42
+ def initialize(app)
43
+ @app = app
44
+ end
45
+
46
+ def call(env)
47
+ @app.call(env)
48
+ env[:response][:works] = true
49
+ env
50
+ end
51
+ end
52
+
53
+ class ResponseMultiModifier
54
+ def initialize(app, value)
55
+ @app = app
56
+ @value = value
57
+ end
58
+
59
+ def call(env)
60
+ @app.call(env)
61
+ (env[:response][:multi_modifier]||=[]) << 'called with ' + @value
62
+ env
63
+ end
64
+ end
65
+
66
+ class ExceptionHandling
67
+ def initialize(app)
68
+ @app = app
69
+ end
70
+
71
+ def call(env)
72
+ begin
73
+ @app.call env
74
+ rescue => ex
75
+ puts ex
76
+ puts ex.backtrace
77
+ hash = { :message => ex.to_s }
78
+ hash[:backtrace] = ex.backtrace
79
+ env['api.errors'] = MultiJson.dump(hash)
80
+ env
81
+ end
82
+ end
83
+ end
84
+
85
+
86
+ class PrepareEnvironment
87
+ def initialize(app)
88
+ @app = app
89
+ end
90
+
91
+ def call(env)
92
+ env[:response]||= {}
93
+ env[:api_params]||= {}
94
+ @app.call(env)
95
+ end
96
+ end
97
+
98
+ class ApiDirect
99
+ def self.call(env)
100
+ begin
101
+ env[:response]||= {}
102
+ env[:api_params]||= {}
103
+ if errors = validate_params(env[:front_params])
104
+ env[:response][:errors] = errors
105
+ else
106
+ (env[:api_params]||={})[:modified] = true
107
+ env[:response][:works] = true
108
+
109
+ (env[:response][:multi_modifier]||=[]) << 'called with ' + 'first'
110
+ (env[:response][:multi_modifier]||=[]) << 'called with ' + 'second'
111
+ env[:api_params][:after] = true
112
+ end
113
+
114
+ rescue => ex
115
+ puts ex
116
+ puts ex.backtrace
117
+ hash = { :message => ex.to_s }
118
+ hash[:backtrace] = ex.backtrace
119
+ env['api.errors'] = MultiJson.dump(hash)
120
+ env
121
+ end
122
+ end
123
+
124
+ def self.validate_params(params)
125
+ if false
126
+ return [{msg: 'Bad format for param xyz!'}]
127
+ end
128
+ end
129
+ end
130
+
131
+
132
+ ApiStack = Middleware::Builder.new do
133
+ use ExceptionHandling
134
+ use PrepareEnvironment
135
+ use FrontParamsValidator
136
+ use ApiParamsGenerator
137
+ use ResponseModifier
138
+ use ResponseMultiModifier, 'first'
139
+ use ResponseMultiModifier, 'second'
140
+ end
141
+
142
+
143
+ ApiStackReuse = Middleware::Builder.new(reuse_instance: true) do
144
+ use ExceptionHandling
145
+ use PrepareEnvironment
146
+ use FrontParamsValidator
147
+ use ApiParamsGenerator
148
+ use ResponseModifier
149
+ use ResponseMultiModifier, 'first'
150
+ use ResponseMultiModifier, 'second'
151
+ end
152
+
153
+ Benchmark.ips do |x|
154
+ x.time = 5
155
+ x.warmup = 2
156
+
157
+ x.report("normal stack") {
158
+ env = {}
159
+ ApiStack.call(env)
160
+ }
161
+
162
+ x.report("reuse stack") {
163
+ env = {}
164
+ ApiStackReuse.call(env)
165
+ }
166
+
167
+ x.report("direct stack") {
168
+ env = {}
169
+ ApiDirect.call(env)
170
+ }
171
+
172
+ x.compare!
173
+ end
174
+
175
+
176
+ ### results
177
+ # Calculating -------------------------------------
178
+ # normal stack 8.621k i/100ms
179
+ # reuse stack 17.643k i/100ms
180
+ # direct stack 21.179k i/100ms
181
+ # -------------------------------------------------
182
+ # normal stack 98.986k (± 6.4%) i/s - 500.018k
183
+ # reuse stack 243.372k (± 6.4%) i/s - 1.217M
184
+ # direct stack 285.986k (± 5.7%) i/s - 1.440M
185
+
186
+ # Comparison:
187
+ # direct stack: 285985.9 i/s
188
+ # reuse stack: 243372.1 i/s - 1.18x slower
189
+ # normal stack: 98986.1 i/s - 2.89x slower
data/lib/middleware.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "middleware/version"
2
+ require "middleware/builder"
3
+ require "middleware/runner"
@@ -0,0 +1,144 @@
1
+ module Middleware
2
+ # This provides a DSL for building up a stack of middlewares.
3
+ #
4
+ # This code is based heavily off of `Rack::Builder` and
5
+ # `ActionDispatch::MiddlewareStack` in Rack and Rails, respectively.
6
+ #
7
+ # # Usage
8
+ #
9
+ # Building a middleware stack is very easy:
10
+ #
11
+ # app = Middleware::Builder.new do
12
+ # use A
13
+ # use B
14
+ # end
15
+ #
16
+ # # Call the middleware
17
+ # app.call(7)
18
+ #
19
+ class Builder
20
+ # Initializes the builder. An optional block can be passed which
21
+ # will be evaluated in the context of the instance.
22
+ #
23
+ # Example:
24
+ #
25
+ # Builder.new do
26
+ # use A
27
+ # use B
28
+ # end
29
+ #
30
+ # @param [Hash] opts Options hash
31
+ # @option opts [Class] :runner_class The class to wrap the middleware stack
32
+ # in which knows how to run them.
33
+ # @yield [] Evaluated in this instance which allows you to use methods
34
+ # like {#use} and such.
35
+ def initialize(opts=nil, &block)
36
+ opts ||= {}
37
+ @runner_class = opts[:runner_class] || Runner
38
+ @reuse_instance = opts[:reuse_instance]
39
+ instance_eval(&block) if block_given?
40
+ end
41
+
42
+ # Returns a mergeable version of the builder. If `use` is called with
43
+ # the return value of this method, then the stack will merge, instead
44
+ # of being treated as a separate single middleware.
45
+ def flatten
46
+ lambda do |env|
47
+ self.call(env)
48
+ end
49
+ end
50
+
51
+ # Adds a middleware class to the middleware stack. Any additional
52
+ # args and a block, if given, are saved and passed to the initializer
53
+ # of the middleware.
54
+ #
55
+ # @param [Class] middleware The middleware class
56
+ def use(middleware, *args, &block)
57
+ if middleware.kind_of?(Builder)
58
+ # Merge in the other builder's stack into our own
59
+ self.stack.concat(middleware.stack)
60
+ else
61
+ self.stack << [middleware, args, block]
62
+ end
63
+
64
+ self
65
+ end
66
+
67
+ # Inserts a middleware at the given index or directly before the
68
+ # given middleware object.
69
+ def insert(index, middleware, *args, &block)
70
+ index = self.index(index) unless index.is_a?(Integer)
71
+ raise "no such middleware to insert before: #{index.inspect}" unless index
72
+ stack.insert(index, [middleware, args, block])
73
+ end
74
+
75
+ alias_method :insert_before, :insert
76
+
77
+ # Inserts a middleware after the given index or middleware object.
78
+ def insert_after(index, middleware, *args, &block)
79
+ index = self.index(index) unless index.is_a?(Integer)
80
+ raise "no such middleware to insert after: #{index.inspect}" unless index
81
+ insert(index + 1, middleware, *args, &block)
82
+ end
83
+
84
+ # Replaces the given middleware object or index with the new
85
+ # middleware.
86
+ def replace(index, middleware, *args, &block)
87
+ if index.is_a?(Integer)
88
+ delete(index)
89
+ insert(index, middleware, *args, &block)
90
+ else
91
+ insert_before(index, middleware, *args, &block)
92
+ delete(index)
93
+ end
94
+ end
95
+
96
+ # Deletes the given middleware object or index
97
+ def delete(index)
98
+ index = self.index(index) unless index.is_a?(Integer)
99
+ stack.delete_at(index)
100
+ end
101
+
102
+ # Runs the builder stack with the given environment.
103
+ def call(env=nil)
104
+ if @reuse_instance
105
+ runner_instance.call(env)
106
+ else
107
+ to_app.call(env)
108
+ end
109
+ end
110
+
111
+ protected
112
+
113
+ # Returns the numeric index for the given middleware object.
114
+ #
115
+ # @param [Object] object The item to find the index for
116
+ # @return [Integer]
117
+ def index(object)
118
+ stack.each_with_index do |item, i|
119
+ return i if item[0] == object
120
+ end
121
+
122
+ nil
123
+ end
124
+
125
+ # Returns the current stack of middlewares. You probably won't
126
+ # need to use this directly, and it's recommended that you don't.
127
+ #
128
+ # @return [Array]
129
+ def stack
130
+ @stack ||= []
131
+ end
132
+
133
+ # Converts the builder stack to a runnable action sequence.
134
+ #
135
+ # @return [Object] A callable object
136
+ def to_app
137
+ @runner_class.new(stack.dup)
138
+ end
139
+
140
+ def runner_instance
141
+ @runner_instance ||= to_app
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,69 @@
1
+ module Middleware
2
+ # This is a basic runner for middleware stacks. This runner does
3
+ # the default expected behavior of running the middleware stacks
4
+ # in order, then reversing the order.
5
+ class Runner
6
+ # A middleware which does nothing
7
+ EMPTY_MIDDLEWARE = lambda { |env| }
8
+
9
+ # Build a new middleware runner with the given middleware
10
+ # stack.
11
+ #
12
+ # Note: This class usually doesn't need to be used directly.
13
+ # Instead, take a look at using the {Builder} class, which is
14
+ # a much friendlier way to build up a middleware stack.
15
+ #
16
+ # @param [Array] stack An array of the middleware to run.
17
+ def initialize(stack)
18
+ # We need to take the stack of middleware and initialize them
19
+ # all so they call the proper next middleware.
20
+ @kickoff = build_call_chain(stack)
21
+ end
22
+
23
+ # Run the middleware stack with the given state bag.
24
+ #
25
+ # @param [Object] env The state to pass into as the initial
26
+ # environment data. This is usual a hash of some sort.
27
+ def call(env)
28
+ # We just call the kickoff middleware, which is responsible
29
+ # for properly calling the next middleware, and so on and so
30
+ # forth.
31
+ @kickoff.call(env)
32
+ end
33
+
34
+ protected
35
+
36
+ # This takes a stack of middlewares and initializes them in a way
37
+ # that each middleware properly calls the next middleware.
38
+ def build_call_chain(stack)
39
+ # We need to instantiate the middleware stack in reverse
40
+ # order so that each middleware can have a reference to
41
+ # the next middleware it has to call. The final middleware
42
+ # is always the empty middleware, which does nothing but return.
43
+ stack.reverse.inject(EMPTY_MIDDLEWARE) do |next_middleware, current_middleware|
44
+ # Unpack the actual item
45
+ klass, args, block = current_middleware
46
+
47
+ # Default the arguments to an empty array. Otherwise in Ruby 1.8
48
+ # a `nil` args will actually pass `nil` into the class. Not what
49
+ # we want!
50
+ args ||= []
51
+
52
+ if klass.is_a?(Class)
53
+ # If the klass actually is a class, then instantiate it with
54
+ # the app and any other arguments given.
55
+ klass.new(next_middleware, *args, &block)
56
+ elsif klass.respond_to?(:call)
57
+ # Make it a lambda which calls the item then forwards up
58
+ # the chain.
59
+ lambda do |env|
60
+ klass.call(env)
61
+ next_middleware.call(env)
62
+ end
63
+ else
64
+ raise "Invalid middleware, doesn't respond to `call`: #{klass.inspect}"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Middleware
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/middleware/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Mitchell Hashimoto", "Roman Heinrich"]
6
+ gem.email = ["mitchell.hashimoto@gmail.com"]
7
+ gem.description = %q{Generalized implementation of the middleware abstraction for Ruby.}
8
+ gem.summary = %q{Generalized implementation of the middleware abstraction for Ruby.}
9
+ gem.homepage = "https://github.com/mindreframer/middleware"
10
+ gem.license = "MIT"
11
+
12
+ gem.add_development_dependency "rake"
13
+ gem.add_development_dependency "redcarpet", "~> 2.1.0"
14
+ gem.add_development_dependency "rspec-core", "~> 2.8.0"
15
+ gem.add_development_dependency "rspec-expectations", "~> 2.8.0"
16
+ gem.add_development_dependency "rspec-mocks", "~> 2.8.0"
17
+ gem.add_development_dependency "yard", "~> 0.7.5"
18
+ gem.add_development_dependency "benchmark-ips"
19
+
20
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ gem.files = `git ls-files`.split("\n")
22
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ gem.name = "middleware-xt"
24
+ gem.require_paths = ["lib"]
25
+ gem.version = Middleware::VERSION
26
+ end
@@ -0,0 +1,160 @@
1
+ require File.expand_path("../../setup", __FILE__)
2
+ require "middleware"
3
+
4
+ describe Middleware::Builder do
5
+ let(:data) { { :data => [] } }
6
+ let(:instance) { described_class.new }
7
+
8
+ # This returns a proc that can be used with the builder
9
+ # that simply appends data to an array in the env.
10
+ def appender_proc(data)
11
+ Proc.new { |env| env[:data] << data }
12
+ end
13
+
14
+ context "basic `use`" do
15
+ it "should add items to the stack and make them callable" do
16
+ data = {}
17
+ proc = Proc.new { |env| env[:data] = true }
18
+
19
+ instance.use proc
20
+ instance.call(data)
21
+
22
+ data[:data].should == true
23
+ end
24
+
25
+ it "should be able to add multiple items" do
26
+ data = {}
27
+ proc1 = Proc.new { |env| env[:one] = true }
28
+ proc2 = Proc.new { |env| env[:two] = true }
29
+
30
+ instance.use proc1
31
+ instance.use proc2
32
+ instance.call(data)
33
+
34
+ data[:one].should == true
35
+ data[:two].should == true
36
+ end
37
+
38
+ it "should be able to add another builder" do
39
+ data = {}
40
+ proc1 = Proc.new { |env| env[:one] = true }
41
+
42
+ # Build the first builder
43
+ one = described_class.new
44
+ one.use proc1
45
+
46
+ # Add it to this builder
47
+ two = described_class.new
48
+ two.use one
49
+
50
+ # Call the 2nd and verify results
51
+ two.call(data)
52
+ data[:one].should == true
53
+ end
54
+
55
+ it "should default the env to `nil` if not given" do
56
+ result = false
57
+ proc = Proc.new { |env| result = env.nil? }
58
+
59
+ instance.use proc
60
+ instance.call
61
+
62
+ result.should be
63
+ end
64
+ end
65
+
66
+ context "inserting" do
67
+ it "can insert at an index" do
68
+ instance.use appender_proc(1)
69
+ instance.insert(0, appender_proc(2))
70
+ instance.call(data)
71
+
72
+ data[:data].should == [2, 1]
73
+ end
74
+
75
+ it "can insert next to a previous object" do
76
+ proc2 = appender_proc(2)
77
+ instance.use appender_proc(1)
78
+ instance.use proc2
79
+ instance.insert(proc2, appender_proc(3))
80
+ instance.call(data)
81
+
82
+ data[:data].should == [1, 3, 2]
83
+ end
84
+
85
+ it "can insert before" do
86
+ instance.use appender_proc(1)
87
+ instance.insert_before 0, appender_proc(2)
88
+ instance.call(data)
89
+
90
+ data[:data].should == [2, 1]
91
+ end
92
+
93
+ it "raises an exception if attempting to insert before an invalid object" do
94
+ expect { instance.insert "object", appender_proc(1) }.
95
+ to raise_error(RuntimeError)
96
+ end
97
+
98
+ it "can insert after" do
99
+ instance.use appender_proc(1)
100
+ instance.use appender_proc(3)
101
+ instance.insert_after 0, appender_proc(2)
102
+ instance.call(data)
103
+
104
+ data[:data].should == [1, 2, 3]
105
+ end
106
+
107
+ it "raises an exception if attempting to insert after an invalid object" do
108
+ expect { instance.insert_after "object", appender_proc(1) }.
109
+ to raise_error(RuntimeError)
110
+ end
111
+ end
112
+
113
+ context "replace" do
114
+ it "can replace an object" do
115
+ proc1 = appender_proc(1)
116
+ proc2 = appender_proc(2)
117
+
118
+ instance.use proc1
119
+ instance.replace proc1, proc2
120
+ instance.call(data)
121
+
122
+ data[:data].should == [2]
123
+ end
124
+
125
+ it "can replace by index" do
126
+ proc1 = appender_proc(1)
127
+ proc2 = appender_proc(2)
128
+
129
+ instance.use proc1
130
+ instance.replace 0, proc2
131
+ instance.call(data)
132
+
133
+ data[:data].should == [2]
134
+ end
135
+ end
136
+
137
+ context "deleting" do
138
+ it "can delete by object" do
139
+ proc1 = appender_proc(1)
140
+
141
+ instance.use proc1
142
+ instance.use appender_proc(2)
143
+ instance.delete proc1
144
+ instance.call(data)
145
+
146
+ data[:data].should == [2]
147
+ end
148
+
149
+ it "can delete by index" do
150
+ proc1 = appender_proc(1)
151
+
152
+ instance.use proc1
153
+ instance.use appender_proc(2)
154
+ instance.delete 0
155
+ instance.call(data)
156
+
157
+ data[:data].should == [2]
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,193 @@
1
+ require File.expand_path("../../setup", __FILE__)
2
+ require "middleware"
3
+
4
+ describe Middleware::Runner do
5
+ it "should work with an empty stack" do
6
+ instance = described_class.new([])
7
+ expect { instance.call({}) }.to_not raise_error
8
+ end
9
+
10
+ it "should call classes in the proper order" do
11
+ a = Class.new do
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ env[:result] << "A"
18
+ @app.call(env)
19
+ env[:result] << "A"
20
+ end
21
+ end
22
+
23
+ b = Class.new do
24
+ def initialize(app)
25
+ @app = app
26
+ end
27
+
28
+ def call(env)
29
+ env[:result] << "B"
30
+ @app.call(env)
31
+ env[:result] << "B"
32
+ end
33
+ end
34
+
35
+ env = { :result => [] }
36
+ instance = described_class.new([a, b])
37
+ instance.call(env)
38
+ env[:result].should == ["A", "B", "B", "A"]
39
+ end
40
+
41
+ it "should call lambdas in the proper order" do
42
+ data = []
43
+ a = lambda { |env| data << "A" }
44
+ b = lambda { |env| data << "B" }
45
+
46
+ instance = described_class.new([a, b])
47
+ instance.call({})
48
+
49
+ data.should == ["A", "B"]
50
+ end
51
+
52
+ it "passes in arguments if given" do
53
+ a = Class.new do
54
+ def initialize(app, value)
55
+ @app = app
56
+ @value = value
57
+ end
58
+
59
+ def call(env)
60
+ env[:result] = @value
61
+ end
62
+ end
63
+
64
+ env = {}
65
+ instance = described_class.new([[a, 42]])
66
+ instance.call(env)
67
+
68
+ env[:result].should == 42
69
+ end
70
+
71
+ it "passes in a block if given" do
72
+ a = Class.new do
73
+ def initialize(app, &block)
74
+ @block = block
75
+ end
76
+
77
+ def call(env)
78
+ env[:result] = @block.call
79
+ end
80
+ end
81
+
82
+ block = Proc.new { 42 }
83
+ env = {}
84
+ instance = described_class.new([[a, nil, block]])
85
+ instance.call(env)
86
+
87
+ env[:result].should == 42
88
+ end
89
+
90
+ it "should raise an error if an invalid middleware is given" do
91
+ expect { described_class.new([27]) }.to raise_error(/Invalid middleware/)
92
+ end
93
+
94
+ it "should not call middlewares which aren't called" do
95
+ # A does not call B, so B should never execute
96
+ data = []
97
+ a = Class.new do
98
+ def initialize(app); @app = app; end
99
+
100
+ define_method :call do |env|
101
+ data << "a"
102
+ end
103
+ end
104
+
105
+ b = lambda { |env| data << "b" }
106
+
107
+ env = {}
108
+ instance = described_class.new([a, b])
109
+ instance.call(env)
110
+
111
+ data.should == ["a"]
112
+ end
113
+
114
+ describe "exceptions" do
115
+ it "should propagate the exception up the middleware chain" do
116
+ # This tests a few important properties:
117
+ # * Exceptions propagate multiple middlewares
118
+ # - C raises an exception, which raises through B to A.
119
+ # * Rescuing exceptions works
120
+ data = []
121
+ a = Class.new do
122
+ def initialize(app)
123
+ @app = app
124
+ end
125
+
126
+ define_method :call do |env|
127
+ data << "a"
128
+ begin
129
+ @app.call(env)
130
+ data << "never"
131
+ rescue Exception => e
132
+ data << "e"
133
+ raise
134
+ end
135
+ end
136
+ end
137
+
138
+ b = Class.new do
139
+ def initialize(app); @app = app; end
140
+
141
+ define_method :call do |env|
142
+ data << "b"
143
+ @app.call(env)
144
+ end
145
+ end
146
+
147
+ c = lambda { |env| raise "ERROR" }
148
+
149
+ env = {}
150
+ instance = described_class.new([a, b, c])
151
+ expect { instance.call(env) }.to raise_error
152
+
153
+ data.should == ["a", "b", "e"]
154
+ end
155
+
156
+ it "should stop propagation if rescued" do
157
+ # This test mainly tests that if there is a sequence A, B, C, and
158
+ # an exception is raised in C, that if B rescues this, then the chain
159
+ # continues fine backwards.
160
+ data = []
161
+ a = Class.new do
162
+ def initialize(app); @app = app; end
163
+
164
+ define_method :call do |env|
165
+ data << "in_a"
166
+ @app.call(env)
167
+ data << "out_a"
168
+ end
169
+ end
170
+
171
+ b = Class.new do
172
+ def initialize(app); @app = app; end
173
+
174
+ define_method :call do |env|
175
+ data << "in_b"
176
+ @app.call(env) rescue nil
177
+ data << "out_b"
178
+ end
179
+ end
180
+
181
+ c = lambda do |env|
182
+ data << "in_c"
183
+ raise "BAD"
184
+ end
185
+
186
+ env = {}
187
+ instance = described_class.new([a, b, c])
188
+ instance.call(env)
189
+
190
+ data.should == ["in_a", "in_b", "in_c", "out_b", "out_a"]
191
+ end
192
+ end
193
+ end
data/spec/setup.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "rubygems"
2
+ require "rspec/autorun"
3
+
4
+ # Do not buffer output
5
+ $stdout.sync = true
6
+ $stderr.sync = true
7
+
8
+ # Configure RSpec
9
+ RSpec.configure do |c|
10
+ end
data/user_guide.md ADDED
@@ -0,0 +1,207 @@
1
+ # Middleware User Guide
2
+
3
+ `middleware` is a library which provides a generalized implementation
4
+ of the middleware pattern for Ruby. The middleware pattern is a useful
5
+ abstraction tool in various cases, but is specifically useful for splitting
6
+ large sequential chunks of logic into small pieces.
7
+
8
+ ## Installing
9
+
10
+ Middleware is distributed as a RubyGem, so simply gem install:
11
+
12
+ ```console
13
+ $ gem install middleware
14
+ ```
15
+
16
+ ## A Basic Example
17
+
18
+ Below is a basic example of the library in use. If you don't understand
19
+ what middleware is, please read below. This example is simply meant to give
20
+ you a quick idea of what the library looks like.
21
+
22
+ ```ruby
23
+ # Basic middleware that just prints the inbound and
24
+ # outbound steps.
25
+ class Trace
26
+ def initialize(app, value)
27
+ @app = app
28
+ @value = value
29
+ end
30
+
31
+ def call(env)
32
+ puts "--> #{@value}"
33
+ @app.call(env)
34
+ puts "<-- #{@value}"
35
+ end
36
+ end
37
+
38
+ # Build the actual middleware stack which runs a sequence
39
+ # of slightly different versions of our middleware.
40
+ stack = Middleware::Builder.new do
41
+ use Trace, "A"
42
+ use Trace, "B"
43
+ use Trace, "C"
44
+ end
45
+
46
+ # Run it!
47
+ stack.call(nil)
48
+ ```
49
+
50
+ And the output:
51
+
52
+ ```
53
+ --> A
54
+ --> B
55
+ --> C
56
+ <-- C
57
+ <-- B
58
+ <-- A
59
+ ```
60
+
61
+
62
+
63
+ ## Middleware
64
+
65
+ ### What is it?
66
+
67
+ Middleware is a reusable chunk of logic that is called to perform some
68
+ action. The middleware itself is responsible for calling up the next item
69
+ in the middleware chain using a recursive-like call. This allows middleware
70
+ to perform logic both _before_ and _after_ something is done.
71
+
72
+ The canonical middleware example is in web request processing, and middleware
73
+ is used heavily by both [Rack](#) and [Rails](#).
74
+ In web processing, the first middleware is called with some information about
75
+ the web request, such as HTTP headers, request URL, etc. The middleware is
76
+ responsible for calling the next middleware, and may modify the request along
77
+ the way. When the middlewares begin returning, the state now has the HTTP
78
+ response, so that the middlewares can then modify the response.
79
+
80
+ Cool? Yeah! And this pattern is generally usable in a wide variety of
81
+ problems.
82
+
83
+ ### Middleware Classes
84
+
85
+ One method of creating middleware, and by far the most common, is to define
86
+ a class that duck types to the following interface:
87
+
88
+ ```ruby
89
+ class MiddlewareExample
90
+ def initialize(app); end
91
+ def call(env); end
92
+ end
93
+ ```
94
+
95
+ Therefore, a basic middleware example follows:
96
+
97
+ ```ruby
98
+ class Trace
99
+ def initialize(app)
100
+ @app = app
101
+ end
102
+
103
+ def call(env)
104
+ puts "Trace up"
105
+ @app.call(env)
106
+ puts "Trace down"
107
+ end
108
+ end
109
+ ```
110
+
111
+ A basic description of the two methods that a middleware must implement:
112
+
113
+ * **initialize(app)** - This is a constructor. It can take additional arguments
114
+ but the first argument sent will always be the next middleware to call, called
115
+ `app` for historical reasons. This should be stored away for later.
116
+
117
+ * **call(env)** - This is what is actually invoked to do work. `env` is just some
118
+ state sent in (defined by the caller, but usually a Hash). This call should also
119
+ call `app.call(env)` at some point to move on.
120
+
121
+ ### Middleware Lambdas
122
+
123
+ A middleware can also be a simple lambda. The downside of using a lambda is that
124
+ it only has access to the state on the initial call, there is no "post" step for
125
+ lambdas. A basic example, in the context of a web request:
126
+
127
+ ```ruby
128
+ lambda { |env| puts "You requested: #{env["http.request_url"]}" }
129
+ ```
130
+
131
+ ## Middleware Stacks
132
+
133
+ Middlewares on their own are useful as small chunks of logic, but their real
134
+ power comes from building them up into a _stack_. A stack of middlewares are
135
+ executed in the order given.
136
+
137
+ ### Basic Building and Running
138
+
139
+ The middleware library comes with a `Builder` class which provides a nice DSL
140
+ for building a stack of middlewares:
141
+
142
+ ```ruby
143
+ stack = Middleware::Builder.new do
144
+ use Trace
145
+ use lambda { |env| puts "LAMBDA!" }
146
+ end
147
+ ```
148
+
149
+ This `stack` variable itself is now a valid middleware and has the same interface,
150
+ so to execute the stack, just call `call` on it:
151
+
152
+ ```ruby
153
+ stack.call
154
+ ```
155
+
156
+ The call method takes an optional parameter which is the state to pass into the
157
+ initial middleware.
158
+
159
+ ### Manipulating a Stack
160
+
161
+ Stacks also provide a set of methods for manipulating the middleware stack. This
162
+ lets you insert, replace, and delete middleware after a stack has already been
163
+ created. Given the `stack` variable created above, we can manipulate it as
164
+ follows. Please imagine that each example runs with the original `stack` variable,
165
+ so that the order of the examples doesn't actually matter:
166
+
167
+ ```ruby
168
+ # Insert a new item after the Trace middleware
169
+ stack.insert_after(Trace, SomeOtherMiddleware)
170
+
171
+ # Replace the lambda
172
+ stack.replace(1, SomeOtherMiddleware)
173
+
174
+ # Delete the lambda
175
+ stack.delete(1)
176
+ ```
177
+
178
+ ### Passing Additional Constructor Arguments
179
+
180
+ When using middleware in a stack, you can also pass in additional constructor
181
+ arguments. Given the following middleware:
182
+
183
+ ```ruby
184
+ class Echo
185
+ def initialize(app, message)
186
+ @app = app
187
+ @message = message
188
+ end
189
+
190
+ def call(env)
191
+ puts @message
192
+ @app.call(env)
193
+ end
194
+ end
195
+ ```
196
+
197
+ We can initialize `Echo` with a proper message as follows:
198
+
199
+ ```ruby
200
+ Middleware::Builder.new do
201
+ use Echo, "Hello, World!"
202
+ end
203
+ ```
204
+
205
+ Then when the stack is called, it will output "Hello, World!"
206
+
207
+ Note that you can also pass blocks in using the `use` method.
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: middleware-xt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Mitchell Hashimoto
8
+ - Roman Heinrich
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-01-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: redcarpet
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 2.1.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 2.1.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec-core
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 2.8.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 2.8.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec-expectations
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 2.8.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 2.8.0
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec-mocks
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 2.8.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 2.8.0
84
+ - !ruby/object:Gem::Dependency
85
+ name: yard
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 0.7.5
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.7.5
98
+ - !ruby/object:Gem::Dependency
99
+ name: benchmark-ips
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ description: Generalized implementation of the middleware abstraction for Ruby.
113
+ email:
114
+ - mitchell.hashimoto@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".travis.yml"
121
+ - ".yardopts"
122
+ - CHANGELOG.md
123
+ - Gemfile
124
+ - LICENSE
125
+ - Makefile
126
+ - README.md
127
+ - Rakefile
128
+ - benchmarks/reusable_runner_instance.rb
129
+ - lib/middleware.rb
130
+ - lib/middleware/builder.rb
131
+ - lib/middleware/runner.rb
132
+ - lib/middleware/version.rb
133
+ - middleware.gemspec
134
+ - spec/middleware/builder_spec.rb
135
+ - spec/middleware/runner_spec.rb
136
+ - spec/setup.rb
137
+ - user_guide.md
138
+ homepage: https://github.com/mindreframer/middleware
139
+ licenses:
140
+ - MIT
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.4.4
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: Generalized implementation of the middleware abstraction for Ruby.
162
+ test_files:
163
+ - spec/middleware/builder_spec.rb
164
+ - spec/middleware/runner_spec.rb
165
+ - spec/setup.rb
166
+ has_rdoc: