rack-contrib 0.9.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/COPYING +18 -0
- data/README.rdoc +60 -0
- data/Rakefile +92 -0
- data/lib/rack/contrib/bounce_favicon.rb +16 -0
- data/lib/rack/contrib/callbacks.rb +37 -0
- data/lib/rack/contrib/etag.rb +20 -0
- data/lib/rack/contrib/evil.rb +12 -0
- data/lib/rack/contrib/garbagecollector.rb +14 -0
- data/lib/rack/contrib/jsonp.rb +38 -0
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
- data/lib/rack/contrib/locale.rb +31 -0
- data/lib/rack/contrib/mailexceptions.rb +120 -0
- data/lib/rack/contrib/nested_params.rb +143 -0
- data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
- data/lib/rack/contrib/proctitle.rb +30 -0
- data/lib/rack/contrib/profiler.rb +106 -0
- data/lib/rack/contrib/route_exceptions.rb +48 -0
- data/lib/rack/contrib/sendfile.rb +142 -0
- data/lib/rack/contrib/time_zone.rb +25 -0
- data/lib/rack/contrib.rb +25 -0
- data/rack-contrib.gemspec +68 -0
- data/test/mail_settings.rb +12 -0
- data/test/spec_rack_callbacks.rb +65 -0
- data/test/spec_rack_contrib.rb +8 -0
- data/test/spec_rack_etag.rb +23 -0
- data/test/spec_rack_evil.rb +19 -0
- data/test/spec_rack_garbagecollector.rb +13 -0
- data/test/spec_rack_jsonp.rb +21 -0
- data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
- data/test/spec_rack_mailexceptions.rb +97 -0
- data/test/spec_rack_nested_params.rb +46 -0
- data/test/spec_rack_post_body_content_type_parser.rb +32 -0
- data/test/spec_rack_proctitle.rb +26 -0
- data/test/spec_rack_profiler.rb +32 -0
- data/test/spec_rack_sendfile.rb +86 -0
- metadata +144 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rack
|
2
|
+
# Middleware to update the process title ($0) with information about the
|
3
|
+
# current request. Based loosely on:
|
4
|
+
# - http://purefiction.net/mongrel_proctitle/
|
5
|
+
# - http://github.com/grempe/thin-proctitle/tree/master
|
6
|
+
#
|
7
|
+
# NOTE: This will not work properly in a multi-threaded environment.
|
8
|
+
class ProcTitle
|
9
|
+
F = ::File
|
10
|
+
PROGNAME = F.basename($0)
|
11
|
+
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
@appname = Dir.pwd.split('/').reverse.
|
15
|
+
find { |name| name !~ /^(\d+|current|releases)$/ } || PROGNAME
|
16
|
+
@requests = 0
|
17
|
+
$0 = "#{PROGNAME} [#{@appname}] init ..."
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
host, port = env['SERVER_NAME'], env['SERVER_PORT']
|
22
|
+
meth, path = env['REQUEST_METHOD'], env['PATH_INFO']
|
23
|
+
@requests += 1
|
24
|
+
$0 = "#{PROGNAME} [#{@appname}/#{port}] (#{@requests}) " \
|
25
|
+
"#{meth} #{path}"
|
26
|
+
|
27
|
+
@app.call(env)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'ruby-prof'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
# Set the profile=process_time query parameter to download a
|
5
|
+
# calltree profile of the request.
|
6
|
+
#
|
7
|
+
# Pass the :printer option to pick a different result format.
|
8
|
+
class Profiler
|
9
|
+
MODES = %w(
|
10
|
+
process_time
|
11
|
+
wall_time
|
12
|
+
cpu_time
|
13
|
+
allocations
|
14
|
+
memory
|
15
|
+
gc_runs
|
16
|
+
gc_time
|
17
|
+
)
|
18
|
+
|
19
|
+
DEFAULT_PRINTER = RubyProf::CallTreePrinter
|
20
|
+
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
21
|
+
|
22
|
+
PRINTER_CONTENT_TYPE = {
|
23
|
+
RubyProf::FlatPrinter => 'text/plain',
|
24
|
+
RubyProf::GraphPrinter => 'text/plain',
|
25
|
+
RubyProf::GraphHtmlPrinter => 'text/html'
|
26
|
+
}
|
27
|
+
|
28
|
+
# Accepts a :printer => [:call_tree|:graph_html|:graph|:flat] option
|
29
|
+
# defaulting to :call_tree.
|
30
|
+
def initialize(app, options = {})
|
31
|
+
@app = app
|
32
|
+
@printer = parse_printer(options[:printer])
|
33
|
+
@times = (options[:times] || 1).to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
if mode = profiling?(env)
|
38
|
+
profile(env, mode)
|
39
|
+
else
|
40
|
+
@app.call(env)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def profiling?(env)
|
46
|
+
unless RubyProf.running?
|
47
|
+
request = Rack::Request.new(env)
|
48
|
+
if mode = request.params.delete('profile')
|
49
|
+
if RubyProf.const_defined?(mode.upcase)
|
50
|
+
mode
|
51
|
+
else
|
52
|
+
env['rack.errors'].write "Invalid RubyProf measure_mode: " +
|
53
|
+
"#{mode}. Use one of #{MODES.to_a.join(', ')}"
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def profile(env, mode)
|
61
|
+
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
|
62
|
+
|
63
|
+
result = RubyProf.profile do
|
64
|
+
@times.times { @app.call(env) }
|
65
|
+
end
|
66
|
+
|
67
|
+
[200, headers(@printer, env, mode), print(@printer, result)]
|
68
|
+
end
|
69
|
+
|
70
|
+
def print(printer, result)
|
71
|
+
body = StringIO.new
|
72
|
+
printer.new(result).print(body, :min_percent => 0.01)
|
73
|
+
body.rewind
|
74
|
+
body
|
75
|
+
end
|
76
|
+
|
77
|
+
def headers(printer, env, mode)
|
78
|
+
headers = { 'Content-Type' => PRINTER_CONTENT_TYPE[printer] || DEFAULT_CONTENT_TYPE }
|
79
|
+
if printer == RubyProf::CallTreePrinter
|
80
|
+
filename = ::File.basename(env['PATH_INFO'])
|
81
|
+
headers['Content-Disposition'] =
|
82
|
+
%(attachment; filename="#{filename}.#{mode}.tree")
|
83
|
+
end
|
84
|
+
headers
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_printer(printer)
|
88
|
+
if printer.nil?
|
89
|
+
DEFAULT_PRINTER
|
90
|
+
elsif printer.is_a?(Class)
|
91
|
+
printer
|
92
|
+
else
|
93
|
+
name = "#{camel_case(printer)}Printer"
|
94
|
+
if RubyProf.const_defined?(name)
|
95
|
+
RubyProf.const_get(name)
|
96
|
+
else
|
97
|
+
DEFAULT_PRINTER
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def camel_case(word)
|
103
|
+
word.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Rack
|
2
|
+
class RouteExceptions
|
3
|
+
ROUTES = [
|
4
|
+
[Exception, '/error/internal']
|
5
|
+
]
|
6
|
+
|
7
|
+
ROUTE_EXCEPTIONS_PATH_INFO = 'rack.route_exceptions.path_info'.freeze
|
8
|
+
ROUTE_EXCEPTIONS_EXCEPTION = 'rack.route_exceptions.exception'.freeze
|
9
|
+
ROUTE_EXCEPTIONS_RESPONSE = 'rack.route_exceptions.response'.freeze
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env, try_again = true)
|
16
|
+
status, header, body = response = @app.call(env)
|
17
|
+
|
18
|
+
response
|
19
|
+
rescue Exception => exception
|
20
|
+
raise(exception) unless try_again
|
21
|
+
|
22
|
+
ROUTES.each do |klass, to|
|
23
|
+
next unless klass === exception
|
24
|
+
return route(to, env, response, exception)
|
25
|
+
end
|
26
|
+
|
27
|
+
raise(exception)
|
28
|
+
end
|
29
|
+
|
30
|
+
def route(to, env, response, exception)
|
31
|
+
hash = {
|
32
|
+
ROUTE_EXCEPTIONS_PATH_INFO => env['PATH_INFO'],
|
33
|
+
ROUTE_EXCEPTIONS_EXCEPTION => exception,
|
34
|
+
ROUTE_EXCEPTIONS_RESPONSE => response
|
35
|
+
}
|
36
|
+
env.merge!(hash)
|
37
|
+
|
38
|
+
env['PATH_INFO'] = to
|
39
|
+
|
40
|
+
call(env, try_again = false)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.route(exception, to)
|
44
|
+
ROUTES.delete_if{|k,v| k == exception }
|
45
|
+
ROUTES << [exception, to]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'rack/file'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class File #:nodoc:
|
5
|
+
alias :to_path :path
|
6
|
+
end
|
7
|
+
|
8
|
+
# = Sendfile
|
9
|
+
#
|
10
|
+
# The Sendfile middleware intercepts responses whose body is being
|
11
|
+
# served from a file and replaces it with a server specific X-Sendfile
|
12
|
+
# header. The web server is then responsible for writing the file contents
|
13
|
+
# to the client. This can dramatically reduce the amount of work required
|
14
|
+
# by the Ruby backend and takes advantage of the web servers optimized file
|
15
|
+
# delivery code.
|
16
|
+
#
|
17
|
+
# In order to take advantage of this middleware, the response body must
|
18
|
+
# respond to +to_path+ and the request must include an X-Sendfile-Type
|
19
|
+
# header. Rack::File and other components implement +to_path+ so there's
|
20
|
+
# rarely anything you need to do in your application. The X-Sendfile-Type
|
21
|
+
# header is typically set in your web servers configuration. The following
|
22
|
+
# sections attempt to document
|
23
|
+
#
|
24
|
+
# === Nginx
|
25
|
+
#
|
26
|
+
# Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
|
27
|
+
# but requires parts of the filesystem to be mapped into a private URL
|
28
|
+
# hierarachy.
|
29
|
+
#
|
30
|
+
# The following example shows the Nginx configuration required to create
|
31
|
+
# a private "/files/" area, enable X-Accel-Redirect, and pass the special
|
32
|
+
# X-Sendfile-Type and X-Accel-Mapping headers to the backend:
|
33
|
+
#
|
34
|
+
# location /files/ {
|
35
|
+
# internal;
|
36
|
+
# alias /var/www/;
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# location / {
|
40
|
+
# proxy_redirect false;
|
41
|
+
#
|
42
|
+
# proxy_set_header Host $host;
|
43
|
+
# proxy_set_header X-Real-IP $remote_addr;
|
44
|
+
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
45
|
+
#
|
46
|
+
# proxy_set_header X-Sendfile-Type X-Accel-Redirect
|
47
|
+
# proxy_set_header X-Accel-Mapping /files/=/var/www/;
|
48
|
+
#
|
49
|
+
# proxy_pass http://127.0.0.1:8080/;
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# Note that the X-Sendfile-Type header must be set exactly as shown above. The
|
53
|
+
# X-Accel-Mapping header should specify the name of the private URL pattern,
|
54
|
+
# followed by an equals sign (=), followed by the location on the file system
|
55
|
+
# that it maps to. The middleware performs a simple substitution on the
|
56
|
+
# resulting path.
|
57
|
+
#
|
58
|
+
# See Also: http://wiki.codemongers.com/NginxXSendfile
|
59
|
+
#
|
60
|
+
# === lighttpd
|
61
|
+
#
|
62
|
+
# Lighttpd has supported some variation of the X-Sendfile header for some
|
63
|
+
# time, although only recent version support X-Sendfile in a reverse proxy
|
64
|
+
# configuration.
|
65
|
+
#
|
66
|
+
# $HTTP["host"] == "example.com" {
|
67
|
+
# proxy-core.protocol = "http"
|
68
|
+
# proxy-core.balancer = "round-robin"
|
69
|
+
# proxy-core.backends = (
|
70
|
+
# "127.0.0.1:8000",
|
71
|
+
# "127.0.0.1:8001",
|
72
|
+
# ...
|
73
|
+
# )
|
74
|
+
#
|
75
|
+
# proxy-core.allow-x-sendfile = "enable"
|
76
|
+
# proxy-core.rewrite-request = (
|
77
|
+
# "X-Sendfile-Type" => (".*" => "X-Sendfile")
|
78
|
+
# )
|
79
|
+
# }
|
80
|
+
#
|
81
|
+
# See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
|
82
|
+
#
|
83
|
+
# === Apache
|
84
|
+
#
|
85
|
+
# X-Sendfile is supported under Apache 2.x using a separate module:
|
86
|
+
#
|
87
|
+
# http://tn123.ath.cx/mod_xsendfile/
|
88
|
+
#
|
89
|
+
# Once the module is compiled and installed, you can enable it using
|
90
|
+
# XSendFile config directive:
|
91
|
+
#
|
92
|
+
# RequestHeader Set X-Sendfile-Type X-Sendfile
|
93
|
+
# ProxyPassReverse / http://localhost:8001/
|
94
|
+
# XSendFile on
|
95
|
+
|
96
|
+
class Sendfile
|
97
|
+
F = ::File
|
98
|
+
|
99
|
+
def initialize(app, variation=nil)
|
100
|
+
@app = app
|
101
|
+
@variation = variation
|
102
|
+
end
|
103
|
+
|
104
|
+
def call(env)
|
105
|
+
status, headers, body = @app.call(env)
|
106
|
+
if body.respond_to?(:to_path)
|
107
|
+
case type = variation(env)
|
108
|
+
when 'X-Accel-Redirect'
|
109
|
+
path = F.expand_path(body.to_path)
|
110
|
+
if url = map_accel_path(env, path)
|
111
|
+
headers[type] = url
|
112
|
+
body = []
|
113
|
+
else
|
114
|
+
env['rack.errors'] << "X-Accel-Mapping header missing"
|
115
|
+
end
|
116
|
+
when 'X-Sendfile', 'X-Lighttpd-Send-File'
|
117
|
+
path = F.expand_path(body.to_path)
|
118
|
+
headers[type] = path
|
119
|
+
body = []
|
120
|
+
when '', nil
|
121
|
+
else
|
122
|
+
env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
[status, headers, body]
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def variation(env)
|
130
|
+
@variation ||
|
131
|
+
env['sendfile.type'] ||
|
132
|
+
env['HTTP_X_SENDFILE_TYPE']
|
133
|
+
end
|
134
|
+
|
135
|
+
def map_accel_path(env, file)
|
136
|
+
if mapping = env['HTTP_X_ACCEL_MAPPING']
|
137
|
+
internal, external = mapping.split('=', 2).map{ |p| p.strip }
|
138
|
+
file.sub(/^#{internal}/i, external)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rack
|
2
|
+
class TimeZone
|
3
|
+
Javascript = <<-EOJ
|
4
|
+
function setTimezoneCookie() {
|
5
|
+
var offset = (new Date()).getTimezoneOffset()
|
6
|
+
var date = new Date();
|
7
|
+
date.setTime(date.getTime()+3600000);
|
8
|
+
document.cookie = "utc_offset="+offset+"; expires="+date.toGMTString();+"; path=/";
|
9
|
+
}
|
10
|
+
EOJ
|
11
|
+
|
12
|
+
def initialize(app)
|
13
|
+
@app = app
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
request = Rack::Request.new(env)
|
18
|
+
if utc_offset = request.cookies["utc_offset"]
|
19
|
+
env["rack.timezone.utc_offset"] = -(utc_offset.to_i * 60)
|
20
|
+
end
|
21
|
+
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rack/contrib.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Contrib
|
5
|
+
def self.release
|
6
|
+
"0.9"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
autoload :BounceFavicon, "rack/contrib/bounce_favicon"
|
11
|
+
autoload :ETag, "rack/contrib/etag"
|
12
|
+
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
13
|
+
autoload :JSONP, "rack/contrib/jsonp"
|
14
|
+
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
15
|
+
autoload :Locale, "rack/contrib/locale"
|
16
|
+
autoload :MailExceptions, "rack/contrib/mailexceptions"
|
17
|
+
autoload :PostBodyContentTypeParser, "rack/contrib/post_body_content_type_parser"
|
18
|
+
autoload :ProcTitle, "rack/contrib/proctitle"
|
19
|
+
autoload :Profiler, "rack/contrib/profiler"
|
20
|
+
autoload :Sendfile, "rack/contrib/sendfile"
|
21
|
+
autoload :TimeZone, "rack/contrib/time_zone"
|
22
|
+
autoload :Evil, "rack/contrib/evil"
|
23
|
+
autoload :Callbacks, "rack/contrib/callbacks"
|
24
|
+
autoload :NestedParams, "rack/contrib/nested_params"
|
25
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
3
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
|
+
|
5
|
+
s.name = 'rack-contrib'
|
6
|
+
s.version = '0.9.0'
|
7
|
+
s.date = '2009-01-23'
|
8
|
+
|
9
|
+
s.description = "Contributed Rack Middleware and Utilities"
|
10
|
+
s.summary = "Contributed Rack Middleware and Utilities"
|
11
|
+
|
12
|
+
s.authors = ["rack-devel"]
|
13
|
+
s.email = "rack-devel@googlegroups.com"
|
14
|
+
|
15
|
+
# = MANIFEST =
|
16
|
+
s.files = %w[
|
17
|
+
COPYING
|
18
|
+
README.rdoc
|
19
|
+
Rakefile
|
20
|
+
lib/rack/contrib.rb
|
21
|
+
lib/rack/contrib/bounce_favicon.rb
|
22
|
+
lib/rack/contrib/callbacks.rb
|
23
|
+
lib/rack/contrib/etag.rb
|
24
|
+
lib/rack/contrib/evil.rb
|
25
|
+
lib/rack/contrib/garbagecollector.rb
|
26
|
+
lib/rack/contrib/jsonp.rb
|
27
|
+
lib/rack/contrib/lighttpd_script_name_fix.rb
|
28
|
+
lib/rack/contrib/locale.rb
|
29
|
+
lib/rack/contrib/mailexceptions.rb
|
30
|
+
lib/rack/contrib/nested_params.rb
|
31
|
+
lib/rack/contrib/post_body_content_type_parser.rb
|
32
|
+
lib/rack/contrib/proctitle.rb
|
33
|
+
lib/rack/contrib/profiler.rb
|
34
|
+
lib/rack/contrib/route_exceptions.rb
|
35
|
+
lib/rack/contrib/sendfile.rb
|
36
|
+
lib/rack/contrib/time_zone.rb
|
37
|
+
rack-contrib.gemspec
|
38
|
+
test/mail_settings.rb
|
39
|
+
test/spec_rack_callbacks.rb
|
40
|
+
test/spec_rack_contrib.rb
|
41
|
+
test/spec_rack_etag.rb
|
42
|
+
test/spec_rack_evil.rb
|
43
|
+
test/spec_rack_garbagecollector.rb
|
44
|
+
test/spec_rack_jsonp.rb
|
45
|
+
test/spec_rack_lighttpd_script_name_fix.rb
|
46
|
+
test/spec_rack_mailexceptions.rb
|
47
|
+
test/spec_rack_nested_params.rb
|
48
|
+
test/spec_rack_post_body_content_type_parser.rb
|
49
|
+
test/spec_rack_proctitle.rb
|
50
|
+
test/spec_rack_profiler.rb
|
51
|
+
test/spec_rack_sendfile.rb
|
52
|
+
]
|
53
|
+
# = MANIFEST =
|
54
|
+
|
55
|
+
s.test_files = s.files.select {|path| path =~ /^test\/spec_.*\.rb/}
|
56
|
+
|
57
|
+
s.extra_rdoc_files = %w[README.rdoc COPYING]
|
58
|
+
s.add_dependency 'rack', '~> 0.9.1'
|
59
|
+
s.add_dependency 'test-spec', '~> 0.9.0'
|
60
|
+
s.add_development_dependency 'tmail', '>= 1.2'
|
61
|
+
s.add_development_dependency 'json', '>= 1.1'
|
62
|
+
|
63
|
+
s.has_rdoc = true
|
64
|
+
s.homepage = "http://github.com/rack/rack-contrib/"
|
65
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "rack-contrib", "--main", "README"]
|
66
|
+
s.require_paths = %w[lib]
|
67
|
+
s.rubygems_version = '1.1.1'
|
68
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
TEST_SMTP = nil
|
2
|
+
|
3
|
+
# Enable SMTP tests by providing the following for your SMTP server.
|
4
|
+
#
|
5
|
+
# TEST_SMTP = {
|
6
|
+
# :server => 'localhost',
|
7
|
+
# :domain => 'localhost',
|
8
|
+
# :port => 25,
|
9
|
+
# :authentication => :login,
|
10
|
+
# :user_name => nil,
|
11
|
+
# :password => nil
|
12
|
+
# }
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
|
4
|
+
class Flame
|
5
|
+
def call(env)
|
6
|
+
env['flame'] = 'F Lifo..'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Pacify
|
11
|
+
def initialize(with)
|
12
|
+
@with = with
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
env['peace'] = @with
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Finale
|
21
|
+
def call(response)
|
22
|
+
status, headers, body = response
|
23
|
+
|
24
|
+
headers['last'] = 'Finale'
|
25
|
+
$old_status = status
|
26
|
+
|
27
|
+
[201, headers, body]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class TheEnd
|
32
|
+
def call(response)
|
33
|
+
status, headers, body = response
|
34
|
+
|
35
|
+
headers['last'] = 'TheEnd'
|
36
|
+
[201, headers, body]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "Rack::Callbacks" do
|
41
|
+
specify "works for love and small stack trace" do
|
42
|
+
callback_app = Rack::Callbacks.new do
|
43
|
+
before Flame
|
44
|
+
before Pacify, "with love"
|
45
|
+
|
46
|
+
run lambda {|env| [200, {}, env['flame'] + env['peace']] }
|
47
|
+
|
48
|
+
after Finale
|
49
|
+
after TheEnd
|
50
|
+
end
|
51
|
+
|
52
|
+
app = Rack::Builder.new do
|
53
|
+
run callback_app
|
54
|
+
end.to_app
|
55
|
+
|
56
|
+
response = Rack::MockRequest.new(app).get("/")
|
57
|
+
|
58
|
+
response.body.should.equal 'F Lifo..with love'
|
59
|
+
|
60
|
+
$old_status.should.equal 200
|
61
|
+
response.status.should.equal 201
|
62
|
+
|
63
|
+
response.headers['last'].should.equal 'TheEnd'
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/etag'
|
4
|
+
|
5
|
+
context "Rack::ETag" do
|
6
|
+
specify "sets ETag if none is set" do
|
7
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
|
8
|
+
response = Rack::ETag.new(app).call({})
|
9
|
+
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "does not change ETag if it is already set" do
|
13
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, "Hello, World!"] }
|
14
|
+
response = Rack::ETag.new(app).call({})
|
15
|
+
response[1]['ETag'].should.equal "\"abc\""
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "does not set ETag if steaming body" do
|
19
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello", "World"]] }
|
20
|
+
response = Rack::ETag.new(app).call({})
|
21
|
+
response[1]['ETag'].should.equal nil
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/evil'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
context "Rack::Evil" do
|
7
|
+
app = lambda do |env|
|
8
|
+
template = ERB.new("<%= throw :response, [404, {'Content-Type' => 'text/html'}, 'Never know where it comes from'] %>")
|
9
|
+
[200, {'Content-Type' => 'text/plain'}, template.result(binding)]
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "should enable the app to return the response from anywhere" do
|
13
|
+
status, headers, body = Rack::Evil.new(app).call({})
|
14
|
+
|
15
|
+
status.should.equal 404
|
16
|
+
headers['Content-Type'].should.equal 'text/html'
|
17
|
+
body.should.equal 'Never know where it comes from'
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/garbagecollector'
|
4
|
+
|
5
|
+
context 'Rack::GarbageCollector' do
|
6
|
+
|
7
|
+
specify 'starts the garbage collector after each request' do
|
8
|
+
app = lambda { |env|
|
9
|
+
[200, {'Content-Type'=>'text/plain'}, ['Hello World']] }
|
10
|
+
Rack::GarbageCollector.new(app).call({})
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/jsonp'
|
4
|
+
|
5
|
+
context "Rack::JSONP" do
|
6
|
+
|
7
|
+
specify "should wrap the response body in the Javascript callback when provided" do
|
8
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, '{"bar":"foo"}'] }
|
9
|
+
request = Rack::MockRequest.env_for("/", :input => "foo=bar&callback=foo")
|
10
|
+
body = Rack::JSONP.new(app).call(request).last
|
11
|
+
body.should == 'foo({"bar":"foo"})'
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "should not change anything if no :callback param is provided" do
|
15
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, '{"bar":"foo"}'] }
|
16
|
+
request = Rack::MockRequest.env_for("/", :input => "foo=bar")
|
17
|
+
body = Rack::JSONP.new(app).call(request).last
|
18
|
+
body.should == '{"bar":"foo"}'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/contrib/lighttpd_script_name_fix'
|
4
|
+
|
5
|
+
context "Rack::LighttpdScriptNameFix" do
|
6
|
+
specify "corrects SCRIPT_NAME and PATH_INFO set by lighttpd " do
|
7
|
+
env = {
|
8
|
+
"PATH_INFO" => "/foo/bar/baz",
|
9
|
+
"SCRIPT_NAME" => "/hello"
|
10
|
+
}
|
11
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
|
12
|
+
response = Rack::LighttpdScriptNameFix.new(app).call(env)
|
13
|
+
env['SCRIPT_NAME'].should.be.empty
|
14
|
+
env['PATH_INFO'].should.equal '/hello/foo/bar/baz'
|
15
|
+
end
|
16
|
+
end
|