rack-contrib 0.9.2 → 1.0.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.
Potentially problematic release.
This version of rack-contrib might be problematic. Click here for more details.
- data/AUTHORS +26 -0
- data/README.rdoc +7 -3
- data/Rakefile +2 -10
- data/lib/rack/contrib.rb +6 -0
- data/lib/rack/contrib/access.rb +85 -0
- data/lib/rack/contrib/cookies.rb +50 -0
- data/lib/rack/contrib/expectation_cascade.rb +32 -0
- data/lib/rack/contrib/host_meta.rb +47 -0
- data/lib/rack/contrib/jsonp.rb +1 -1
- data/lib/rack/contrib/mailexceptions.rb +1 -1
- data/lib/rack/contrib/profiler.rb +3 -1
- data/lib/rack/contrib/response_headers.rb +24 -0
- data/lib/rack/contrib/runtime.rb +31 -0
- data/lib/rack/contrib/simple_endpoint.rb +81 -0
- data/lib/rack/contrib/static_cache.rb +93 -0
- data/rack-contrib.gemspec +22 -5
- data/test/documents/test +1 -0
- data/test/spec_rack_access.rb +154 -0
- data/test/spec_rack_cookies.rb +56 -0
- data/test/spec_rack_expectation_cascade.rb +72 -0
- data/test/spec_rack_host_meta.rb +50 -0
- data/test/spec_rack_jsonp.rb +5 -5
- data/test/spec_rack_post_body_content_type_parser.rb +1 -1
- data/test/spec_rack_profiler.rb +1 -1
- data/test/spec_rack_response_headers.rb +35 -0
- data/test/spec_rack_runtime.rb +35 -0
- data/test/spec_rack_simple_endpoint.rb +95 -0
- data/test/spec_rack_static_cache.rb +91 -0
- data/test/statics/test +1 -0
- metadata +72 -25
- data/lib/rack/contrib/etag.rb +0 -20
- data/test/spec_rack_etag.rb +0 -23
data/AUTHORS
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Ryan Tomayko <rtomayko@gmail.com>
|
2
|
+
Joshua Peek <josh@joshpeek.com>
|
3
|
+
Jeremy Kemper <jeremy@bitsweat.net>
|
4
|
+
mynyml <mynyml@gmail.com>
|
5
|
+
Cameron Walters <cameron.walters@gmail.com>
|
6
|
+
Jon Crosby <jon@joncrosby.me>
|
7
|
+
Matt Todd <chiology@gmail.com>
|
8
|
+
Pirmin Kalberer <pka@sourcepole.ch>
|
9
|
+
Rune Botten <rbotten@gmail.com>
|
10
|
+
Pratik Naik <pratiknaik@gmail.com>
|
11
|
+
Paul Sadauskas <psadauskas@gmail.com>
|
12
|
+
Jeremy Evans <code@jeremyevans.net>
|
13
|
+
Michael Fellinger <m.fellinger@gmail.com>
|
14
|
+
Geoff Buesing <gbuesing@gmail.com>
|
15
|
+
Nicolas Mérouze <nicolas.merouze@gmail.com>
|
16
|
+
Cyril Rohr <cyril.rohr@irisa.fr>
|
17
|
+
Harry Vangberg <harry@vangberg.name>
|
18
|
+
James Rosen <jrosen@mitre.org>
|
19
|
+
Mislav Marohnić <mislav.marohnic@gmail.com>
|
20
|
+
Ben Brinckerhoff <ben@devver.net>
|
21
|
+
Rafael Souza <rafael.ssouza@gmail.com>
|
22
|
+
Stephen Delano <sdelano@sdelano-air.(none)>
|
23
|
+
TJ Holowaychuk <tj@vision-media.ca>
|
24
|
+
anupom syam <anupom.syam@gmail.com>
|
25
|
+
ichverstehe <ichverstehe@gmail.com>
|
26
|
+
kubicek <jiri@kubicek.cz>
|
data/README.rdoc
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
This package includes a variety of add-on components for Rack, a Ruby web server
|
4
4
|
interface:
|
5
5
|
|
6
|
-
* Rack::ETag - Automatically sets the ETag header on all String bodies.
|
7
6
|
* Rack::JSONP - Adds JSON-P support by stripping out the callback param
|
8
7
|
and padding the response with the appropriate callback format.
|
9
8
|
* Rack::LighttpdScriptNameFix - Fixes how lighttpd sets the SCRIPT_NAME
|
@@ -25,6 +24,8 @@ interface:
|
|
25
24
|
from file.
|
26
25
|
* Rack::Signals - Installs signal handlers that are safely processed after
|
27
26
|
a request
|
27
|
+
* Rack::StaticCache - Modifies the response headers to facilitiate client and proxy caching for
|
28
|
+
static files that minimizes http requests and improves overall load times for second time visitors.
|
28
29
|
* Rack::TimeZone - Detects the clients timezone using JavaScript and sets
|
29
30
|
a variable in Rack's environment with the offset from UTC.
|
30
31
|
* Rack::Evil - Lets the rack application return a response to the client from any place.
|
@@ -43,6 +44,9 @@ interface:
|
|
43
44
|
* Rack::AcceptFormat - Adds a format extension at the end of the URI when there is none, corresponding to the mime-type given in the Accept HTTP header.
|
44
45
|
* Rack::HostMeta - Configures /host-meta using a block
|
45
46
|
* Rack::Cookies - Adds simple cookie jar hash to env
|
47
|
+
* Rack::Access - Limit access based on IP address
|
48
|
+
* Rack::ResponseHeaders - Manipulate response headers object at runtime
|
49
|
+
* Rack::SimpleEndpoint - Create simple endpoints with routing rules, similar to Sinatra actions
|
46
50
|
|
47
51
|
=== Use
|
48
52
|
|
@@ -50,9 +54,9 @@ Git is the quickest way to the rack-contrib sources:
|
|
50
54
|
|
51
55
|
git clone git://github.com/rack/rack-contrib.git
|
52
56
|
|
53
|
-
Gems are
|
57
|
+
Gems are available too:
|
54
58
|
|
55
|
-
gem install rack-
|
59
|
+
gem install rack-contrib
|
56
60
|
|
57
61
|
Requiring 'rack/contrib' will add autoloads to the Rack modules for all of the
|
58
62
|
components included. The following example shows what a simple rackup
|
data/Rakefile
CHANGED
@@ -42,8 +42,8 @@ task :rdoc => ["RDOX"]
|
|
42
42
|
|
43
43
|
# PACKAGING =================================================================
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
require 'rubygems'
|
46
|
+
# load gemspec
|
47
47
|
$spec = eval(File.read('rack-contrib.gemspec'))
|
48
48
|
|
49
49
|
def package(ext='')
|
@@ -69,14 +69,6 @@ file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
|
|
69
69
|
sh "git archive --format=tar HEAD | gzip > #{f.name}"
|
70
70
|
end
|
71
71
|
|
72
|
-
desc 'Publish gem and tarball to rubyforge'
|
73
|
-
task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
|
74
|
-
sh <<-end
|
75
|
-
rubyforge add_release rack rack-contrib #{$spec.version} #{package('.gem')} &&
|
76
|
-
rubyforge add_file rack rack-contrib #{$spec.version} #{package('.tar.gz')}
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
72
|
# GEMSPEC ===================================================================
|
81
73
|
|
82
74
|
file 'rack-contrib.gemspec' => FileList['{lib,test}/**','Rakefile', 'README.rdoc'] do |f|
|
data/lib/rack/contrib.rb
CHANGED
@@ -8,11 +8,13 @@ module Rack
|
|
8
8
|
end
|
9
9
|
|
10
10
|
autoload :AcceptFormat, "rack/contrib/accept_format"
|
11
|
+
autoload :Access, "rack/contrib/access"
|
11
12
|
autoload :BounceFavicon, "rack/contrib/bounce_favicon"
|
12
13
|
autoload :Cookies, "rack/contrib/cookies"
|
13
14
|
autoload :CSSHTTPRequest, "rack/contrib/csshttprequest"
|
14
15
|
autoload :Deflect, "rack/contrib/deflect"
|
15
16
|
autoload :ETag, "rack/contrib/etag"
|
17
|
+
autoload :ExpectationCascade, "rack/contrib/expectation_cascade"
|
16
18
|
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
17
19
|
autoload :JSONP, "rack/contrib/jsonp"
|
18
20
|
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
@@ -21,8 +23,11 @@ module Rack
|
|
21
23
|
autoload :PostBodyContentTypeParser, "rack/contrib/post_body_content_type_parser"
|
22
24
|
autoload :ProcTitle, "rack/contrib/proctitle"
|
23
25
|
autoload :Profiler, "rack/contrib/profiler"
|
26
|
+
autoload :ResponseHeaders, "rack/contrib/response_headers"
|
27
|
+
autoload :Runtime, "rack/contrib/runtime"
|
24
28
|
autoload :Sendfile, "rack/contrib/sendfile"
|
25
29
|
autoload :Signals, "rack/contrib/signals"
|
30
|
+
autoload :SimpleEndpoint, "rack/contrib/simple_endpoint"
|
26
31
|
autoload :TimeZone, "rack/contrib/time_zone"
|
27
32
|
autoload :Evil, "rack/contrib/evil"
|
28
33
|
autoload :Callbacks, "rack/contrib/callbacks"
|
@@ -31,4 +36,5 @@ module Rack
|
|
31
36
|
autoload :NotFound, "rack/contrib/not_found"
|
32
37
|
autoload :ResponseCache, "rack/contrib/response_cache"
|
33
38
|
autoload :RelativeRedirect, "rack/contrib/relative_redirect"
|
39
|
+
autoload :StaticCache, "rack/contrib/static_cache"
|
34
40
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "ipaddr"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
##
|
6
|
+
# Rack middleware for limiting access based on IP address
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# === Options:
|
10
|
+
#
|
11
|
+
# path => ipmasks ipmasks: Array of remote addresses which are allowed to access
|
12
|
+
#
|
13
|
+
# === Examples:
|
14
|
+
#
|
15
|
+
# use Rack::Access, '/backend' => [ '127.0.0.1', '192.168.1.0/24' ]
|
16
|
+
#
|
17
|
+
#
|
18
|
+
|
19
|
+
class Access
|
20
|
+
|
21
|
+
attr_reader :options
|
22
|
+
|
23
|
+
def initialize(app, options = {})
|
24
|
+
@app = app
|
25
|
+
mapping = options.empty? ? {"/" => ["127.0.0.1"]} : options
|
26
|
+
@mapping = remap(mapping)
|
27
|
+
end
|
28
|
+
|
29
|
+
def remap(mapping)
|
30
|
+
mapping.map { |location, ipmasks|
|
31
|
+
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
32
|
+
host, location = $1, $2
|
33
|
+
else
|
34
|
+
host = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
unless location[0] == ?/
|
38
|
+
raise ArgumentError, "paths need to start with /"
|
39
|
+
end
|
40
|
+
location = location.chomp('/')
|
41
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
|
42
|
+
|
43
|
+
ipmasks.collect! do |ipmask|
|
44
|
+
ipmask.is_a?(IPAddr) ? ipmask : IPAddr.new(ipmask)
|
45
|
+
end
|
46
|
+
[host, location, match, ipmasks]
|
47
|
+
}.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(env)
|
51
|
+
@original_request = Request.new(env)
|
52
|
+
ipmasks = ipmasks_for_path(env)
|
53
|
+
return forbidden! unless ip_authorized?(ipmasks)
|
54
|
+
status, headers, body = @app.call(env)
|
55
|
+
[status, headers, body]
|
56
|
+
end
|
57
|
+
|
58
|
+
def ipmasks_for_path(env)
|
59
|
+
path = env["PATH_INFO"].to_s
|
60
|
+
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
|
61
|
+
@mapping.each do |host, location, match, ipmasks|
|
62
|
+
next unless (hHost == host || sName == host \
|
63
|
+
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
|
64
|
+
next unless path =~ match && rest = $1
|
65
|
+
next unless rest.empty? || rest[0] == ?/
|
66
|
+
|
67
|
+
return ipmasks
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def forbidden!
|
73
|
+
[403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, '']
|
74
|
+
end
|
75
|
+
|
76
|
+
def ip_authorized?(ipmasks)
|
77
|
+
return true if ipmasks.nil?
|
78
|
+
|
79
|
+
ipmasks.any? do |ip_mask|
|
80
|
+
ip_mask.include?(IPAddr.new(@original_request.ip))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Rack
|
2
|
+
class Cookies
|
3
|
+
class CookieJar < Hash
|
4
|
+
def initialize(cookies)
|
5
|
+
@set_cookies = {}
|
6
|
+
@delete_cookies = {}
|
7
|
+
super()
|
8
|
+
update(cookies)
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](name)
|
12
|
+
super(name.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key, options)
|
16
|
+
unless options.is_a?(Hash)
|
17
|
+
options = { :value => options }
|
18
|
+
end
|
19
|
+
|
20
|
+
options[:path] ||= '/'
|
21
|
+
@set_cookies[key] = options
|
22
|
+
super(key.to_s, options[:value])
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key, options = {})
|
26
|
+
options[:path] ||= '/'
|
27
|
+
@delete_cookies[key] = options
|
28
|
+
super(key.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def finish!(resp)
|
32
|
+
@set_cookies.each { |k, v| resp.set_cookie(k, v) }
|
33
|
+
@delete_cookies.each { |k, v| resp.delete_cookie(k, v) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(app)
|
38
|
+
@app = app
|
39
|
+
end
|
40
|
+
|
41
|
+
def call(env)
|
42
|
+
req = Request.new(env)
|
43
|
+
env['rack.cookies'] = cookies = CookieJar.new(req.cookies)
|
44
|
+
status, headers, body = @app.call(env)
|
45
|
+
resp = Response.new(body, status, headers)
|
46
|
+
cookies.finish!(resp)
|
47
|
+
resp.to_a
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rack
|
2
|
+
class ExpectationCascade
|
3
|
+
Expect = "Expect".freeze
|
4
|
+
ContinueExpectation = "100-continue".freeze
|
5
|
+
|
6
|
+
ExpectationFailed = [417, {"Content-Type" => "text/html"}, []].freeze
|
7
|
+
NotFound = [404, {"Content-Type" => "text/html"}, []].freeze
|
8
|
+
|
9
|
+
attr_reader :apps
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@apps = []
|
13
|
+
yield self if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
set_expectation = env[Expect] != ContinueExpectation
|
18
|
+
env[Expect] = ContinueExpectation if set_expectation
|
19
|
+
@apps.each do |app|
|
20
|
+
result = app.call(env)
|
21
|
+
return result unless result[0].to_i == 417
|
22
|
+
end
|
23
|
+
set_expectation ? NotFound : ExpectationFailed
|
24
|
+
ensure
|
25
|
+
env.delete(Expect) if set_expectation
|
26
|
+
end
|
27
|
+
|
28
|
+
def <<(app)
|
29
|
+
@apps << app
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rack
|
2
|
+
|
3
|
+
# Rack middleware implementing the IETF draft: "Host Metadata for the Web"
|
4
|
+
# including support for Link-Pattern elements as described in the IETF draft:
|
5
|
+
# "Link-based Resource Descriptor Discovery."
|
6
|
+
#
|
7
|
+
# Usage:
|
8
|
+
# use Rack::HostMeta do
|
9
|
+
# link :uri => '/robots.txt', :rel => 'robots'
|
10
|
+
# link :uri => '/w3c/p3p.xml', :rel => 'privacy', :type => 'application/p3p.xml'
|
11
|
+
# link :pattern => '{uri};json_schema', :rel => 'describedby', :type => 'application/x-schema+json'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# See also:
|
15
|
+
# http://tools.ietf.org/html/draft-nottingham-site-meta
|
16
|
+
# http://tools.ietf.org/html/draft-hammer-discovery
|
17
|
+
#
|
18
|
+
# TODO:
|
19
|
+
# Accept POST operations allowing downstream services to register themselves
|
20
|
+
#
|
21
|
+
class HostMeta
|
22
|
+
def initialize(app, &block)
|
23
|
+
@app = app
|
24
|
+
@lines = []
|
25
|
+
instance_eval(&block)
|
26
|
+
@response = @lines.join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
if env['PATH_INFO'] == '/host-meta'
|
31
|
+
[200, {'Content-Type' => 'application/host-meta'}, [@response]]
|
32
|
+
else
|
33
|
+
@app.call(env)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def link(config)
|
40
|
+
line = config[:uri] ? "Link: <#{config[:uri]}>;" : "Link-Pattern: <#{config[:pattern]}>;"
|
41
|
+
fragments = []
|
42
|
+
fragments << "rel=\"#{config[:rel]}\"" if config[:rel]
|
43
|
+
fragments << "type=\"#{config[:type]}\"" if config[:type]
|
44
|
+
@lines << "#{line} #{fragments.join("; ")}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/rack/contrib/jsonp.rb
CHANGED
@@ -22,7 +22,7 @@ module Rack
|
|
22
22
|
response = pad(request.params.delete('callback'), response)
|
23
23
|
headers['Content-Length'] = response.length.to_s
|
24
24
|
end
|
25
|
-
[status, headers,
|
25
|
+
[status, headers, response]
|
26
26
|
end
|
27
27
|
|
28
28
|
# Pads the response with the appropriate callback format according to the
|
@@ -70,7 +70,7 @@ module Rack
|
|
70
70
|
env['mail.sent'] = true
|
71
71
|
return if smtp[:server] == 'example.com'
|
72
72
|
|
73
|
-
Net::SMTP.start smtp[:
|
73
|
+
Net::SMTP.start smtp[:server], smtp[:port], smtp[:domain], smtp[:user_name], smtp[:password], smtp[:authentication] do |server|
|
74
74
|
mail.to.each do |recipient|
|
75
75
|
server.send_message mail.to_s, mail.from, recipient
|
76
76
|
end
|
@@ -44,7 +44,7 @@ module Rack
|
|
44
44
|
private
|
45
45
|
def profiling?(env)
|
46
46
|
unless RubyProf.running?
|
47
|
-
request = Rack::Request.new(env)
|
47
|
+
request = Rack::Request.new(env.clone)
|
48
48
|
if mode = request.params.delete('profile')
|
49
49
|
if RubyProf.const_defined?(mode.upcase)
|
50
50
|
mode
|
@@ -60,9 +60,11 @@ module Rack
|
|
60
60
|
def profile(env, mode)
|
61
61
|
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
62
62
|
|
63
|
+
GC.enable_stats if GC.respond_to?(:enable_stats)
|
63
64
|
result = RubyProf.profile do
|
64
65
|
@times.times { @app.call(env) }
|
65
66
|
end
|
67
|
+
GC.disable_stats if GC.respond_to?(:disable_stats)
|
66
68
|
|
67
69
|
[200, headers(@printer, env, mode), print(@printer, result)]
|
68
70
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rack
|
2
|
+
# Allows you to tap into the response headers. Yields a Rack::Utils::HeaderHash
|
3
|
+
# of current response headers to the block. Example:
|
4
|
+
#
|
5
|
+
# use Rack::ResponseHeaders do |headers|
|
6
|
+
# headers['X-Foo'] = 'bar'
|
7
|
+
# headers.delete('X-Baz')
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
class ResponseHeaders
|
11
|
+
def initialize(app, &block)
|
12
|
+
@app = app
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
response = @app.call(env)
|
18
|
+
headers = Utils::HeaderHash.new(response[1])
|
19
|
+
@block.call(headers)
|
20
|
+
response[1] = headers
|
21
|
+
response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module Rack
|
3
|
+
# Sets an "X-Runtime" response header, indicating the response
|
4
|
+
# time of the request, in seconds
|
5
|
+
#
|
6
|
+
# You can put it right before the application to see the processing
|
7
|
+
# time, or before all the other middlewares to include time for them,
|
8
|
+
# too.
|
9
|
+
class Runtime
|
10
|
+
def initialize(app, name = nil)
|
11
|
+
@app = app
|
12
|
+
@header_name = "X-Runtime"
|
13
|
+
@header_name << "-#{name}" if name
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
start_time = Time.now
|
18
|
+
status, headers, body = @app.call(env)
|
19
|
+
request_time = Time.now - start_time
|
20
|
+
|
21
|
+
if !headers.has_key?(@header_name)
|
22
|
+
headers[@header_name] = "%0.6f" % request_time
|
23
|
+
end
|
24
|
+
|
25
|
+
[status, headers, body]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Rack
|
2
|
+
# Create simple endpoints with routing rules, similar to Sinatra actions.
|
3
|
+
#
|
4
|
+
# Simplest example:
|
5
|
+
#
|
6
|
+
# use Rack::SimpleEndpoint, '/ping_monitor' do
|
7
|
+
# 'pong'
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# The value returned from the block will be written to the response body, so
|
11
|
+
# the above example will return "pong" when the request path is /ping_monitor.
|
12
|
+
#
|
13
|
+
# HTTP verb requirements can optionally be specified:
|
14
|
+
#
|
15
|
+
# use Rack::SimpleEndpoint, '/foo' => :get do
|
16
|
+
# 'only GET requests will match'
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# use Rack::SimpleEndpoint, '/bar' => [:get, :post] do
|
20
|
+
# 'only GET and POST requests will match'
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Rack::Request and Rack::Response objects are yielded to block:
|
24
|
+
#
|
25
|
+
# use Rack::SimpleEndpoint, '/json' do |req, res|
|
26
|
+
# res['Content-Type'] = 'application/json'
|
27
|
+
# %({"foo": "#{req[:foo]}"})
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# When path is a Regexp, match data object is yielded as third argument to block
|
31
|
+
#
|
32
|
+
# use Rack::SimpleEndpoint, %r{^/(john|paul|george|ringo)} do |req, res, match|
|
33
|
+
# "Hello, #{match[1]}"
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# A :pass symbol returned from block will not return a response; control will continue down the
|
37
|
+
# Rack stack:
|
38
|
+
#
|
39
|
+
# use Rack::SimpleEndpoint, '/api_key' do |req, res|
|
40
|
+
# req.env['myapp.user'].authorized? ? '12345' : :pass
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # Unauthorized access to /api_key will be handled by PublicApp
|
44
|
+
# run PublicApp
|
45
|
+
class SimpleEndpoint
|
46
|
+
def initialize(app, arg, &block)
|
47
|
+
@app = app
|
48
|
+
@path = extract_path(arg)
|
49
|
+
@verbs = extract_verbs(arg)
|
50
|
+
@block = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(env)
|
54
|
+
match = match_path(env['PATH_INFO'])
|
55
|
+
if match && valid_method?(env['REQUEST_METHOD'])
|
56
|
+
req, res = Request.new(env), Response.new
|
57
|
+
body = @block.call(req, res, (match unless match == true))
|
58
|
+
body == :pass ? @app.call(env) : (res.write(body); res.finish)
|
59
|
+
else
|
60
|
+
@app.call(env)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def extract_path(arg)
|
66
|
+
arg.is_a?(Hash) ? arg.keys.first : arg
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_verbs(arg)
|
70
|
+
arg.is_a?(Hash) ? [arg.values.first].flatten.map {|verb| verb.to_s.upcase} : []
|
71
|
+
end
|
72
|
+
|
73
|
+
def match_path(path)
|
74
|
+
@path.is_a?(Regexp) ? @path.match(path.to_s) : @path == path.to_s
|
75
|
+
end
|
76
|
+
|
77
|
+
def valid_method?(method)
|
78
|
+
@verbs.empty? || @verbs.include?(method)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|