rack 0.3.0 → 0.4.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 (50) hide show
  1. data/AUTHORS +1 -0
  2. data/RDOX +61 -3
  3. data/README +94 -9
  4. data/Rakefile +36 -32
  5. data/SPEC +1 -7
  6. data/bin/rackup +31 -13
  7. data/lib/rack.rb +8 -19
  8. data/lib/rack/auth/digest/params.rb +2 -2
  9. data/lib/rack/auth/openid.rb +406 -80
  10. data/lib/rack/builder.rb +1 -1
  11. data/lib/rack/cascade.rb +10 -0
  12. data/lib/rack/commonlogger.rb +6 -1
  13. data/lib/rack/deflater.rb +63 -0
  14. data/lib/rack/directory.rb +158 -0
  15. data/lib/rack/file.rb +11 -5
  16. data/lib/rack/handler.rb +44 -0
  17. data/lib/rack/handler/evented_mongrel.rb +8 -0
  18. data/lib/rack/handler/fastcgi.rb +1 -0
  19. data/lib/rack/handler/mongrel.rb +21 -1
  20. data/lib/rack/lint.rb +20 -13
  21. data/lib/rack/mock.rb +1 -0
  22. data/lib/rack/request.rb +69 -2
  23. data/lib/rack/session/abstract/id.rb +140 -0
  24. data/lib/rack/session/memcache.rb +97 -0
  25. data/lib/rack/session/pool.rb +50 -59
  26. data/lib/rack/showstatus.rb +3 -1
  27. data/lib/rack/urlmap.rb +12 -12
  28. data/lib/rack/utils.rb +88 -9
  29. data/test/cgi/lighttpd.conf +1 -1
  30. data/test/cgi/test.fcgi +1 -2
  31. data/test/cgi/test.ru +2 -2
  32. data/test/spec_rack_auth_openid.rb +137 -0
  33. data/test/spec_rack_camping.rb +37 -33
  34. data/test/spec_rack_cascade.rb +15 -0
  35. data/test/spec_rack_cgi.rb +4 -3
  36. data/test/spec_rack_deflater.rb +70 -0
  37. data/test/spec_rack_directory.rb +56 -0
  38. data/test/spec_rack_fastcgi.rb +4 -3
  39. data/test/spec_rack_file.rb +11 -1
  40. data/test/spec_rack_handler.rb +24 -0
  41. data/test/spec_rack_lint.rb +19 -33
  42. data/test/spec_rack_mongrel.rb +71 -0
  43. data/test/spec_rack_request.rb +91 -1
  44. data/test/spec_rack_session_memcache.rb +132 -0
  45. data/test/spec_rack_session_pool.rb +48 -1
  46. data/test/spec_rack_showstatus.rb +5 -4
  47. data/test/spec_rack_urlmap.rb +60 -25
  48. data/test/spec_rack_utils.rb +118 -1
  49. data/test/testrequest.rb +3 -1
  50. metadata +67 -44
@@ -12,12 +12,13 @@ context "Rack::Handler::CGI" do
12
12
  # Keep this first.
13
13
  specify "startup" do
14
14
  $pid = fork {
15
- Dir.chdir File.join(File.dirname(__FILE__), 'cgi')
15
+ Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
16
16
  exec "lighttpd -D -f lighttpd.conf"
17
17
  }
18
18
  end
19
19
 
20
20
  specify "should respond" do
21
+ sleep 1
21
22
  lambda {
22
23
  GET("/test")
23
24
  }.should.not.raise
@@ -29,8 +30,8 @@ context "Rack::Handler::CGI" do
29
30
  response["SERVER_SOFTWARE"].should =~ /lighttpd/
30
31
  response["HTTP_VERSION"].should.equal "HTTP/1.1"
31
32
  response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
32
- response["SERVER_PORT"].should.equal "9203"
33
- response["SERVER_NAME"].should =~ "0.0.0.0"
33
+ response["SERVER_PORT"].should.equal @port.to_s
34
+ response["SERVER_NAME"].should =~ @host
34
35
  end
35
36
 
36
37
  specify "should have rack headers" do
