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.

Files changed (54) hide show
  1. data/AUTHORS +3 -0
  2. data/COPYING +18 -0
  3. data/KNOWN-ISSUES +18 -0
  4. data/RDOX +144 -0
  5. data/README +154 -0
  6. data/Rakefile +174 -0
  7. data/SPEC +132 -0
  8. data/bin/rackup +148 -0
  9. data/contrib/rack_logo.svg +111 -0
  10. data/example/lobster.ru +4 -0
  11. data/lib/rack.rb +67 -0
  12. data/lib/rack/adapter/camping.rb +16 -0
  13. data/lib/rack/adapter/rails.rb +65 -0
  14. data/lib/rack/builder.rb +52 -0
  15. data/lib/rack/cascade.rb +26 -0
  16. data/lib/rack/commonlogger.rb +56 -0
  17. data/lib/rack/file.rb +108 -0
  18. data/lib/rack/handler/cgi.rb +57 -0
  19. data/lib/rack/handler/fastcgi.rb +81 -0
  20. data/lib/rack/handler/mongrel.rb +57 -0
  21. data/lib/rack/handler/webrick.rb +56 -0
  22. data/lib/rack/lint.rb +394 -0
  23. data/lib/rack/lobster.rb +65 -0
  24. data/lib/rack/mock.rb +183 -0
  25. data/lib/rack/recursive.rb +57 -0
  26. data/lib/rack/reloader.rb +64 -0
  27. data/lib/rack/request.rb +112 -0
  28. data/lib/rack/response.rb +114 -0
  29. data/lib/rack/showexceptions.rb +344 -0
  30. data/lib/rack/urlmap.rb +50 -0
  31. data/lib/rack/utils.rb +176 -0
  32. data/test/cgi/lighttpd.conf +20 -0
  33. data/test/cgi/test +9 -0
  34. data/test/cgi/test.fcgi +9 -0
  35. data/test/cgi/test.ru +7 -0
  36. data/test/spec_rack_camping.rb +44 -0
  37. data/test/spec_rack_cascade.rb +35 -0
  38. data/test/spec_rack_cgi.rb +82 -0
  39. data/test/spec_rack_commonlogger.rb +32 -0
  40. data/test/spec_rack_fastcgi.rb +82 -0
  41. data/test/spec_rack_file.rb +32 -0
  42. data/test/spec_rack_lint.rb +317 -0
  43. data/test/spec_rack_lobster.rb +45 -0
  44. data/test/spec_rack_mock.rb +150 -0
  45. data/test/spec_rack_mongrel.rb +87 -0
  46. data/test/spec_rack_recursive.rb +77 -0
  47. data/test/spec_rack_request.rb +219 -0
  48. data/test/spec_rack_response.rb +110 -0
  49. data/test/spec_rack_showexceptions.rb +21 -0
  50. data/test/spec_rack_urlmap.rb +140 -0
  51. data/test/spec_rack_utils.rb +57 -0
  52. data/test/spec_rack_webrick.rb +89 -0
  53. data/test/testrequest.rb +43 -0
  54. metadata +117 -0
@@ -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
@@ -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
@@ -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