rack 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- data/AUTHORS +3 -0
- data/COPYING +18 -0
- data/KNOWN-ISSUES +18 -0
- data/RDOX +144 -0
- data/README +154 -0
- data/Rakefile +174 -0
- data/SPEC +132 -0
- data/bin/rackup +148 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/lib/rack.rb +67 -0
- data/lib/rack/adapter/camping.rb +16 -0
- data/lib/rack/adapter/rails.rb +65 -0
- data/lib/rack/builder.rb +52 -0
- data/lib/rack/cascade.rb +26 -0
- data/lib/rack/commonlogger.rb +56 -0
- data/lib/rack/file.rb +108 -0
- data/lib/rack/handler/cgi.rb +57 -0
- data/lib/rack/handler/fastcgi.rb +81 -0
- data/lib/rack/handler/mongrel.rb +57 -0
- data/lib/rack/handler/webrick.rb +56 -0
- data/lib/rack/lint.rb +394 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/mock.rb +183 -0
- data/lib/rack/recursive.rb +57 -0
- data/lib/rack/reloader.rb +64 -0
- data/lib/rack/request.rb +112 -0
- data/lib/rack/response.rb +114 -0
- data/lib/rack/showexceptions.rb +344 -0
- data/lib/rack/urlmap.rb +50 -0
- data/lib/rack/utils.rb +176 -0
- data/test/cgi/lighttpd.conf +20 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +9 -0
- data/test/cgi/test.ru +7 -0
- data/test/spec_rack_camping.rb +44 -0
- data/test/spec_rack_cascade.rb +35 -0
- data/test/spec_rack_cgi.rb +82 -0
- data/test/spec_rack_commonlogger.rb +32 -0
- data/test/spec_rack_fastcgi.rb +82 -0
- data/test/spec_rack_file.rb +32 -0
- data/test/spec_rack_lint.rb +317 -0
- data/test/spec_rack_lobster.rb +45 -0
- data/test/spec_rack_mock.rb +150 -0
- data/test/spec_rack_mongrel.rb +87 -0
- data/test/spec_rack_recursive.rb +77 -0
- data/test/spec_rack_request.rb +219 -0
- data/test/spec_rack_response.rb +110 -0
- data/test/spec_rack_showexceptions.rb +21 -0
- data/test/spec_rack_urlmap.rb +140 -0
- data/test/spec_rack_utils.rb +57 -0
- data/test/spec_rack_webrick.rb +89 -0
- data/test/testrequest.rb +43 -0
- metadata +117 -0
data/lib/rack/lobster.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
require 'rack/request'
|
4
|
+
require 'rack/response'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
# Paste has a Pony, Rack has a Lobster!
|
8
|
+
class Lobster
|
9
|
+
LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2
|
10
|
+
P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0
|
11
|
+
t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ
|
12
|
+
I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0])
|
13
|
+
|
14
|
+
LambdaLobster = lambda { |env|
|
15
|
+
if env["QUERY_STRING"].include?("flip")
|
16
|
+
lobster = LobsterString.split("\n").
|
17
|
+
map { |line| line.ljust(42).reverse }.
|
18
|
+
join("\n")
|
19
|
+
href = "?"
|
20
|
+
else
|
21
|
+
lobster = LobsterString
|
22
|
+
href = "?flip"
|
23
|
+
end
|
24
|
+
|
25
|
+
[200, {"Content-Type" => "text/html"},
|
26
|
+
["<title>Lobstericious!</title>",
|
27
|
+
"<pre>", lobster, "</pre>",
|
28
|
+
"<a href='#{href}'>flip!</a>"]
|
29
|
+
]
|
30
|
+
}
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
req = Request.new(env)
|
34
|
+
if req.GET["flip"] == "left"
|
35
|
+
lobster = LobsterString.split("\n").
|
36
|
+
map { |line| line.ljust(42).reverse }.
|
37
|
+
join("\n")
|
38
|
+
href = "?flip=right"
|
39
|
+
elsif req.GET["flip"] == "crash"
|
40
|
+
raise "Lobster crashed"
|
41
|
+
else
|
42
|
+
lobster = LobsterString
|
43
|
+
href = "?flip=left"
|
44
|
+
end
|
45
|
+
|
46
|
+
Response.new.finish do |res|
|
47
|
+
res.write "<title>Lobstericious!</title>"
|
48
|
+
res.write "<pre>"
|
49
|
+
res.write lobster
|
50
|
+
res.write "</pre>"
|
51
|
+
res.write "<p><a href='#{href}'>flip!</a></p>"
|
52
|
+
res.write "<p><a href='?flip=crash'>crash!</a></p>"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if $0 == __FILE__
|
60
|
+
require 'rack'
|
61
|
+
require 'rack/showexceptions'
|
62
|
+
Rack::Handler::WEBrick.run \
|
63
|
+
Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)),
|
64
|
+
:Port => 9202
|
65
|
+
end
|
data/lib/rack/mock.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'stringio'
|
3
|
+
require 'rack/lint'
|
4
|
+
require 'rack/utils'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
# Rack::MockRequest helps testing your Rack application without
|
8
|
+
# actually using HTTP.
|
9
|
+
#
|
10
|
+
# After performing a request on a URL with get/post/put/delete, it
|
11
|
+
# returns a MockResponse with useful helper methods for effective
|
12
|
+
# testing.
|
13
|
+
#
|
14
|
+
# You can pass a hash with additional configuration to the
|
15
|
+
# get/post/put/delete.
|
16
|
+
# <tt>:input</tt>:: A String or IO-like to be used as rack.input.
|
17
|
+
# <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
|
18
|
+
# <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
|
19
|
+
|
20
|
+
class MockRequest
|
21
|
+
class FatalWarning < RuntimeError
|
22
|
+
end
|
23
|
+
|
24
|
+
class FatalWarner
|
25
|
+
def puts(warning)
|
26
|
+
raise FatalWarning, warning
|
27
|
+
end
|
28
|
+
|
29
|
+
def write(warning)
|
30
|
+
raise FatalWarning, warning
|
31
|
+
end
|
32
|
+
|
33
|
+
def flush
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
DEFAULT_ENV = {
|
38
|
+
"rack.version" => [0,1],
|
39
|
+
"rack.input" => StringIO.new,
|
40
|
+
"rack.errors" => StringIO.new,
|
41
|
+
"rack.multithread" => true,
|
42
|
+
"rack.multiprocess" => true,
|
43
|
+
"rack.run_once" => false,
|
44
|
+
}
|
45
|
+
|
46
|
+
def initialize(app)
|
47
|
+
@app = app
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(uri, opts={}) request("GET", uri, opts) end
|
51
|
+
def post(uri, opts={}) request("POST", uri, opts) end
|
52
|
+
def put(uri, opts={}) request("PUT", uri, opts) end
|
53
|
+
def delete(uri, opts={}) request("DELETE", uri, opts) end
|
54
|
+
|
55
|
+
def request(method="GET", uri="", opts={})
|
56
|
+
env = self.class.env_for(uri, opts.merge(:method => method))
|
57
|
+
|
58
|
+
if opts[:lint]
|
59
|
+
app = Rack::Lint.new(@app)
|
60
|
+
else
|
61
|
+
app = @app
|
62
|
+
end
|
63
|
+
|
64
|
+
errors = env["rack.errors"]
|
65
|
+
MockResponse.new(*(app.call(env) + [errors]))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the Rack environment used for a request to +uri+.
|
69
|
+
def self.env_for(uri="", opts={})
|
70
|
+
uri = URI(uri)
|
71
|
+
env = DEFAULT_ENV.dup
|
72
|
+
|
73
|
+
env["REQUEST_METHOD"] = opts[:method] || "GET"
|
74
|
+
env["SERVER_NAME"] = uri.host || "example.org"
|
75
|
+
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
|
76
|
+
env["QUERY_STRING"] = uri.query.to_s
|
77
|
+
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
|
78
|
+
env["rack.url_scheme"] = uri.scheme || "http"
|
79
|
+
|
80
|
+
env["SCRIPT_NAME"] = opts[:script_name] || ""
|
81
|
+
|
82
|
+
if opts[:fatal]
|
83
|
+
env["rack.errors"] = FatalWarner.new
|
84
|
+
else
|
85
|
+
env["rack.errors"] = StringIO.new
|
86
|
+
end
|
87
|
+
|
88
|
+
opts[:input] ||= ""
|
89
|
+
if String === opts[:input]
|
90
|
+
env["rack.input"] = StringIO.new(opts[:input])
|
91
|
+
else
|
92
|
+
env["rack.input"] = opts[:input]
|
93
|
+
end
|
94
|
+
|
95
|
+
opts.each { |field, value|
|
96
|
+
env[field] = value if String === field
|
97
|
+
}
|
98
|
+
|
99
|
+
env
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Rack::MockResponse provides useful helpers for testing your apps.
|
104
|
+
# Usually, you don't create the MockResponse on your own, but use
|
105
|
+
# MockRequest.
|
106
|
+
|
107
|
+
class MockResponse
|
108
|
+
def initialize(status, headers, body, errors=StringIO.new(""))
|
109
|
+
@status = status.to_i
|
110
|
+
|
111
|
+
@original_headers = headers
|
112
|
+
@headers = Rack::Utils::HeaderHash.new
|
113
|
+
headers.each { |field, values|
|
114
|
+
values.each { |value|
|
115
|
+
@headers[field] = value
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
@body = ""
|
120
|
+
body.each { |part| @body << part }
|
121
|
+
|
122
|
+
@errors = errors.string
|
123
|
+
end
|
124
|
+
|
125
|
+
# Status
|
126
|
+
attr_reader :status
|
127
|
+
|
128
|
+
def invalid?; @status < 100 || @status >= 600; end
|
129
|
+
|
130
|
+
def informational?; @status >= 100 && @status < 200; end
|
131
|
+
def successful?; @status >= 200 && @status < 300; end
|
132
|
+
def redirection?; @status >= 300 && @status < 400; end
|
133
|
+
def client_error?; @status >= 400 && @status < 500; end
|
134
|
+
def server_error?; @status >= 500 && @status < 600; end
|
135
|
+
|
136
|
+
def ok?; @status == 200; end
|
137
|
+
def forbidden?; @status == 403; end
|
138
|
+
def not_found?; @status == 404; end
|
139
|
+
|
140
|
+
def redirect?; [301, 302, 303, 307].include? @status; end
|
141
|
+
def empty?; [201, 204, 304].include? @status; end
|
142
|
+
|
143
|
+
# Headers
|
144
|
+
attr_reader :headers, :original_headers
|
145
|
+
|
146
|
+
def include?(header)
|
147
|
+
!!headers[header]
|
148
|
+
end
|
149
|
+
|
150
|
+
def [](field)
|
151
|
+
headers[field]
|
152
|
+
end
|
153
|
+
|
154
|
+
def content_type
|
155
|
+
headers["Content-Type"]
|
156
|
+
end
|
157
|
+
|
158
|
+
def content_length
|
159
|
+
cl = headers["Content-Length"]
|
160
|
+
cl ? cl.to_i : cl
|
161
|
+
end
|
162
|
+
|
163
|
+
def location
|
164
|
+
headers["Location"]
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# Body
|
169
|
+
attr_reader :body
|
170
|
+
|
171
|
+
def =~(other)
|
172
|
+
@body =~ other
|
173
|
+
end
|
174
|
+
|
175
|
+
def match(other)
|
176
|
+
@body.match other
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# Errors
|
181
|
+
attr_accessor :errors
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
# Rack::ForwardRequest gets caught by Rack::Recursive and redirects
|
5
|
+
# the current request to the app at +url+.
|
6
|
+
#
|
7
|
+
# raise ForwardRequest.new("/not-found")
|
8
|
+
#
|
9
|
+
|
10
|
+
class ForwardRequest < Exception
|
11
|
+
attr_reader :url, :env
|
12
|
+
|
13
|
+
def initialize(url, env={})
|
14
|
+
@url = URI(url)
|
15
|
+
@env = env
|
16
|
+
|
17
|
+
@env["PATH_INFO"] = @url.path
|
18
|
+
@env["QUERY_STRING"] = @url.query if @url.query
|
19
|
+
@env["HTTP_HOST"] = @url.host if @url.host
|
20
|
+
@env["HTTP_PORT"] = @url.port if @url.port
|
21
|
+
@env["rack.url_scheme"] = @url.scheme if @url.scheme
|
22
|
+
|
23
|
+
super "forwarding to #{url}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Rack::Recursive allows applications called down the chain to
|
28
|
+
# include data from other applications (by using
|
29
|
+
# <tt>rack['rack.recursive.include'][...]</tt> or raise a
|
30
|
+
# ForwardRequest to redirect internally.
|
31
|
+
|
32
|
+
class Recursive
|
33
|
+
def initialize(app)
|
34
|
+
@app = app
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
@script_name = env["SCRIPT_NAME"]
|
39
|
+
@app.call(env.merge('rack.recursive.include' => method(:include)))
|
40
|
+
rescue ForwardRequest => req
|
41
|
+
call(env.merge(req.env))
|
42
|
+
end
|
43
|
+
|
44
|
+
def include(env, path)
|
45
|
+
unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ ||
|
46
|
+
path[@script_name.size].nil?)
|
47
|
+
raise ArgumentError, "can only include below #{@script_name}, not #{path}"
|
48
|
+
end
|
49
|
+
|
50
|
+
env = env.merge("PATH_INFO" => path, "SCRIPT_NAME" => @script_name,
|
51
|
+
"REQUEST_METHOD" => "GET",
|
52
|
+
"CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "",
|
53
|
+
"rack.input" => StringIO.new(""))
|
54
|
+
@app.call(env)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
# Rack::Reloader checks on every request, but at most every +secs+
|
5
|
+
# seconds, if a file loaded changed, and reloads it, logging to
|
6
|
+
# rack.errors.
|
7
|
+
#
|
8
|
+
# It is recommended you use ShowExceptions to catch SyntaxErrors etc.
|
9
|
+
|
10
|
+
class Reloader
|
11
|
+
def initialize(app, secs=10)
|
12
|
+
@app = app
|
13
|
+
@secs = secs # reload every @secs seconds max
|
14
|
+
@last = Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
if Time.now > @last + @secs
|
19
|
+
Thread.exclusive {
|
20
|
+
reload!(env['rack.errors'])
|
21
|
+
@last = Time.now
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
@app.call(env)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reload!(stderr=STDERR)
|
29
|
+
need_reload = $LOADED_FEATURES.find_all { |loaded|
|
30
|
+
begin
|
31
|
+
if loaded =~ /\A[.\/]/ # absolute filename or 1.9
|
32
|
+
abs = loaded
|
33
|
+
else
|
34
|
+
abs = $LOAD_PATH.map { |path| ::File.join(path, loaded) }.
|
35
|
+
find { |file| ::File.exist? file }
|
36
|
+
end
|
37
|
+
|
38
|
+
if abs
|
39
|
+
::File.mtime(abs) > @last - @secs rescue false
|
40
|
+
else
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
need_reload.each { |l|
|
47
|
+
$LOADED_FEATURES.delete l
|
48
|
+
}
|
49
|
+
|
50
|
+
need_reload.each { |to_load|
|
51
|
+
begin
|
52
|
+
if require to_load
|
53
|
+
stderr.puts "#{self.class}: reloaded `#{to_load}'"
|
54
|
+
end
|
55
|
+
rescue LoadError, SyntaxError => e
|
56
|
+
raise e # Possibly ShowExceptions
|
57
|
+
end
|
58
|
+
}
|
59
|
+
|
60
|
+
stderr.flush
|
61
|
+
need_reload
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/rack/request.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
# Rack::Request provides a convenient interface to a Rack
|
5
|
+
# environment. It is stateless, the environment +env+ passed to the
|
6
|
+
# constructor will be directly modified.
|
7
|
+
#
|
8
|
+
# req = Rack::Request.new(env)
|
9
|
+
# req.post?
|
10
|
+
# req.params["data"]
|
11
|
+
|
12
|
+
class Request
|
13
|
+
# The environment of the request.
|
14
|
+
attr_reader :env
|
15
|
+
|
16
|
+
def initialize(env)
|
17
|
+
@env = env
|
18
|
+
end
|
19
|
+
|
20
|
+
def body; @env["rack.input"] end
|
21
|
+
def scheme; @env["rack.url_scheme"] end
|
22
|
+
def script_name; @env["SCRIPT_NAME"].to_s end
|
23
|
+
def path_info; @env["PATH_INFO"].to_s end
|
24
|
+
def port; @env["SERVER_PORT"].to_i end
|
25
|
+
def request_method; @env["REQUEST_METHOD"] end
|
26
|
+
def query_string; @env["QUERY_STRING"].to_s end
|
27
|
+
|
28
|
+
def host
|
29
|
+
# Remove port number.
|
30
|
+
(@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
|
31
|
+
end
|
32
|
+
|
33
|
+
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
|
34
|
+
def path_info=(s); @env["PATH_INFO"] = s.to_s end
|
35
|
+
|
36
|
+
def get?; request_method == "GET" end
|
37
|
+
def post?; request_method == "POST" end
|
38
|
+
def put?; request_method == "PUT" end
|
39
|
+
def delete?; request_method == "DELETE" end
|
40
|
+
|
41
|
+
# Returns the data recieved in the query string.
|
42
|
+
def GET
|
43
|
+
if @env["rack.request.query_string"] == query_string
|
44
|
+
@env["rack.request.query_hash"]
|
45
|
+
else
|
46
|
+
@env["rack.request.query_string"] = query_string
|
47
|
+
@env["rack.request.query_hash"] =
|
48
|
+
Utils.parse_query(query_string)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the data recieved in the request body.
|
53
|
+
#
|
54
|
+
# This method support both application/x-www-form-urlencoded and
|
55
|
+
# multipart/form-data.
|
56
|
+
def POST
|
57
|
+
if @env["rack.request.form_input"] == @env["rack.input"]
|
58
|
+
@env["rack.request.form_hash"]
|
59
|
+
else
|
60
|
+
@env["rack.request.form_input"] = @env["rack.input"]
|
61
|
+
unless @env["rack.request.form_hash"] =
|
62
|
+
Utils::Multipart.parse_multipart(env)
|
63
|
+
@env["rack.request.form_vars"] = @env["rack.input"].read
|
64
|
+
@env["rack.request.form_hash"] = Utils.parse_query(@env["rack.request.form_vars"])
|
65
|
+
end
|
66
|
+
@env["rack.request.form_hash"]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# The union of GET and POST data.
|
71
|
+
def params
|
72
|
+
self.GET.update(self.POST)
|
73
|
+
end
|
74
|
+
|
75
|
+
def cookies
|
76
|
+
return {} unless @env["HTTP_COOKIE"]
|
77
|
+
|
78
|
+
if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
|
79
|
+
@env["rack.request.cookie_hash"]
|
80
|
+
else
|
81
|
+
@env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
|
82
|
+
# XXX sure?
|
83
|
+
@env["rack.request.cookie_hash"] =
|
84
|
+
Utils.parse_query(@env["rack.request.cookie_string"], ';,')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def xhr?
|
89
|
+
@env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Tries to return a remake of the original request URL as a string.
|
93
|
+
def url
|
94
|
+
url = scheme + "://"
|
95
|
+
url << host
|
96
|
+
|
97
|
+
if scheme == "https" && port != 443 ||
|
98
|
+
scheme == "http" && port != 80
|
99
|
+
url << ":#{port}"
|
100
|
+
end
|
101
|
+
|
102
|
+
url << script_name
|
103
|
+
url << path_info
|
104
|
+
|
105
|
+
unless query_string.empty?
|
106
|
+
url << "?" << query_string
|
107
|
+
end
|
108
|
+
|
109
|
+
url
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|