@@ -0,0 +1,70 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/mock'
4
+ require 'rack/deflater'
5
+ require 'stringio'
6
+
7
+ context "Rack::Deflater" do
8
+ def build_response(body, accept_encoding, headers = {})
9
+ app = lambda { |env| [200, {}, body] }
10
+ request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
11
+ response = Rack::Deflater.new(app).call(request)
12
+
13
+ return response
14
+ end
15
+
16
+ specify "should be able to deflate bodies that respond to each" do
17
+ body = Object.new
18
+ class << body; def each; yield("foo"); yield("bar"); end; end
19
+
20
+ response = build_response(body, "deflate")
21
+
22
+ response[0].should.equal(200)
23
+ response[1].should.equal({ "Content-Encoding" => "deflate" })
24
+ response[2].to_s.should.equal("K\313\317OJ,\002\000")
25
+ end
26
+
27
+ # TODO: This is really just a special case of the above...
28
+ specify "should be able to deflate String bodies" do
29
+ response = build_response("Hello world!", "deflate")
30
+
31
+ response[0].should.equal(200)
32
+ response[1].should.equal({ "Content-Encoding" => "deflate" })
33
+ response[2].to_s.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
34
+ end
35
+
36
+ specify "should be able to gzip bodies that respond to each" do
37
+ body = Object.new
38
+ class << body; def each; yield("foo"); yield("bar"); end; end
39
+
40
+ response = build_response(body, "gzip")
41
+
42
+ response[0].should.equal(200)
43
+ response[1].should.equal({ "Content-Encoding" => "gzip" })
44
+
45
+ io = StringIO.new(response[2].to_s)
46
+ gz = Zlib::GzipReader.new(io)
47
+ gz.read.should.equal("foobar")
48
+ gz.close
49
+ end
50
+
51
+ specify "should be able to fallback to no deflation" do
52
+ response = build_response("Hello world!", "superzip")
53
+
54
+ response[0].should.equal(200)
55
+ response[1].should.equal({})
56
+ response[2].should.equal("Hello world!")
57
+ end
58
+
59
+ specify "should handle the lack of an acceptable encoding" do
60
+ response1 = build_response("Hello world!", "identity;q=0", "PATH_INFO" => "/")
61
+ response1[0].should.equal(406)
62
+ response1[1].should.equal({"Content-Type" => "text/plain"})
63
+ response1[2].should.equal("An acceptable encoding for the requested resource / could not be found.")
64
+
65
+ response2 = build_response("Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
66
+ response2[0].should.equal(406)
67
+ response2[1].should.equal({"Content-Type" => "text/plain"})
68
+ response2[2].should.equal("An acceptable encoding for the requested resource /foo/bar could not be found.")
69
+ end
70
+ end
@@ -0,0 +1,56 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/directory'
4
+ require 'rack/lint'
5
+
6
+ require 'rack/mock'
7
+
8
+ context "Rack::Directory" do
9
+ DOCROOT = File.expand_path(File.dirname(__FILE__))
10
+ FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, 'passed!'] }
11
+ app = Rack::Directory.new DOCROOT, FILE_CATCH
12
+
13
+ specify "serves directory indices" do
14
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
15
+ get("/cgi/")
16
+
17
+ res.should.be.ok
18
+ res.should =~ /<html><head>/
19
+ end
20
+
21
+ specify "passes to app if file found" do
22
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
23
+ get("/cgi/test")
24
+
25
+ res.should.be.ok
26
+ res.should =~ /passed!/
27
+ end
28
+
29
+ specify "serves uri with URL encoded filenames" do
30
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
31
+ get("/%63%67%69/") # "/cgi/test"
32
+
33
+ res.should.be.ok
34
+ res.should =~ /<html><head>/
35
+
36
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
37
+ get("/cgi/%74%65%73%74") # "/cgi/test"
38
+
39
+ res.should.be.ok
40
+ res.should =~ /passed!/
41
+ end
42
+
43
+ specify "does not allow directory traversal" do
44
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
45
+ get("/cgi/../test")
46
+
47
+ res.should.be.forbidden
48
+ end
49
+
50
+ specify "404s if it can't find the file" do
51
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
52
+ get("/cgi/blubb")
53
+
54
+ res.should.be.not_found
55
+ end
56
+ end
@@ -12,12 +12,13 @@ context "Rack::Handler::FastCGI" do
12
12
  # Keep this first.
13
13
  specify "startup" do
14
14
  $pid = fork {
15
- Dir.chdir File.join(File.dirname(__FILE__), 'cgi')
15
+ Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
16
16
  exec "lighttpd -D -f lighttpd.conf"
17
17
  }
18
18
  end
19
19
 
20
20
  specify "should respond" do
21
+ sleep 1
21
22
  lambda {
22
23
  GET("/test.fcgi")
23
24
  }.should.not.raise
