middleware-xt 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: