circuit 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.
- data/Gemfile +34 -0
- data/LICENSE +20 -0
- data/README.md +161 -0
- data/Rakefile +27 -0
- data/config.ru +7 -0
- data/description.md +5 -0
- data/docs/COMPATIBILITY.md +14 -0
- data/docs/ROADMAP.md +29 -0
- data/lib/circuit.rb +125 -0
- data/lib/circuit/behavior.rb +99 -0
- data/lib/circuit/compatibility.rb +73 -0
- data/lib/circuit/middleware.rb +6 -0
- data/lib/circuit/middleware/rewriter.rb +43 -0
- data/lib/circuit/rack.rb +14 -0
- data/lib/circuit/rack/behavioral.rb +45 -0
- data/lib/circuit/rack/builder.rb +50 -0
- data/lib/circuit/rack/multi_site.rb +22 -0
- data/lib/circuit/rack/request.rb +81 -0
- data/lib/circuit/railtie.rb +24 -0
- data/lib/circuit/storage.rb +74 -0
- data/lib/circuit/storage/memory_model.rb +70 -0
- data/lib/circuit/storage/nodes.rb +56 -0
- data/lib/circuit/storage/nodes/memory_store.rb +63 -0
- data/lib/circuit/storage/nodes/model.rb +67 -0
- data/lib/circuit/storage/nodes/mongoid_store.rb +56 -0
- data/lib/circuit/storage/sites.rb +38 -0
- data/lib/circuit/storage/sites/memory_store.rb +53 -0
- data/lib/circuit/storage/sites/model.rb +29 -0
- data/lib/circuit/storage/sites/mongoid_store.rb +43 -0
- data/lib/circuit/validators.rb +74 -0
- data/lib/circuit/version.rb +3 -0
- data/spec/internal/app/behaviors/change_path.rb +7 -0
- data/spec/internal/app/controllers/application_controller.rb +5 -0
- data/spec/internal/app/helpers/application_helper.rb +2 -0
- data/spec/internal/config/initializers/circuit.rb +7 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +1 -0
- data/spec/lib/circuit/behavior_spec.rb +113 -0
- data/spec/lib/circuit/middleware/rewriter_spec.rb +79 -0
- data/spec/lib/circuit/rack/behavioral_spec.rb +60 -0
- data/spec/lib/circuit/rack/builder_spec.rb +125 -0
- data/spec/lib/circuit/rack/multi_site_spec.rb +34 -0
- data/spec/lib/circuit/rack/request_spec.rb +80 -0
- data/spec/lib/circuit/railtie_spec.rb +34 -0
- data/spec/lib/circuit/storage/nodes_spec.rb +62 -0
- data/spec/lib/circuit/storage/sites_spec.rb +60 -0
- data/spec/lib/circuit/storage_spec.rb +20 -0
- data/spec/lib/circuit/validators_spec.rb +69 -0
- data/spec/lib/circuit_spec.rb +139 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/blueprints.rb +24 -0
- data/spec/support/matchers/be_current_time_matcher.rb +14 -0
- data/spec/support/matchers/extended_have_key.rb +31 -0
- data/spec/support/matchers/have_accessor_matcher.rb +13 -0
- data/spec/support/matchers/have_attribute_matcher.rb +22 -0
- data/spec/support/matchers/have_block_matcher.rb +14 -0
- data/spec/support/matchers/have_errors_on_matcher.rb +30 -0
- data/spec/support/matchers/have_module_matcher.rb +13 -0
- data/spec/support/matchers/have_reader_matcher.rb +18 -0
- data/spec/support/matchers/have_writer_matcher.rb +18 -0
- data/spec/support/matchers/set_instance_variable.rb +32 -0
- data/spec/support/spec_helpers/base_behaviors.rb +20 -0
- data/spec/support/spec_helpers/base_models.rb +64 -0
- data/spec/support/spec_helpers/logger_helpers.rb +58 -0
- data/spec/support/spec_helpers/multi_site_helper.rb +48 -0
- data/spec/support/spec_helpers/rack_helpers.rb +8 -0
- data/spec/support/spec_helpers/shared_examples/node_store.rb +87 -0
- data/spec/support/spec_helpers/shared_examples/site_store.rb +76 -0
- data/spec/support/spec_helpers/simple_machinable.rb +29 -0
- data/spec/support/spec_helpers/stores_cleaner.rb +46 -0
- data/vendor/active_support-3.2/core_ext/string/inflections.rb +53 -0
- data/vendor/active_support-3.2/inflector/methods.rb +65 -0
- data/vendor/rack-1.4/builder.rb +167 -0
- data/vendor/rack-1.4/urlmap.rb +96 -0
- metadata +238 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module SpecHelpers
|
4
|
+
module MultiSiteHelper
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include BaseModels
|
7
|
+
|
8
|
+
def setup_site!(site, behavior)
|
9
|
+
site.route.behavior = behavior
|
10
|
+
site.route.save!
|
11
|
+
site
|
12
|
+
end
|
13
|
+
|
14
|
+
def stub_app_with_circuit_site(my_site, middleware_klass=set_site_middleware)
|
15
|
+
Rack::Builder.app do
|
16
|
+
use middleware_klass, my_site
|
17
|
+
use Circuit::Rack::Behavioral
|
18
|
+
run Proc.new {|env| [404, {}, ["downstream #{env['PATH_INFO']}"]] }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_site_middleware()
|
23
|
+
(Class.new do
|
24
|
+
def initialize(app, site)
|
25
|
+
@app = app
|
26
|
+
@site = site
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
request = Rack::Request.new(env)
|
31
|
+
request.site = @site
|
32
|
+
@app.call(request.env)
|
33
|
+
end
|
34
|
+
end)
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
|
39
|
+
def get(*args)
|
40
|
+
before do
|
41
|
+
super(*args) if respond_to?(:super)
|
42
|
+
get *args
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "active_support/inflector"
|
2
|
+
|
3
|
+
shared_examples "node store" do
|
4
|
+
subject { Circuit.node_store }
|
5
|
+
it { should be_instance_of(Circuit::Storage::Nodes.const_get(store.to_s.classify)) }
|
6
|
+
|
7
|
+
context "get root" do
|
8
|
+
before { root }
|
9
|
+
it { subject.get(site, "/").should == [root] }
|
10
|
+
end
|
11
|
+
|
12
|
+
context "get missing root" do
|
13
|
+
it do
|
14
|
+
expect { subject.get!(dup_site_1, "/") }.
|
15
|
+
to raise_error(Circuit::Storage::Nodes::NotFoundError, "Path not found")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "get nodes" do
|
20
|
+
# before { great_grandchild }
|
21
|
+
it do
|
22
|
+
subject.get(site, great_grandchild.path).
|
23
|
+
should == [root, child, grandchild, great_grandchild]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "get missing route" do
|
28
|
+
before { child }
|
29
|
+
it { subject.get(site, child.path+"/foobar").
|
30
|
+
should == [root, child] }
|
31
|
+
it { subject.get!(site, child.path+"/foobar").
|
32
|
+
should == [root, child] }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe Circuit::Node do
|
36
|
+
subject { child }
|
37
|
+
|
38
|
+
context "has slug" do
|
39
|
+
it { should respond_to(:slug) }
|
40
|
+
it { subject.slug.should_not be_blank}
|
41
|
+
end
|
42
|
+
|
43
|
+
context "requires slug" do
|
44
|
+
before { subject.slug = nil }
|
45
|
+
it { subject.save.should be_false }
|
46
|
+
end
|
47
|
+
|
48
|
+
context "has behavior_klass" do
|
49
|
+
it { should respond_to(:behavior_klass) }
|
50
|
+
it { subject.behavior_klass.should_not be_blank}
|
51
|
+
end
|
52
|
+
|
53
|
+
context "requires behavior_klass" do
|
54
|
+
before { subject.behavior_klass = nil }
|
55
|
+
it { subject.save.should be_false }
|
56
|
+
end
|
57
|
+
|
58
|
+
context "behavior is constantized behavior_klass" do
|
59
|
+
it { subject.behavior.should == subject.behavior_klass.constantize }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "behavior_klass is settable by behavior= with module" do
|
63
|
+
before { subject.behavior = ChangePath }
|
64
|
+
it { subject.behavior_klass.should == "ChangePath" }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "behavior_klass is settable by behavior= with cru" do
|
68
|
+
before { subject.behavior = Circuit::Behavior.get("Redirect") }
|
69
|
+
it { subject.behavior_klass.should == "Redirect" }
|
70
|
+
end
|
71
|
+
|
72
|
+
context "root has a site, slug is nil" do
|
73
|
+
it { root.site.should == site }
|
74
|
+
it { root.root?.should be_true}
|
75
|
+
it { root.slug.should be_nil }
|
76
|
+
end
|
77
|
+
|
78
|
+
context "child does not have a site" do
|
79
|
+
it { subject.site.should be_nil }
|
80
|
+
it { subject.root?.should be_false }
|
81
|
+
end
|
82
|
+
|
83
|
+
context "child should build path" do
|
84
|
+
it { subject.path.should == "/#{subject.slug}" }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "active_support/inflector"
|
2
|
+
|
3
|
+
shared_examples "site store" do
|
4
|
+
subject { Circuit.site_store }
|
5
|
+
it { should be_instance_of(Circuit::Storage::Sites.const_get(store.to_s.classify)) }
|
6
|
+
|
7
|
+
context "get host" do
|
8
|
+
before { root }
|
9
|
+
it { subject.get(site.host).should eql(site) }
|
10
|
+
end
|
11
|
+
|
12
|
+
context "get alias" do
|
13
|
+
it { subject.get(site.aliases.first).should == site }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "get missing host" do
|
17
|
+
it { subject.get("www.missinghost.com").should be_nil }
|
18
|
+
it do
|
19
|
+
expect { subject.get!("www.missinghost.com") }.
|
20
|
+
to raise_error(Circuit::Storage::Sites::NotFoundError, "Host not found")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "get duplicated host" do
|
25
|
+
it do
|
26
|
+
expect { dup_site_1_dup; subject.get(dup_site_1.host) }.
|
27
|
+
to raise_error(Circuit::Storage::Sites::MultipleFoundError, "Multiple sites found")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "get duplicated host by alias" do
|
32
|
+
it do
|
33
|
+
expect { dup_site_2_dup; subject.get(dup_site_2.host) }.
|
34
|
+
to raise_error(Circuit::Storage::Sites::MultipleFoundError, "Multiple sites found")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "get duplicated alias" do
|
39
|
+
it do
|
40
|
+
expect { dup_site_3_dup; subject.get(dup_site_3.aliases.first) }.
|
41
|
+
to raise_error(Circuit::Storage::Sites::MultipleFoundError, "Multiple sites found")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe Circuit::Site do
|
46
|
+
subject { site }
|
47
|
+
|
48
|
+
context "has aliases" do
|
49
|
+
it { should respond_to(:aliases) }
|
50
|
+
it { subject.aliases.should_not be_blank }
|
51
|
+
it { subject.aliases.should be_a(Array) }
|
52
|
+
it { subject.aliases.length.should == 2 }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "allows blank aliases" do
|
56
|
+
before { subject.aliases = [] }
|
57
|
+
it { subject.aliases.should be_empty }
|
58
|
+
it { subject.save.should be_true }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "has host" do
|
62
|
+
it { should respond_to(:host) }
|
63
|
+
it { subject.host.should_not be_blank}
|
64
|
+
end
|
65
|
+
|
66
|
+
context "requires hosts" do
|
67
|
+
before { subject.host = nil }
|
68
|
+
it { subject.save.should be_false }
|
69
|
+
end
|
70
|
+
|
71
|
+
context "has route" do
|
72
|
+
before { root }
|
73
|
+
it { subject.route.should == root }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module SimpleMachinable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class SimpleBlueprint < Machinist::Blueprint
|
7
|
+
def make!(attributes={})
|
8
|
+
make(attributes).tap(&:save!)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
included do
|
13
|
+
extend Machinist::Machinable
|
14
|
+
# ClassMethods is included *before* the included block, so blueprint_class
|
15
|
+
# would be overriden by the extend if it were in the ClassMethods module
|
16
|
+
class_eval do
|
17
|
+
def self.blueprint_class
|
18
|
+
SimpleBlueprint
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.ensure_machinable(*klasses)
|
24
|
+
klasses.each do |klass|
|
25
|
+
next if klass.respond_to?(:blueprint)
|
26
|
+
klass.send(:include, SimpleMachinable)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module SpecHelpers
|
4
|
+
module StoresCleaner
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include CircuitBlueprints
|
7
|
+
|
8
|
+
included do
|
9
|
+
around :each do |example|
|
10
|
+
orig_site_store = Circuit::Storage::Sites.instance_variable_get(:@instance)
|
11
|
+
orig_node_store = Circuit::Storage::Nodes.instance_variable_get(:@instance)
|
12
|
+
clear_storage
|
13
|
+
|
14
|
+
if @storage
|
15
|
+
Circuit.set_site_store @storage
|
16
|
+
Circuit.set_node_store @storage
|
17
|
+
ensure_blueprints
|
18
|
+
end
|
19
|
+
|
20
|
+
example.run
|
21
|
+
|
22
|
+
clear_storage
|
23
|
+
silence_warnings do
|
24
|
+
Circuit.set_site_store orig_site_store
|
25
|
+
Circuit.set_node_store orig_node_store
|
26
|
+
end
|
27
|
+
ensure_blueprints
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def use_storage(val)
|
33
|
+
before(:all) { @storage = val }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def clear_storage
|
40
|
+
Circuit::Storage::Sites.instance_variable_set(:@instance, nil)
|
41
|
+
Circuit::Storage::Nodes.instance_variable_set(:@instance, nil)
|
42
|
+
Circuit.send(:remove_const, :Site) rescue NameError
|
43
|
+
Circuit.send(:remove_const, :Node) rescue NameError
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
## Copied from Rails 3.2.6 on 19 June 2012.
|
2
|
+
## see http://github.com/rails/rails/blob/v3.2.6/activesupport/lib/active_support/core_ext/string/inflections.rb
|
3
|
+
|
4
|
+
# Copyright (c) 2005-2011 David Heinemeier Hansson
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
# String inflections define new methods on the String class to transform names for different purposes.
|
26
|
+
# For instance, you can figure out the name of a table from the name of a class.
|
27
|
+
#
|
28
|
+
# "ScaleScore".tableize # => "scale_scores"
|
29
|
+
#
|
30
|
+
class String
|
31
|
+
# Removes the module part from the constant expression in the string.
|
32
|
+
#
|
33
|
+
# "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
|
34
|
+
# "Inflections".demodulize # => "Inflections"
|
35
|
+
#
|
36
|
+
# See also +deconstantize+.
|
37
|
+
def demodulize
|
38
|
+
ActiveSupport::Inflector.demodulize(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Removes the rightmost segment from the constant expression in the string.
|
42
|
+
#
|
43
|
+
# "Net::HTTP".deconstantize # => "Net"
|
44
|
+
# "::Net::HTTP".deconstantize # => "::Net"
|
45
|
+
# "String".deconstantize # => ""
|
46
|
+
# "::String".deconstantize # => ""
|
47
|
+
# "".deconstantize # => ""
|
48
|
+
#
|
49
|
+
# See also +demodulize+.
|
50
|
+
def deconstantize
|
51
|
+
ActiveSupport::Inflector.deconstantize(self)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
## Copied from Rails 3.2.6 on 19 June 2012.
|
2
|
+
## see http://github.com/rails/rails/blob/v3.2.6/activesupport/lib/active_support/inflector/methods.rb
|
3
|
+
|
4
|
+
# Copyright (c) 2005-2011 David Heinemeier Hansson
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
25
|
+
module ActiveSupport
|
26
|
+
# The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
|
27
|
+
# and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
|
28
|
+
# in inflections.rb.
|
29
|
+
#
|
30
|
+
# The Rails core team has stated patches for the inflections library will not be accepted
|
31
|
+
# in order to avoid breaking legacy applications which may be relying on errant inflections.
|
32
|
+
# If you discover an incorrect inflection and require it for your application, you'll need
|
33
|
+
# to correct it yourself (explained below).
|
34
|
+
module Inflector
|
35
|
+
extend self
|
36
|
+
|
37
|
+
# Removes the module part from the expression in the string:
|
38
|
+
#
|
39
|
+
# "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
|
40
|
+
# "Inflections".demodulize # => "Inflections"
|
41
|
+
#
|
42
|
+
# See also +deconstantize+.
|
43
|
+
def demodulize(path)
|
44
|
+
path = path.to_s
|
45
|
+
if i = path.rindex('::')
|
46
|
+
path[(i+2)..-1]
|
47
|
+
else
|
48
|
+
path
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Removes the rightmost segment from the constant expression in the string:
|
53
|
+
#
|
54
|
+
# "Net::HTTP".deconstantize # => "Net"
|
55
|
+
# "::Net::HTTP".deconstantize # => "::Net"
|
56
|
+
# "String".deconstantize # => ""
|
57
|
+
# "::String".deconstantize # => ""
|
58
|
+
# "".deconstantize # => ""
|
59
|
+
#
|
60
|
+
# See also +demodulize+.
|
61
|
+
def deconstantize(path)
|
62
|
+
path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
## Copied from Rack 1.4.1 on 19 June 2012.
|
2
|
+
## see http://github.com/rack/rack/blob/1.4.1/lib/rack/builder.rb
|
3
|
+
|
4
|
+
# Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to
|
8
|
+
# deal in the Software without restriction, including without limitation the
|
9
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
10
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
19
|
+
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
20
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
21
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module Rack
|
24
|
+
# Rack::Builder implements a small DSL to iteratively construct Rack
|
25
|
+
# applications.
|
26
|
+
#
|
27
|
+
# Example:
|
28
|
+
#
|
29
|
+
# require 'rack/lobster'
|
30
|
+
# app = Rack::Builder.new do
|
31
|
+
# use Rack::CommonLogger
|
32
|
+
# use Rack::ShowExceptions
|
33
|
+
# map "/lobster" do
|
34
|
+
# use Rack::Lint
|
35
|
+
# run Rack::Lobster.new
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# run app
|
40
|
+
#
|
41
|
+
# Or
|
42
|
+
#
|
43
|
+
# app = Rack::Builder.app do
|
44
|
+
# use Rack::CommonLogger
|
45
|
+
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# run app
|
49
|
+
#
|
50
|
+
# +use+ adds a middleware to the stack, +run+ dispatches to an application.
|
51
|
+
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
52
|
+
|
53
|
+
class Builder
|
54
|
+
def self.parse_file(config, opts = Server::Options.new)
|
55
|
+
options = {}
|
56
|
+
if config =~ /\.ru$/
|
57
|
+
cfgfile = ::File.read(config)
|
58
|
+
if cfgfile[/^#\\(.*)/] && opts
|
59
|
+
options = opts.parse! $1.split(/\s+/)
|
60
|
+
end
|
61
|
+
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
62
|
+
app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
|
63
|
+
TOPLEVEL_BINDING, config
|
64
|
+
else
|
65
|
+
require config
|
66
|
+
app = Object.const_get(::File.basename(config, '.rb').capitalize)
|
67
|
+
end
|
68
|
+
return app, options
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(default_app = nil,&block)
|
72
|
+
@use, @map, @run = [], nil, default_app
|
73
|
+
instance_eval(&block) if block_given?
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.app(default_app = nil, &block)
|
77
|
+
self.new(default_app, &block).to_app
|
78
|
+
end
|
79
|
+
|
80
|
+
# Specifies a middleware to use in a stack.
|
81
|
+
#
|
82
|
+
# class Middleware
|
83
|
+
# def initialize(app)
|
84
|
+
# @app = app
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# def call(env)
|
88
|
+
# env["rack.some_header"] = "setting an example"
|
89
|
+
# @app.call(env)
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# use Middleware
|
94
|
+
# run lambda { |env| [200, { "Content-Type => "text/plain" }, ["OK"]] }
|
95
|
+
#
|
96
|
+
# All requests through to this application will first be processed by the middleware class.
|
97
|
+
# The +call+ method in this example sets an additional environment key which then can be
|
98
|
+
# referenced in the application if required.
|
99
|
+
def use(middleware, *args, &block)
|
100
|
+
if @map
|
101
|
+
mapping, @map = @map, nil
|
102
|
+
@use << proc { |app| generate_map app, mapping }
|
103
|
+
end
|
104
|
+
@use << proc { |app| middleware.new(app, *args, &block) }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Takes an argument that is an object that responds to #call and returns a Rack response.
|
108
|
+
# The simplest form of this is a lambda object:
|
109
|
+
#
|
110
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
111
|
+
#
|
112
|
+
# However this could also be a class:
|
113
|
+
#
|
114
|
+
# class Heartbeat
|
115
|
+
# def self.call(env)
|
116
|
+
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# run Heartbeat
|
121
|
+
def run(app)
|
122
|
+
@run = app
|
123
|
+
end
|
124
|
+
|
125
|
+
# Creates a route within the application.
|
126
|
+
#
|
127
|
+
# Rack::Builder.app do
|
128
|
+
# map '/' do
|
129
|
+
# run Heartbeat
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# The +use+ method can also be used here to specify middleware to run under a specific path:
|
134
|
+
#
|
135
|
+
# Rack::Builder.app do
|
136
|
+
# map '/' do
|
137
|
+
# use Middleware
|
138
|
+
# run Heartbeat
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
|
143
|
+
#
|
144
|
+
def map(path, &block)
|
145
|
+
@map ||= {}
|
146
|
+
@map[path] = block
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_app
|
150
|
+
app = @map ? generate_map(@run, @map) : @run
|
151
|
+
fail "missing run or map statement" unless app
|
152
|
+
@use.reverse.inject(app) { |a,e| e[a] }
|
153
|
+
end
|
154
|
+
|
155
|
+
def call(env)
|
156
|
+
to_app.call(env)
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def generate_map(default_app, mapping)
|
162
|
+
mapped = default_app ? {'/' => default_app} : {}
|
163
|
+
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b) }
|
164
|
+
URLMap.new(mapped)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|