@@ -29,8 +30,8 @@ context "Rack::Handler::FastCGI" do
29
30
  response["SERVER_SOFTWARE"].should =~ /lighttpd/
30
31
  response["HTTP_VERSION"].should.equal "HTTP/1.1"
31
32
  response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
32
- response["SERVER_PORT"].should.equal "9203"
33
- response["SERVER_NAME"].should =~ "0.0.0.0"
33
+ response["SERVER_PORT"].should.equal @port.to_s
34
+ response["SERVER_NAME"].should =~ @host
34
35
  end
35
36
 
36
37
  specify "should have rack headers" do
@@ -15,7 +15,17 @@ context "Rack::File" do
15
15
  res.should.be.ok
16
16
  res.should =~ /ruby/
17
17
  end
18
-
18
+
19
+ specify "sets Last-Modified header" do
20
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
21
+ get("/cgi/test")
22
+
23
+ path = File.join(DOCROOT, "/cgi/test")
24
+
25
+ res.should.be.ok
26
+ res["Last-Modified"].should.equal File.mtime(path).httpdate
27
+ end
28
+
19
29
  specify "serves files with URL encoded filenames" do
20
30
  res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
21
31
  get("/cgi/%74%65%73%74") # "/cgi/test"
@@ -0,0 +1,24 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/handler'
4
+
5
+ context "Rack::Handler" do
6
+ class Rack::Handler::Lobster; end
7
+ class RockLobster; end
8
+
9
+ specify "has registered default handlers" do
10
+ Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
11
+ Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
12
+ Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
13
+ Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
14
+ end
15
+
16
+ specify "should get unregistered handler by name" do
17
+ Rack::Handler.get('lobster').should.equal Rack::Handler::Lobster
18
+ end
19
+
20
+ specify "should register custom handler" do
21
+ Rack::Handler.register('rock_lobster', 'RockLobster')
22
+ Rack::Handler.get('rock_lobster').should.equal RockLobster
23
+ end
24
+ end
@@ -8,11 +8,11 @@ context "Rack::Lint" do
8
8
  def env(*args)
9
9
  Rack::MockRequest.env_for("/", *args)
10
10
  end
11
-
11
+
12
12
  specify "passes valid request" do
13
13
  lambda {
14
14
  Rack::Lint.new(lambda { |env|
15
- [200, {"Content-type" => "test/plain"}, "foo"]
15
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
16
16
  }).call(env({}))
17
17
  }.should.not.raise
18
18
  end
@@ -120,14 +120,14 @@ context "Rack::Lint" do
120
120
  ["cc", {}, ""]
121
121
  }).call(env({}))
122
122
  }.should.raise(Rack::Lint::LintError).
123
- message.should.match(/must be >100 seen as integer/)
123
+ message.should.match(/must be >=100 seen as integer/)
124
124
 
125
125
  lambda {
126
126
  Rack::Lint.new(lambda { |env|
127
127
  [42, {}, ""]
128
128
  }).call(env({}))
129
129
  }.should.raise(Rack::Lint::LintError).
130
- message.should.match(/must be >100 seen as integer/)
130
+ message.should.match(/must be >=100 seen as integer/)
131
131
  end
132
132
 
133
133
  specify "notices header errors" do
@@ -136,14 +136,14 @@ context "Rack::Lint" do
136
136
  [200, Object.new, ""]
137
137
  }).call(env({}))
138
138
  }.should.raise(Rack::Lint::LintError).
