technomancy-rack 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/KNOWN-ISSUES +18 -0
  2. data/README +242 -0
  3. data/bin/rackup +183 -0
  4. data/lib/rack.rb +92 -0
  5. data/lib/rack/adapter/camping.rb +22 -0
  6. data/lib/rack/auth/abstract/handler.rb +28 -0
  7. data/lib/rack/auth/abstract/request.rb +37 -0
  8. data/lib/rack/auth/basic.rb +58 -0
  9. data/lib/rack/auth/digest/md5.rb +124 -0
  10. data/lib/rack/auth/digest/nonce.rb +51 -0
  11. data/lib/rack/auth/digest/params.rb +55 -0
  12. data/lib/rack/auth/digest/request.rb +40 -0
  13. data/lib/rack/auth/openid.rb +116 -0
  14. data/lib/rack/builder.rb +56 -0
  15. data/lib/rack/cascade.rb +36 -0
  16. data/lib/rack/commonlogger.rb +56 -0
  17. data/lib/rack/file.rb +112 -0
  18. data/lib/rack/handler/cgi.rb +57 -0
  19. data/lib/rack/handler/fastcgi.rb +83 -0
  20. data/lib/rack/handler/lsws.rb +52 -0
  21. data/lib/rack/handler/mongrel.rb +97 -0
  22. data/lib/rack/handler/scgi.rb +57 -0
  23. data/lib/rack/handler/webrick.rb +57 -0
  24. data/lib/rack/lint.rb +394 -0
  25. data/lib/rack/lobster.rb +65 -0
  26. data/lib/rack/mock.rb +172 -0
  27. data/lib/rack/recursive.rb +57 -0
  28. data/lib/rack/reloader.rb +64 -0
  29. data/lib/rack/request.rb +197 -0
  30. data/lib/rack/response.rb +166 -0
  31. data/lib/rack/session/abstract/id.rb +126 -0
  32. data/lib/rack/session/cookie.rb +71 -0
  33. data/lib/rack/session/memcache.rb +83 -0
  34. data/lib/rack/session/pool.rb +67 -0
  35. data/lib/rack/showexceptions.rb +344 -0
  36. data/lib/rack/showstatus.rb +103 -0
  37. data/lib/rack/static.rb +38 -0
  38. data/lib/rack/urlmap.rb +48 -0
  39. data/lib/rack/utils.rb +256 -0
  40. data/test/spec_rack_auth_basic.rb +69 -0
  41. data/test/spec_rack_auth_digest.rb +169 -0
  42. data/test/spec_rack_builder.rb +50 -0
  43. data/test/spec_rack_camping.rb +47 -0
  44. data/test/spec_rack_cascade.rb +50 -0
  45. data/test/spec_rack_cgi.rb +91 -0
  46. data/test/spec_rack_commonlogger.rb +32 -0
  47. data/test/spec_rack_fastcgi.rb +91 -0
  48. data/test/spec_rack_file.rb +40 -0
  49. data/test/spec_rack_lint.rb +317 -0
  50. data/test/spec_rack_lobster.rb +45 -0
  51. data/test/spec_rack_mock.rb +152 -0
  52. data/test/spec_rack_mongrel.rb +165 -0
  53. data/test/spec_rack_recursive.rb +77 -0
  54. data/test/spec_rack_request.rb +384 -0
  55. data/test/spec_rack_response.rb +167 -0
  56. data/test/spec_rack_session_cookie.rb +49 -0
  57. data/test/spec_rack_session_memcache.rb +100 -0
  58. data/test/spec_rack_session_pool.rb +84 -0
  59. data/test/spec_rack_showexceptions.rb +21 -0
  60. data/test/spec_rack_showstatus.rb +71 -0
  61. data/test/spec_rack_static.rb +37 -0
  62. data/test/spec_rack_urlmap.rb +175 -0
  63. data/test/spec_rack_utils.rb +69 -0
  64. data/test/spec_rack_webrick.rb +106 -0
  65. data/test/testrequest.rb +43 -0
  66. metadata +167 -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 => 9292
