edgar-rack 1.2.1
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/COPYING +18 -0
- data/KNOWN-ISSUES +21 -0
- data/README +401 -0
- data/Rakefile +101 -0
- data/SPEC +171 -0
- data/bin/rackup +4 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack.rb +81 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +43 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +51 -0
- data/lib/rack/auth/digest/params.rb +53 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/builder.rb +80 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +52 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +63 -0
- data/lib/rack/config.rb +15 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +157 -0
- data/lib/rack/etag.rb +59 -0
- data/lib/rack/file.rb +118 -0
- data/lib/rack/handler.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +90 -0
- data/lib/rack/handler/lsws.rb +61 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +59 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +17 -0
- data/lib/rack/handler/webrick.rb +73 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +567 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +44 -0
- data/lib/rack/logger.rb +18 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +210 -0
- data/lib/rack/mock.rb +185 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +61 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +307 -0
- data/lib/rack/response.rb +151 -0
- data/lib/rack/rewindable_input.rb +104 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +139 -0
- data/lib/rack/server.rb +289 -0
- data/lib/rack/session/abstract/id.rb +348 -0
- data/lib/rack/session/cookie.rb +152 -0
- data/lib/rack/session/memcache.rb +93 -0
- data/lib/rack/session/pool.rb +79 -0
- data/lib/rack/showexceptions.rb +378 -0
- data/lib/rack/showstatus.rb +113 -0
- data/lib/rack/static.rb +53 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +698 -0
- data/rack.gemspec +39 -0
- data/test/cgi/lighttpd.conf +25 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +6 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/rackup/config.ru +31 -0
- data/test/spec_auth_basic.rb +70 -0
- data/test/spec_auth_digest.rb +241 -0
- data/test/spec_builder.rb +123 -0
- data/test/spec_cascade.rb +45 -0
- data/test/spec_cgi.rb +102 -0
- data/test/spec_chunked.rb +60 -0
- data/test/spec_commonlogger.rb +56 -0
- data/test/spec_conditionalget.rb +86 -0
- data/test/spec_config.rb +23 -0
- data/test/spec_content_length.rb +36 -0
- data/test/spec_content_type.rb +29 -0
- data/test/spec_deflater.rb +125 -0
- data/test/spec_directory.rb +57 -0
- data/test/spec_etag.rb +75 -0
- data/test/spec_fastcgi.rb +107 -0
- data/test/spec_file.rb +92 -0
- data/test/spec_handler.rb +49 -0
- data/test/spec_head.rb +30 -0
- data/test/spec_lint.rb +515 -0
- data/test/spec_lobster.rb +43 -0
- data/test/spec_lock.rb +142 -0
- data/test/spec_logger.rb +28 -0
- data/test/spec_methodoverride.rb +58 -0
- data/test/spec_mock.rb +241 -0
- data/test/spec_mongrel.rb +182 -0
- data/test/spec_nulllogger.rb +12 -0
- data/test/spec_recursive.rb +69 -0
- data/test/spec_request.rb +774 -0
- data/test/spec_response.rb +245 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +39 -0
- data/test/spec_sendfile.rb +83 -0
- data/test/spec_server.rb +8 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +171 -0
- data/test/spec_session_memcache.rb +289 -0
- data/test/spec_session_pool.rb +200 -0
- data/test/spec_showexceptions.rb +87 -0
- data/test/spec_showstatus.rb +79 -0
- data/test/spec_static.rb +48 -0
- data/test/spec_thin.rb +86 -0
- data/test/spec_urlmap.rb +213 -0
- data/test/spec_utils.rb +678 -0
- data/test/spec_webrick.rb +141 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +329 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'rack/auth/abstract/handler'
|
|
2
|
+
require 'rack/auth/abstract/request'
|
|
3
|
+
|
|
4
|
+
module Rack
|
|
5
|
+
module Auth
|
|
6
|
+
# Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.
|
|
7
|
+
#
|
|
8
|
+
# Initialize with the Rack application that you want protecting,
|
|
9
|
+
# and a block that checks if a username and password pair are valid.
|
|
10
|
+
#
|
|
11
|
+
# See also: <tt>example/protectedlobster.rb</tt>
|
|
12
|
+
|
|
13
|
+
class Basic < AbstractHandler
|
|
14
|
+
|
|
15
|
+
def call(env)
|
|
16
|
+
auth = Basic::Request.new(env)
|
|
17
|
+
|
|
18
|
+
return unauthorized unless auth.provided?
|
|
19
|
+
|
|
20
|
+
return bad_request unless auth.basic?
|
|
21
|
+
|
|
22
|
+
if valid?(auth)
|
|
23
|
+
env['REMOTE_USER'] = auth.username
|
|
24
|
+
|
|
25
|
+
return @app.call(env)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unauthorized
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def challenge
|
|
35
|
+
'Basic realm="%s"' % realm
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def valid?(auth)
|
|
39
|
+
@authenticator.call(*auth.credentials)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Request < Auth::AbstractRequest
|
|
43
|
+
def basic?
|
|
44
|
+
:basic == scheme
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def credentials
|
|
48
|
+
@credentials ||= params.unpack("m*").first.split(/:/, 2)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def username
|
|
52
|
+
credentials.first
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'rack/auth/abstract/handler'
|
|
2
|
+
require 'rack/auth/digest/request'
|
|
3
|
+
require 'rack/auth/digest/params'
|
|
4
|
+
require 'rack/auth/digest/nonce'
|
|
5
|
+
require 'digest/md5'
|
|
6
|
+
|
|
7
|
+
module Rack
|
|
8
|
+
module Auth
|
|
9
|
+
module Digest
|
|
10
|
+
# Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
|
|
11
|
+
# HTTP Digest Authentication, as per RFC 2617.
|
|
12
|
+
#
|
|
13
|
+
# Initialize with the [Rack] application that you want protecting,
|
|
14
|
+
# and a block that looks up a plaintext password for a given username.
|
|
15
|
+
#
|
|
16
|
+
# +opaque+ needs to be set to a constant base64/hexadecimal string.
|
|
17
|
+
#
|
|
18
|
+
class MD5 < AbstractHandler
|
|
19
|
+
|
|
20
|
+
attr_accessor :opaque
|
|
21
|
+
|
|
22
|
+
attr_writer :passwords_hashed
|
|
23
|
+
|
|
24
|
+
def initialize(*args)
|
|
25
|
+
super
|
|
26
|
+
@passwords_hashed = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def passwords_hashed?
|
|
30
|
+
!!@passwords_hashed
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call(env)
|
|
34
|
+
auth = Request.new(env)
|
|
35
|
+
|
|
36
|
+
unless auth.provided?
|
|
37
|
+
return unauthorized
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
|
|
41
|
+
return bad_request
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if valid?(auth)
|
|
45
|
+
if auth.nonce.stale?
|
|
46
|
+
return unauthorized(challenge(:stale => true))
|
|
47
|
+
else
|
|
48
|
+
env['REMOTE_USER'] = auth.username
|
|
49
|
+
|
|
50
|
+
return @app.call(env)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
unauthorized
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
QOP = 'auth'.freeze
|
|
61
|
+
|
|
62
|
+
def params(hash = {})
|
|
63
|
+
Params.new do |params|
|
|
64
|
+
params['realm'] = realm
|
|
65
|
+
params['nonce'] = Nonce.new.to_s
|
|
66
|
+
params['opaque'] = H(opaque)
|
|
67
|
+
params['qop'] = QOP
|
|
68
|
+
|
|
69
|
+
hash.each { |k, v| params[k] = v }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def challenge(hash = {})
|
|
74
|
+
"Digest #{params(hash)}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def valid?(auth)
|
|
78
|
+
valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def valid_qop?(auth)
|
|
82
|
+
QOP == auth.qop
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def valid_opaque?(auth)
|
|
86
|
+
H(opaque) == auth.opaque
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def valid_nonce?(auth)
|
|
90
|
+
auth.nonce.valid?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def valid_digest?(auth)
|
|
94
|
+
digest(auth, @authenticator.call(auth.username)) == auth.response
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def md5(data)
|
|
98
|
+
::Digest::MD5.hexdigest(data)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
alias :H :md5
|
|
102
|
+
|
|
103
|
+
def KD(secret, data)
|
|
104
|
+
H([secret, data] * ':')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def A1(auth, password)
|
|
108
|
+
[ auth.username, auth.realm, password ] * ':'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def A2(auth)
|
|
112
|
+
[ auth.method, auth.uri ] * ':'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def digest(auth, password)
|
|
116
|
+
password_hash = passwords_hashed? ? password : H(A1(auth, password))
|
|
117
|
+
|
|
118
|
+
KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
module Auth
|
|
5
|
+
module Digest
|
|
6
|
+
# Rack::Auth::Digest::Nonce is the default nonce generator for the
|
|
7
|
+
# Rack::Auth::Digest::MD5 authentication handler.
|
|
8
|
+
#
|
|
9
|
+
# +private_key+ needs to set to a constant string.
|
|
10
|
+
#
|
|
11
|
+
# +time_limit+ can be optionally set to an integer (number of seconds),
|
|
12
|
+
# to limit the validity of the generated nonces.
|
|
13
|
+
|
|
14
|
+
class Nonce
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
attr_accessor :private_key, :time_limit
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.parse(string)
|
|
21
|
+
new(*string.unpack("m*").first.split(' ', 2))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(timestamp = Time.now, given_digest = nil)
|
|
25
|
+
@timestamp, @given_digest = timestamp.to_i, given_digest
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
[([ @timestamp, digest ] * ' ')].pack("m*").strip
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def digest
|
|
33
|
+
::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def valid?
|
|
37
|
+
digest == @given_digest
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def stale?
|
|
41
|
+
!self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def fresh?
|
|
45
|
+
!stale?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
module Auth
|
|
3
|
+
module Digest
|
|
4
|
+
class Params < Hash
|
|
5
|
+
|
|
6
|
+
def self.parse(str)
|
|
7
|
+
Params[*split_header_value(str).map do |param|
|
|
8
|
+
k, v = param.split('=', 2)
|
|
9
|
+
[k, dequote(v)]
|
|
10
|
+
end.flatten]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.dequote(str) # From WEBrick::HTTPUtils
|
|
14
|
+
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
|
15
|
+
ret.gsub!(/\\(.)/, "\\1")
|
|
16
|
+
ret
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.split_header_value(str)
|
|
20
|
+
str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
super
|
|
25
|
+
|
|
26
|
+
yield self if block_given?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def [](k)
|
|
30
|
+
super k.to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def []=(k, v)
|
|
34
|
+
super k.to_s, v.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
UNQUOTED = ['nc', 'stale']
|
|
38
|
+
|
|
39
|
+
def to_s
|
|
40
|
+
map do |k, v|
|
|
41
|
+
"#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
|
|
42
|
+
end.join(', ')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def quote(str) # From WEBrick::HTTPUtils
|
|
46
|
+
'"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'rack/auth/abstract/request'
|
|
2
|
+
require 'rack/auth/digest/params'
|
|
3
|
+
require 'rack/auth/digest/nonce'
|
|
4
|
+
|
|
5
|
+
module Rack
|
|
6
|
+
module Auth
|
|
7
|
+
module Digest
|
|
8
|
+
class Request < Auth::AbstractRequest
|
|
9
|
+
|
|
10
|
+
def method
|
|
11
|
+
@env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD']
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def digest?
|
|
15
|
+
:digest == scheme
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def correct_uri?
|
|
19
|
+
request.fullpath == uri
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def nonce
|
|
23
|
+
@nonce ||= Nonce.parse(params['nonce'])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def params
|
|
27
|
+
@params ||= Params.parse(parts.last)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def method_missing(sym)
|
|
31
|
+
if params.has_key? key = sym.to_s
|
|
32
|
+
return params[key]
|
|
33
|
+
end
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/rack/builder.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
# Rack::Builder implements a small DSL to iteratively construct Rack
|
|
3
|
+
# applications.
|
|
4
|
+
#
|
|
5
|
+
# Example:
|
|
6
|
+
#
|
|
7
|
+
# app = Rack::Builder.new {
|
|
8
|
+
# use Rack::CommonLogger
|
|
9
|
+
# use Rack::ShowExceptions
|
|
10
|
+
# map "/lobster" do
|
|
11
|
+
# use Rack::Lint
|
|
12
|
+
# run Rack::Lobster.new
|
|
13
|
+
# end
|
|
14
|
+
# }
|
|
15
|
+
#
|
|
16
|
+
# Or
|
|
17
|
+
#
|
|
18
|
+
# app = Rack::Builder.app do
|
|
19
|
+
# use Rack::CommonLogger
|
|
20
|
+
# lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# +use+ adds a middleware to the stack, +run+ dispatches to an application.
|
|
24
|
+
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
|
25
|
+
|
|
26
|
+
class Builder
|
|
27
|
+
def self.parse_file(config, opts = Server::Options.new)
|
|
28
|
+
options = {}
|
|
29
|
+
if config =~ /\.ru$/
|
|
30
|
+
cfgfile = ::File.read(config)
|
|
31
|
+
if cfgfile[/^#\\(.*)/] && opts
|
|
32
|
+
options = opts.parse! $1.split(/\s+/)
|
|
33
|
+
end
|
|
34
|
+
cfgfile.sub!(/^__END__\n.*/, '')
|
|
35
|
+
app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
|
|
36
|
+
TOPLEVEL_BINDING, config
|
|
37
|
+
else
|
|
38
|
+
require config
|
|
39
|
+
app = Object.const_get(::File.basename(config, '.rb').capitalize)
|
|
40
|
+
end
|
|
41
|
+
return app, options
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def initialize(&block)
|
|
45
|
+
@ins = []
|
|
46
|
+
instance_eval(&block) if block_given?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.app(&block)
|
|
50
|
+
self.new(&block).to_app
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def use(middleware, *args, &block)
|
|
54
|
+
@ins << lambda { |app| middleware.new(app, *args, &block) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run(app)
|
|
58
|
+
@ins << app #lambda { |nothing| app }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def map(path, &block)
|
|
62
|
+
if @ins.last.kind_of? Hash
|
|
63
|
+
@ins.last[path] = self.class.new(&block).to_app
|
|
64
|
+
else
|
|
65
|
+
@ins << {}
|
|
66
|
+
map(path, &block)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_app
|
|
71
|
+
@ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
|
|
72
|
+
inner_app = @ins.last
|
|
73
|
+
@ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def call(env)
|
|
77
|
+
to_app.call(env)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/rack/cascade.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
# Rack::Cascade tries an request on several apps, and returns the
|
|
3
|
+
# first response that is not 404 (or in a list of configurable
|
|
4
|
+
# status codes).
|
|
5
|
+
|
|
6
|
+
class Cascade
|
|
7
|
+
NotFound = [404, {}, []]
|
|
8
|
+
|
|
9
|
+
attr_reader :apps
|
|
10
|
+
|
|
11
|
+
def initialize(apps, catch=404)
|
|
12
|
+
@apps = []; @has_app = {}
|
|
13
|
+
apps.each { |app| add app }
|
|
14
|
+
|
|
15
|
+
@catch = {}
|
|
16
|
+
[*catch].each { |status| @catch[status] = true }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(env)
|
|
20
|
+
result = NotFound
|
|
21
|
+
|
|
22
|
+
@apps.each do |app|
|
|
23
|
+
result = app.call(env)
|
|
24
|
+
break unless @catch.include?(result[0].to_i)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add app
|
|
31
|
+
@has_app[app] = true
|
|
32
|
+
@apps << app
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def include? app
|
|
36
|
+
@has_app.include? app
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
alias_method :<<, :add
|
|
40
|
+
end
|
|
41
|
+
end
|