139
- message.should.match(/should respond to #each/)
139
+ message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
140
140
 
141
141
  lambda {
142
142
  Rack::Lint.new(lambda { |env|
143
143
  [200, {true=>false}, ""]
144
144
  }).call(env({}))
145
145
  }.should.raise(Rack::Lint::LintError).
146
- message.should.match(/header key must be a string/)
146
+ message.should.equal("header key must be a string, was TrueClass")
147
147
 
148
148
  lambda {
149
149
  Rack::Lint.new(lambda { |env|
@@ -171,21 +171,21 @@ context "Rack::Lint" do
171
171
  [200, {"..%%quark%%.." => "text/plain"}, ""]
172
172
  }).call(env({}))
173
173
  }.should.raise(Rack::Lint::LintError).
174
- message.should.match(/invalid header/)
174
+ message.should.equal("invalid header name: ..%%quark%%..")
175
175
 
176
176
  lambda {
177
177
  Rack::Lint.new(lambda { |env|
178
178
  [200, {"Foo" => Object.new}, ""]
179
179
  }).call(env({}))
180
180
  }.should.raise(Rack::Lint::LintError).
181
- message.should.match(/must respond to #each/)
181
+ message.should.equal("header values must respond to #each, but the value of 'Foo' doesn't (is Object)")
182
182
 
183
183
  lambda {
184
184
  Rack::Lint.new(lambda { |env|
185
185
  [200, {"Foo" => [1,2,3]}, ""]
186
186
  }).call(env({}))
187
187
  }.should.raise(Rack::Lint::LintError).
188
- message.should.match(/must consist of Strings/)
188
+ message.should.equal("header values must consist of Strings, but 'Foo' also contains a Fixnum")
189
189
 
190
190
 
191
191
  lambda {
@@ -199,30 +199,16 @@ context "Rack::Lint" do
199
199
  specify "notices content-type errors" do
200
200
  lambda {
201
201
  Rack::Lint.new(lambda { |env|
202
- [200, {}, ""]
202
+ [200, {"Content-length" => "0"}, ""]
203
203
  }).call(env({}))
204
204
  }.should.raise(Rack::Lint::LintError).
205
205
  message.should.match(/No Content-Type/)
206
-
207
- lambda {
208
- Rack::Lint.new(lambda { |env|
209
- [204, {"Content-Type" => "text/plain"}, ""]
210
- }).call(env({}))
211
- }.should.raise(Rack::Lint::LintError).
212
- message.should.match(/Content-Type header found/)
213
-
214
- lambda {
215
- Rack::Lint.new(lambda { |env|
216
- [204, {"Content-type" => "text/plain"}, ""]
217
- }).call(env({}))
218
- }.should.raise(Rack::Lint::LintError).
219
- message.should.match(/Content-Type header found/)
220
206
  end
221
207
 
222
208
  specify "notices body errors" do
223
209
  lambda {
224
210
  status, header, body = Rack::Lint.new(lambda { |env|
225
- [200, {"Content-type" => "text/plain"}, [1,2,3]]
211
+ [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
226
212
  }).call(env({}))
227
213
  body.each { |part| }
228
214
  }.should.raise(Rack::Lint::LintError).
@@ -233,7 +219,7 @@ context "Rack::Lint" do
233
219
  lambda {
234
220
  Rack::Lint.new(lambda { |env|
235
221
  env["rack.input"].gets("\r\n")
236
- [201, {"Content-type" => "text/plain"}, ""]
222
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
237
223
  }).call(env({}))
238
224
  }.should.raise(Rack::Lint::LintError).
239
225
  message.should.match(/gets called with arguments/)
@@ -241,7 +227,7 @@ context "Rack::Lint" do
241
227
  lambda {
242
228
  Rack::Lint.new(lambda { |env|
243
229
  env["rack.input"].read("foo")
244
- [201, {"Content-type" => "text/plain"}, ""]
230
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
245
231
  }).call(env({}))
246
232
  }.should.raise(Rack::Lint::LintError).
247
233
  message.should.match(/read called with non-integer argument/)
@@ -265,7 +251,7 @@ context "Rack::Lint" do
265
251
  lambda {
266
252
  Rack::Lint.new(lambda { |env|
267
253
  env["rack.input"].gets
268
- [201, {"Content-type" => "text/plain"}, ""]
254
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
269
255
  }).call(env("rack.input" => weirdio))
270
256
  }.should.raise(Rack::Lint::LintError).
271
257
  message.should.match(/gets didn't return a String/)
@@ -273,7 +259,7 @@ context "Rack::Lint" do
273
259
  lambda {
274
260
  Rack::Lint.new(lambda { |env|
275
261
  env["rack.input"].each { |x| }
276
- [201, {"Content-type" => "text/plain"}, ""]
262
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
277
263
  }).call(env("rack.input" => weirdio))
278
264
  }.should.raise(Rack::Lint::LintError).
279
265
  message.should.match(/each didn't yield a String/)
@@ -281,7 +267,7 @@ context "Rack::Lint" do
281
267
  lambda {
282
268
  Rack::Lint.new(lambda { |env|
283
269
  env["rack.input"].read
284
- [201, {"Content-type" => "text/plain"}, ""]
270
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
285
271
  }).call(env("rack.input" => weirdio))
286
272
  }.should.raise(Rack::Lint::LintError).
287
273
  message.should.match(/read didn't return a String/)
@@ -290,7 +276,7 @@ context "Rack::Lint" do
290
276
  lambda {
291
277
  Rack::Lint.new(lambda { |env|
292
278
  env["rack.input"].close
293
- [201, {"Content-type" => "text/plain"}, ""]
279
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
294
280
  }).call(env({}))
295
281
  }.should.raise(Rack::Lint::LintError).
296
282
  message.should.match(/close must not be called/)
@@ -300,7 +286,7 @@ context "Rack::Lint" do
300
286
  lambda {
301
287
  Rack::Lint.new(lambda { |env|
302
288
  env["rack.errors"].write(42)
303
- [201, {"Content-type" => "text/plain"}, ""]
289
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
304
290
  }).call(env({}))
305
291
  }.should.raise(Rack::Lint::LintError).
306
292
  message.should.match(/write not called with a String/)
@@ -308,7 +294,7 @@ context "Rack::Lint" do
308
294
  lambda {
309
295
  Rack::Lint.new(lambda { |env|
310
296
  env["rack.errors"].close
311
- [201, {"Content-type" => "text/plain"}, ""]
297
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
312
298
  }).call(env({}))
313
299
  }.should.raise(Rack::Lint::LintError).
314
300
  message.should.match(/close must not be called/)
@@ -1,6 +1,8 @@
1
1
  require 'test/spec'
2
2
 
3
+ begin
3
4
  require 'rack/handler/mongrel'
5
+ require 'rack/urlmap'
4
6
  require 'rack/lint'
5
7
  require 'testrequest'
6
8
 
@@ -93,7 +95,76 @@ context "Rack::Handler::Mongrel" do
93
95
  block_ran.should.be true
94
96
  end
95
97
 
98
+ specify "should provide a .run that maps a hash" do
99
+ block_ran = false
100
+ Thread.new {
101
+ map = {'/'=>lambda{},'/foo'=>lambda{}}
102
+ Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
103
+ server.should.be.kind_of Mongrel::HttpServer
104
+ server.classifier.uris.size.should.be 2
105
+ server.classifier.uris.should.not.include '/arf'
106
+ server.classifier.uris.should.include '/'
107
+ server.classifier.uris.should.include '/foo'
108
+ block_ran = true
109
+ }
110
+ }
111
+ sleep 1
112
+ block_ran.should.be true
113
+ end
114
+
115
+ specify "should provide a .run that maps a urlmap" do
116
+ block_ran = false
117
+ Thread.new {
118
+ map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
119
+ Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
120
+ server.should.be.kind_of Mongrel::HttpServer
121
+ server.classifier.uris.size.should.be 2
122
+ server.classifier.uris.should.not.include '/arf'
123
+ server.classifier.uris.should.include '/'
124
+ server.classifier.uris.should.include '/bar'
125
+ block_ran = true
126
+ }
127
+ }
128
+ sleep 1
129
+ block_ran.should.be true
130
+ end
131
+
132
+ specify "should provide a .run that maps a urlmap restricting by host" do
133
+ block_ran = false
134
+ Thread.new {
135
+ map = Rack::URLMap.new({
136
+ '/' => lambda{},
137
+ '/foo' => lambda{},
138
+ '/bar' => lambda{},
139
+ 'http://localhost/' => lambda{},
140
+ 'http://localhost/bar' => lambda{},
141
+ 'http://falsehost/arf' => lambda{},
142
+ 'http://falsehost/qux' => lambda{}
143
+ })
144
+ opt = {:map => true, :Port => 9241, :Host => 'localhost'}
145
+ Rack::Handler::Mongrel.run(map, opt) { |server|
146
+ server.should.be.kind_of Mongrel::HttpServer
147
+ server.classifier.uris.should.include '/'
148
+ server.classifier.handler_map['/'].size.should.be 2
149
+ server.classifier.uris.should.include '/foo'
150
+ server.classifier.handler_map['/foo'].size.should.be 1
151
+ server.classifier.uris.should.include '/bar'
152
+ server.classifier.handler_map['/bar'].size.should.be 2
153
+ server.classifier.uris.should.not.include '/qux'
154
+ server.classifier.uris.should.not.include '/arf'
155
+ server.classifier.uris.size.should.be 3
156
+ block_ran = true
157
+ }
158
+ }
159
+ sleep 1
160
+ block_ran.should.be true
161
+ end
162
+
96
163
  teardown do
97
164
  @acc.raise Mongrel::StopServer
98
165
  end
99
166
  end
167
+
168
+ rescue LoadError
169
+ $stderr.puts "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again."
170
+ end