rack-contrib 0.9.0 → 0.9.2
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/README.rdoc +16 -0
- data/Rakefile +6 -1
- data/lib/rack/contrib.rb +10 -1
- data/lib/rack/contrib/accept_format.rb +46 -0
- data/lib/rack/contrib/backstage.rb +20 -0
- data/lib/rack/contrib/config.rb +16 -0
- data/lib/rack/contrib/csshttprequest.rb +39 -0
- data/lib/rack/contrib/deflect.rb +137 -0
- data/lib/rack/contrib/jsonp.rb +18 -15
- data/lib/rack/contrib/not_found.rb +18 -0
- data/lib/rack/contrib/post_body_content_type_parser.rb +10 -10
- data/lib/rack/contrib/relative_redirect.rb +44 -0
- data/lib/rack/contrib/response_cache.rb +59 -0
- data/lib/rack/contrib/route_exceptions.rb +20 -19
- data/lib/rack/contrib/signals.rb +63 -0
- data/rack-contrib.gemspec +22 -3
- data/test/404.html +1 -0
- data/test/Maintenance.html +1 -0
- data/test/spec_rack_accept_format.rb +72 -0
- data/test/spec_rack_backstage.rb +26 -0
- data/test/spec_rack_callbacks.rb +2 -2
- data/test/spec_rack_config.rb +22 -0
- data/test/spec_rack_csshttprequest.rb +66 -0
- data/test/spec_rack_deflect.rb +107 -0
- data/test/spec_rack_jsonp.rb +23 -10
- data/test/spec_rack_lighttpd_script_name_fix.rb +1 -1
- data/test/spec_rack_not_found.rb +17 -0
- data/test/spec_rack_post_body_content_type_parser.rb +5 -5
- data/test/spec_rack_profiler.rb +31 -26
- data/test/spec_rack_relative_redirect.rb +78 -0
- data/test/spec_rack_response_cache.rb +137 -0
- metadata +30 -3
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
# Rack::RelativeRedirect is a simple middleware that converts relative paths in
|
4
|
+
# redirects in absolute urls, so they conform to RFC2616. It allows the user to
|
5
|
+
# specify the absolute path to use (with a sensible default), and handles
|
6
|
+
# relative paths (those that don't start with a slash) as well.
|
7
|
+
class Rack::RelativeRedirect
|
8
|
+
SCHEME_MAP = {'http'=>'80', 'https'=>'443'}
|
9
|
+
# The default proc used if a block is not provided to .new
|
10
|
+
# Just uses the url scheme of the request and the server name.
|
11
|
+
DEFAULT_ABSOLUTE_PROC = proc do |env, res|
|
12
|
+
port = env['SERVER_PORT']
|
13
|
+
scheme = env['rack.url_scheme']
|
14
|
+
"#{scheme}://#{env['SERVER_NAME']}#{":#{port}" unless SCHEME_MAP[scheme] == port}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initialize a new RelativeRedirect object with the given arguments. Arguments:
|
18
|
+
# * app : The next middleware in the chain. This is always called.
|
19
|
+
# * &block : If provided, it is called with the environment and the response
|
20
|
+
# from the next middleware. It should return a string representing the scheme
|
21
|
+
# and server name (such as 'http://example.org').
|
22
|
+
def initialize(app, &block)
|
23
|
+
@app = app
|
24
|
+
@absolute_proc = block || DEFAULT_ABSOLUTE_PROC
|
25
|
+
end
|
26
|
+
|
27
|
+
# Call the next middleware with the environment. If the request was a
|
28
|
+
# redirect (response status 301, 302, or 303), and the location header does
|
29
|
+
# not start with an http or https url scheme, call the block provided by new
|
30
|
+
# and use that to make the Location header an absolute url. If the Location
|
31
|
+
# does not start with a slash, make location relative to the path requested.
|
32
|
+
def call(env)
|
33
|
+
res = @app.call(env)
|
34
|
+
if [301,302,303].include?(res[0]) and loc = res[1]['Location'] and !%r{\Ahttps?://}o.match(loc)
|
35
|
+
absolute = @absolute_proc.call(env, res)
|
36
|
+
res[1]['Location'] = if %r{\A/}.match(loc)
|
37
|
+
"#{absolute}#{loc}"
|
38
|
+
else
|
39
|
+
"#{absolute}#{File.dirname(Rack::Utils.unescape(env['PATH_INFO']))}/#{loc}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
res
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
# Rack::ResponseCache is a Rack middleware that caches responses for successful
|
5
|
+
# GET requests with no query string to disk or any ruby object that has an
|
6
|
+
# []= method (so it works with memcached). When caching to disk, it works similar to
|
7
|
+
# Rails' page caching, allowing you to cache dynamic pages to static files that can
|
8
|
+
# be served directly by a front end webserver.
|
9
|
+
class Rack::ResponseCache
|
10
|
+
# The default proc used if a block is not provided to .new
|
11
|
+
# It unescapes the PATH_INFO of the environment, and makes sure that it doesn't
|
12
|
+
# include '..'. If the Content-Type of the response is text/(html|css|xml),
|
13
|
+
# return a path with the appropriate extension (.html, .css, or .xml).
|
14
|
+
# If the path ends with a / and the Content-Type is text/html, change the basename
|
15
|
+
# of the path to index.html.
|
16
|
+
DEFAULT_PATH_PROC = proc do |env, res|
|
17
|
+
path = Rack::Utils.unescape(env['PATH_INFO'])
|
18
|
+
if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(res[1]['Content-Type'])
|
19
|
+
type = match[1]
|
20
|
+
path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
|
21
|
+
path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
|
22
|
+
path
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Initialize a new ReponseCache object with the given arguments. Arguments:
|
27
|
+
# * app : The next middleware in the chain. This is always called.
|
28
|
+
# * cache : The place to cache responses. If a string is provided, a disk
|
29
|
+
# cache is used, and all cached files will use this directory as the root directory.
|
30
|
+
# If anything other than a string is provided, it should respond to []=, which will
|
31
|
+
# be called with a path string and a body value (the 3rd element of the response).
|
32
|
+
# * &block : If provided, it is called with the environment and the response from the next middleware.
|
33
|
+
# It should return nil or false if the path should not be cached, and should return
|
34
|
+
# the pathname to use as a string if the result should be cached.
|
35
|
+
# If not provided, the DEFAULT_PATH_PROC is used.
|
36
|
+
def initialize(app, cache, &block)
|
37
|
+
@app = app
|
38
|
+
@cache = cache
|
39
|
+
@path_proc = block || DEFAULT_PATH_PROC
|
40
|
+
end
|
41
|
+
|
42
|
+
# Call the next middleware with the environment. If the request was successful (response status 200),
|
43
|
+
# was a GET request, and had an empty query string, call the block set up in initialize to get the path.
|
44
|
+
# If the cache is a string, create any necessary middle directories, and cache the file in the appropriate
|
45
|
+
# subdirectory of cache. Otherwise, cache the body of the reponse as the value with the path as the key.
|
46
|
+
def call(env)
|
47
|
+
res = @app.call(env)
|
48
|
+
if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and res[0] == 200 and path = @path_proc.call(env, res)
|
49
|
+
if @cache.is_a?(String)
|
50
|
+
path = File.join(@cache, path) if @cache
|
51
|
+
FileUtils.mkdir_p(File.dirname(path))
|
52
|
+
File.open(path, 'wb'){|f| res[2].each{|c| f.write(c)}}
|
53
|
+
else
|
54
|
+
@cache[path] = res[2]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
res
|
58
|
+
end
|
59
|
+
end
|
@@ -4,45 +4,46 @@ module Rack
|
|
4
4
|
[Exception, '/error/internal']
|
5
5
|
]
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
PATH_INFO = 'rack.route_exceptions.path_info'.freeze
|
8
|
+
EXCEPTION = 'rack.route_exceptions.exception'.freeze
|
9
|
+
RETURNED = 'rack.route_exceptions.returned'.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def route(exception, to)
|
13
|
+
ROUTES.delete_if{|k,v| k == exception }
|
14
|
+
ROUTES << [exception, to]
|
15
|
+
end
|
16
|
+
|
17
|
+
alias []= route
|
18
|
+
end
|
10
19
|
|
11
20
|
def initialize(app)
|
12
21
|
@app = app
|
13
22
|
end
|
14
23
|
|
15
24
|
def call(env, try_again = true)
|
16
|
-
|
17
|
-
|
18
|
-
response
|
25
|
+
returned = @app.call(env)
|
19
26
|
rescue Exception => exception
|
20
27
|
raise(exception) unless try_again
|
21
28
|
|
22
29
|
ROUTES.each do |klass, to|
|
23
30
|
next unless klass === exception
|
24
|
-
return route(to, env,
|
31
|
+
return route(to, env, returned, exception)
|
25
32
|
end
|
26
33
|
|
27
34
|
raise(exception)
|
28
35
|
end
|
29
36
|
|
30
|
-
def route(to, env,
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
env.merge!(hash)
|
37
|
+
def route(to, env, returned, exception)
|
38
|
+
env.merge!(
|
39
|
+
PATH_INFO => env['PATH_INFO'],
|
40
|
+
EXCEPTION => exception,
|
41
|
+
RETURNED => returned
|
42
|
+
)
|
37
43
|
|
38
44
|
env['PATH_INFO'] = to
|
39
45
|
|
40
46
|
call(env, try_again = false)
|
41
47
|
end
|
42
|
-
|
43
|
-
def self.route(exception, to)
|
44
|
-
ROUTES.delete_if{|k,v| k == exception }
|
45
|
-
ROUTES << [exception, to]
|
46
|
-
end
|
47
48
|
end
|
48
49
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Rack
|
2
|
+
# Installs signal handlers that are safely processed after a request
|
3
|
+
#
|
4
|
+
# NOTE: This middleware should not be used in a threaded environment
|
5
|
+
#
|
6
|
+
# use Rack::Signals.new do
|
7
|
+
# trap 'INT', lambda {
|
8
|
+
# puts "Exiting now"
|
9
|
+
# exit
|
10
|
+
# }
|
11
|
+
#
|
12
|
+
# trap_when_ready 'USR1', lambda {
|
13
|
+
# puts "Exiting when ready"
|
14
|
+
# exit
|
15
|
+
# }
|
16
|
+
# end
|
17
|
+
class Signals
|
18
|
+
class BodyWithCallback
|
19
|
+
def initialize(body, callback)
|
20
|
+
@body, @callback = body, callback
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
@body.each(&block)
|
25
|
+
@callback.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(app, &block)
|
30
|
+
@app = app
|
31
|
+
@processing = false
|
32
|
+
@when_ready = nil
|
33
|
+
instance_eval(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
begin
|
38
|
+
@processing, @when_ready = true, nil
|
39
|
+
status, headers, body = @app.call(env)
|
40
|
+
|
41
|
+
if handler = @when_ready
|
42
|
+
body = BodyWithCallback.new(body, handler)
|
43
|
+
@when_ready = nil
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
@processing = false
|
47
|
+
end
|
48
|
+
|
49
|
+
[status, headers, body]
|
50
|
+
end
|
51
|
+
|
52
|
+
def trap_when_ready(signal, handler)
|
53
|
+
when_ready_handler = lambda { |signal|
|
54
|
+
if @processing
|
55
|
+
@when_ready = lambda { handler.call(signal) }
|
56
|
+
else
|
57
|
+
handler.call(signal)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
trap(signal, when_ready_handler)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/rack-contrib.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'rack-contrib'
|
6
|
-
s.version = '0.9.
|
7
|
-
s.date = '2009-
|
6
|
+
s.version = '0.9.2'
|
7
|
+
s.date = '2009-03-07'
|
8
8
|
|
9
9
|
s.description = "Contributed Rack Middleware and Utilities"
|
10
10
|
s.summary = "Contributed Rack Middleware and Utilities"
|
@@ -18,8 +18,13 @@ Gem::Specification.new do |s|
|
|
18
18
|
README.rdoc
|
19
19
|
Rakefile
|
20
20
|
lib/rack/contrib.rb
|
21
|
+
lib/rack/contrib/accept_format.rb
|
22
|
+
lib/rack/contrib/backstage.rb
|
21
23
|
lib/rack/contrib/bounce_favicon.rb
|
22
24
|
lib/rack/contrib/callbacks.rb
|
25
|
+
lib/rack/contrib/config.rb
|
26
|
+
lib/rack/contrib/csshttprequest.rb
|
27
|
+
lib/rack/contrib/deflect.rb
|
23
28
|
lib/rack/contrib/etag.rb
|
24
29
|
lib/rack/contrib/evil.rb
|
25
30
|
lib/rack/contrib/garbagecollector.rb
|
@@ -28,16 +33,27 @@ Gem::Specification.new do |s|
|
|
28
33
|
lib/rack/contrib/locale.rb
|
29
34
|
lib/rack/contrib/mailexceptions.rb
|
30
35
|
lib/rack/contrib/nested_params.rb
|
36
|
+
lib/rack/contrib/not_found.rb
|
31
37
|
lib/rack/contrib/post_body_content_type_parser.rb
|
32
38
|
lib/rack/contrib/proctitle.rb
|
33
39
|
lib/rack/contrib/profiler.rb
|
40
|
+
lib/rack/contrib/relative_redirect.rb
|
41
|
+
lib/rack/contrib/response_cache.rb
|
34
42
|
lib/rack/contrib/route_exceptions.rb
|
35
43
|
lib/rack/contrib/sendfile.rb
|
44
|
+
lib/rack/contrib/signals.rb
|
36
45
|
lib/rack/contrib/time_zone.rb
|
37
46
|
rack-contrib.gemspec
|
47
|
+
test/404.html
|
48
|
+
test/Maintenance.html
|
38
49
|
test/mail_settings.rb
|
50
|
+
test/spec_rack_accept_format.rb
|
51
|
+
test/spec_rack_backstage.rb
|
39
52
|
test/spec_rack_callbacks.rb
|
53
|
+
test/spec_rack_config.rb
|
40
54
|
test/spec_rack_contrib.rb
|
55
|
+
test/spec_rack_csshttprequest.rb
|
56
|
+
test/spec_rack_deflect.rb
|
41
57
|
test/spec_rack_etag.rb
|
42
58
|
test/spec_rack_evil.rb
|
43
59
|
test/spec_rack_garbagecollector.rb
|
@@ -45,9 +61,12 @@ Gem::Specification.new do |s|
|
|
45
61
|
test/spec_rack_lighttpd_script_name_fix.rb
|
46
62
|
test/spec_rack_mailexceptions.rb
|
47
63
|
test/spec_rack_nested_params.rb
|
64
|
+
test/spec_rack_not_found.rb
|
48
65
|
test/spec_rack_post_body_content_type_parser.rb
|
49
66
|
test/spec_rack_proctitle.rb
|
50
67
|
test/spec_rack_profiler.rb
|
68
|
+
test/spec_rack_relative_redirect.rb
|
69
|
+
test/spec_rack_response_cache.rb
|
51
70
|
test/spec_rack_sendfile.rb
|
52
71
|
]
|
53
72
|
# = MANIFEST =
|
@@ -55,7 +74,7 @@ Gem::Specification.new do |s|
|
|
55
74
|
s.test_files = s.files.select {|path| path =~ /^test\/spec_.*\.rb/}
|
56
75
|
|
57
76
|
s.extra_rdoc_files = %w[README.rdoc COPYING]
|
58
|
-
s.add_dependency 'rack', '
|
77
|
+
s.add_dependency 'rack', '>= 0.9.1'
|
59
78
|
s.add_dependency 'test-spec', '~> 0.9.0'
|
60
79
|
s.add_development_dependency 'tmail', '>= 1.2'
|
61
80
|
s.add_development_dependency 'json', '>= 1.1'
|
data/test/404.html
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Not Found
|
@@ -0,0 +1 @@
|
|
1
|
+
Under maintenance.
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/accept_format'
|
4
|
+
require 'rack/mime'
|
5
|
+
|
6
|
+
context "Rack::AcceptFormat" do
|
7
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
|
8
|
+
|
9
|
+
specify "should do nothing when a format extension is already provided" do
|
10
|
+
request = Rack::MockRequest.env_for("/resource.json")
|
11
|
+
body = Rack::AcceptFormat.new(app).call(request).last
|
12
|
+
body.should == "/resource.json"
|
13
|
+
end
|
14
|
+
|
15
|
+
context "default extention" do
|
16
|
+
specify "should allow custom default" do
|
17
|
+
request = Rack::MockRequest.env_for("/resource")
|
18
|
+
body = Rack::AcceptFormat.new(app, '.xml').call(request).last
|
19
|
+
body.should == "/resource.xml"
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "should default to html" do
|
23
|
+
request = Rack::MockRequest.env_for("/resource")
|
24
|
+
body = Rack::AcceptFormat.new(app).call(request).last
|
25
|
+
body.should == "/resource.html"
|
26
|
+
end
|
27
|
+
|
28
|
+
specify "should notmalize custom extention" do
|
29
|
+
request = Rack::MockRequest.env_for("/resource")
|
30
|
+
|
31
|
+
body = Rack::AcceptFormat.new(app,'xml').call(request).last #no dot prefix
|
32
|
+
body.should == "/resource.xml"
|
33
|
+
|
34
|
+
body = Rack::AcceptFormat.new(app, :xml).call(request).last
|
35
|
+
body.should == "/resource.xml"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "there is no format extension" do
|
40
|
+
Rack::Mime::MIME_TYPES.clear
|
41
|
+
|
42
|
+
def mime(ext, type)
|
43
|
+
ext = ".#{ext}" unless ext.to_s[0] == ?.
|
44
|
+
Rack::Mime::MIME_TYPES[ext.to_s] = type
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "should add the default extension if no Accept header" do
|
48
|
+
request = Rack::MockRequest.env_for("/resource")
|
49
|
+
body = Rack::AcceptFormat.new(app).call(request).last
|
50
|
+
body.should == "/resource.html"
|
51
|
+
end
|
52
|
+
|
53
|
+
specify "should add the default extension if the Accept header is not registered in the Mime::Types" do
|
54
|
+
request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
|
55
|
+
body = Rack::AcceptFormat.new(app).call(request).last
|
56
|
+
body.should == "/resource.html"
|
57
|
+
end
|
58
|
+
|
59
|
+
specify "should add the correct extension if the Accept header is registered in the Mime::Types" do
|
60
|
+
mime :json, 'application/json'
|
61
|
+
request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
|
62
|
+
body = Rack::AcceptFormat.new(app).call(request).last
|
63
|
+
body.should == "/resource.json"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
specify "shouldn't confuse extention when there are dots in path" do
|
68
|
+
request = Rack::MockRequest.env_for("/parent.resource/resource")
|
69
|
+
body = Rack::AcceptFormat.new(app, '.html').call(request).last
|
70
|
+
body.should == "/parent.resource/resource.html"
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/builder'
|
3
|
+
require 'rack/mock'
|
4
|
+
require 'rack/contrib/backstage'
|
5
|
+
|
6
|
+
context "Rack::Backstage" do
|
7
|
+
specify "shows maintenances page if present" do
|
8
|
+
app = Rack::Builder.new do
|
9
|
+
use Rack::Backstage, 'test/Maintenance.html'
|
10
|
+
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
11
|
+
end
|
12
|
+
response = Rack::MockRequest.new(app).get('/')
|
13
|
+
response.body.should.equal('Under maintenance.')
|
14
|
+
response.status.should.equal(503)
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "passes on request if page is not present" do
|
18
|
+
app = Rack::Builder.new do
|
19
|
+
use Rack::Backstage, 'test/Nonsense.html'
|
20
|
+
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
21
|
+
end
|
22
|
+
response = Rack::MockRequest.new(app).get('/')
|
23
|
+
response.body.should.equal('Hello, World!')
|
24
|
+
response.status.should.equal(200)
|
25
|
+
end
|
26
|
+
end
|
data/test/spec_rack_callbacks.rb
CHANGED
@@ -43,7 +43,7 @@ context "Rack::Callbacks" do
|
|
43
43
|
before Flame
|
44
44
|
before Pacify, "with love"
|
45
45
|
|
46
|
-
run lambda {|env| [200, {}, env['flame']
|
46
|
+
run lambda {|env| [200, {}, [env['flame'], env['peace']]] }
|
47
47
|
|
48
48
|
after Finale
|
49
49
|
after TheEnd
|
@@ -62,4 +62,4 @@ context "Rack::Callbacks" do
|
|
62
62
|
|
63
63
|
response.headers['last'].should.equal 'TheEnd'
|
64
64
|
end
|
65
|
-
end
|
65
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/config'
|
4
|
+
|
5
|
+
context "Rack::Config" do
|
6
|
+
|
7
|
+
specify "should accept a block that modifies the environment" do
|
8
|
+
app = Rack::Builder.new do
|
9
|
+
use Rack::Lint
|
10
|
+
use Rack::ContentLength
|
11
|
+
use Rack::Config do |env|
|
12
|
+
env['greeting'] = 'hello'
|
13
|
+
end
|
14
|
+
run lambda { |env|
|
15
|
+
[200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
response = Rack::MockRequest.new(app).get('/')
|
19
|
+
response.body.should.equal('hello')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|