65
+ end
@@ -0,0 +1,172 @@
1
+ require 'uri'
2
+ require 'stringio'
3
+ require 'rack/lint'
4
+ require 'rack/utils'
5
+ require 'rack/response'
6
+
7
+ module Rack
8
+ # Rack::MockRequest helps testing your Rack application without
9
+ # actually using HTTP.
10
+ #
11
+ # After performing a request on a URL with get/post/put/delete, it
12
+ # returns a MockResponse with useful helper methods for effective
13
+ # testing.
14
+ #
15
+ # You can pass a hash with additional configuration to the
16
+ # get/post/put/delete.
17
+ # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
18
+ # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
19
+ # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
20
+
21
+ class MockRequest
22
+ class FatalWarning < RuntimeError
23
+ end
24
+
25
+ class FatalWarner
26
+ def puts(warning)
27
+ raise FatalWarning, warning
28
+ end
29
+
30
+ def write(warning)
31
+ raise FatalWarning, warning
32
+ end
33
+
34
+ def flush
35
+ end
36
+
37
+ def string
38
+ ""
39
+ end
40
+ end
41
+
42
+ DEFAULT_ENV = {
43
+ "rack.version" => [0,1],
44
+ "rack.input" => StringIO.new,
45
+ "rack.errors" => StringIO.new,
46
+ "rack.multithread" => true,
47
+ "rack.multiprocess" => true,
48
+ "rack.run_once" => false,
49
+ }
50
+
51
+ def initialize(app)
52
+ @app = app
53
+ end
54
+
55
+ def get(uri, opts={}) request("GET", uri, opts) end
56
+ def post(uri, opts={}) request("POST", uri, opts) end
57
+ def put(uri, opts={}) request("PUT", uri, opts) end
58
+ def delete(uri, opts={}) request("DELETE", uri, opts) end
59
+
60
+ def request(method="GET", uri="", opts={})
61
+ env = self.class.env_for(uri, opts.merge(:method => method))
62
+
63
+ if opts[:lint]
64
+ app = Rack::Lint.new(@app)
65
+ else
66
+ app = @app
67
+ end
68
+
69
+ errors = env["rack.errors"]
70
+ MockResponse.new(*(app.call(env) + [errors]))
71
+ end
72
+
73
+ # Return the Rack environment used for a request to +uri+.
74
+ def self.env_for(uri="", opts={})
75
+ uri = URI(uri)
76
+ env = DEFAULT_ENV.dup
77
+
78
+ env["REQUEST_METHOD"] = opts[:method] || "GET"
79
+ env["SERVER_NAME"] = uri.host || "example.org"
80
+ env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
81
+ env["QUERY_STRING"] = uri.query.to_s
82
+ env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
83
+ env["rack.url_scheme"] = uri.scheme || "http"
84
+
85
+ env["SCRIPT_NAME"] = opts[:script_name] || ""
86
+
87
+ if opts[:fatal]
88
+ env["rack.errors"] = FatalWarner.new
89
+ else
90
+ env["rack.errors"] = StringIO.new
91
+ end
92
+
93
+ opts[:input] ||= ""
94
+ if String === opts[:input]
95
+ env["rack.input"] = StringIO.new(opts[:input])
96
+ else
97
+ env["rack.input"] = opts[:input]
98
+ end
99
+
100
+ opts.each { |field, value|
101
+ env[field] = value if String === field
102
+ }
103
+
104
+ env
105
+ end
106
+ end
107
+
108
+ # Rack::MockResponse provides useful helpers for testing your apps.
109
+ # Usually, you don't create the MockResponse on your own, but use
110
+ # MockRequest.
111
+
112
+ class MockResponse
113
+ def initialize(status, headers, body, errors=StringIO.new(""))
114
+ @status = status.to_i
115
+
116
+ @original_headers = headers
117
+ @headers = Rack::Utils::HeaderHash.new
118
+ headers.each { |field, values|
119
+ values.each { |value|
120
+ @headers[field] = value
121
+ }
122
+ @headers[field] = "" if values.empty?
123
+ }
124
+
125
+ @body = ""
126
+
127
+ if body.respond_to?(:call)
128
+ body.call(self)
129
+ else
130
+ body.each { |part| @body << part }
131
+ end
132
+
133
+ @errors = errors.string
134
+ end
135
+
136
+ # Status
137
+ attr_reader :status
138
+
139
+ # Included for compatibility with MongrelHandler
140
+ def send_status_no_connection_close(*args)
141
+ end
142
+
143
+ # Headers
144
+ attr_reader :headers, :original_headers
145
+
146
+ def [](field)
147
+ headers[field]
148
+ end
149
+
150
+ # Included for compatibility with MongrelHandler
151
+ def send_header(*args)
152
+ end
153
+
154
+ # Body
155
+ attr_reader :body
156
+
157
+ def =~(other)
158
+ @body =~ other
159
+ end
160
+
161
+ def match(other)
162
+ @body.match other
163
+ end
164
+
165
+
166
+ # Errors
167
+ attr_accessor :errors
168
+
169
+
170
+ include Response::Helpers
171
+ end
172
+ 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,197 @@
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
+ def content_length; @env['CONTENT_LENGTH'] end
28
+ def content_type; @env['CONTENT_TYPE'] end
29
+
30
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
31
+ # without any media type parameters. e.g., when CONTENT_TYPE is
32
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
33
+ #
34
+ # For more information on the use of media types in HTTP, see:
35
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
36
+ def media_type
37
+ content_type && content_type.split(/\s*[;,]\s*/, 2)[0].downcase
38
+ end
39
+
40
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
41
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
42
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
43
+ # this method responds with the following Hash:
44
+ # { 'charset' => 'utf-8' }
45
+ def media_type_params
46
+ return {} if content_type.nil?
47
+ content_type.split(/\s*[;,]\s*/)[1..-1].
48
+ collect { |s| s.split('=', 2) }.
49
+ inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash }
50
+ end
51
+
52
+ # The character set of the request body if a "charset" media type
53
+ # parameter was given, or nil if no "charset" was specified. Note
54
+ # that, per RFC2616, text/* media types that specify no explicit
55
+ # charset are to be considered ISO-8859-1.
56
+ def content_charset
57
+ media_type_params['charset']
58
+ end
59
+
60
+ def host
61
+ # Remove port number.
62
+ (@env["HTTP_HOST"] || @env["SERVER_NAME"]).gsub(/:\d+\z/, '')
63
+ end
64
+
65
+ def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
66
+ def path_info=(s); @env["PATH_INFO"] = s.to_s end
67
+
68
+ def get?; request_method == "GET" end
69
+ def post?; request_method == "POST" end
70
+ def put?; request_method == "PUT" end
71
+ def delete?; request_method == "DELETE" end
72
+ def head?; request_method == "HEAD" end
73
+
74
+ # The set of form-data media-types. Requests that do not indicate
75
+ # one of the media types presents in this list will not be eligible
76
+ # for form-data / param parsing.
77
+ FORM_DATA_MEDIA_TYPES = [
78
+ nil,
79
+ 'application/x-www-form-urlencoded',
80
+ 'multipart/form-data'
81
+ ]
82
+
83
+ # Determine whether the request body contains form-data by checking
84
+ # the request media_type against registered form-data media-types:
85
+ # "application/x-www-form-urlencoded" and "multipart/form-data". The
86
+ # list of form-data media types can be modified through the
87
+ # +FORM_DATA_MEDIA_TYPES+ array.
88
+ def form_data?
89
+ FORM_DATA_MEDIA_TYPES.include?(media_type)
90
+ end
91
+
92
+ # Returns the data recieved in the query string.
93
+ def GET
94
+ if @env["rack.request.query_string"] == query_string
95
+ @env["rack.request.query_hash"]
96
+ else
97
+ @env["rack.request.query_string"] = query_string
98
+ @env["rack.request.query_hash"] =
99
+ Utils.parse_query(query_string)
100
+ end
101
+ end
102
+
103
+ # Returns the data recieved in the request body.
104
+ #
105
+ # This method support both application/x-www-form-urlencoded and
106
+ # multipart/form-data.
107
+ def POST
108
+ if @env["rack.request.form_input"].eql? @env["rack.input"]
109
+ @env["rack.request.form_hash"]
110
+ elsif form_data?
111
+ @env["rack.request.form_input"] = @env["rack.input"]
112
+ unless @env["rack.request.form_hash"] =
113
+ Utils::Multipart.parse_multipart(env)
114
+ @env["rack.request.form_vars"] = @env["rack.input"].read
115
+ @env["rack.request.form_hash"] = Utils.parse_query(@env["rack.request.form_vars"])
116
+ end
117
+ @env["rack.request.form_hash"]
118
+ else
119
+ {}
120
+ end
121
+ end
122
+
123
+ # The union of GET and POST data.
124
+ def params
125
+ self.GET.update(self.POST)
126
+ rescue EOFError => e
127
+ self.GET
128
+ end
129
+
130
+ # shortcut for request.params[key]
131
+ def [](key)
132
+ params[key.to_s]
133
+ end
134
+
135
+ # shortcut for request.params[key] = value
136
+ def []=(key, value)
137
+ params[key.to_s] = value
138
+ end
139
+
140
+ # like Hash#values_at
141
+ def values_at(*keys)
142
+ keys.map{|key| params[key] }
143
+ end
144
+
145
+ # the referer of the client or '/'
146
+ def referer
147
+ @env['HTTP_REFERER'] || '/'
148
+ end
149
+ alias referrer referer
150
+
151
+
152
+ def cookies
153
+ return {} unless @env["HTTP_COOKIE"]
154
+
155
+ if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
156
+ @env["rack.request.cookie_hash"]
157
+ else
158
+ @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
159
+ # According to RFC 2109:
160
+ # If multiple cookies satisfy the criteria above, they are ordered in
161
+ # the Cookie header such that those with more specific Path attributes
162
+ # precede those with less specific. Ordering with respect to other
163
+ # attributes (e.g., Domain) is unspecified.
164
+ @env["rack.request.cookie_hash"] =
165
+ Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)|
166
+ h[k] = Array === v ? v.first : v
167
+ h
168
+ }
169
+ end
170
+ end
171
+
172
+ def xhr?
173
+ @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
174
+ end
175
+
176
+ # Tries to return a remake of the original request URL as a string.
177
+ def url
178
+ url = scheme + "://"
179
+ url << host
180
+
181
+ if scheme == "https" && port != 443 ||
182
+ scheme == "http" && port != 80
183
+ url << ":#{port}"
184
+ end
185
+
186
+ url << fullpath
187
+
188
+ url
189
+ end
190
+
191
+ def fullpath
192
+ path = script_name + path_info
193
+ path << "?" << query_string unless query_string.empty?
194
+ path
195
+ end
196
+ end
197
+ end