rack 0.9.1 → 1.0.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/COPYING +1 -1
- data/RDOX +115 -16
- data/README +54 -7
- data/Rakefile +61 -85
- data/SPEC +50 -17
- data/bin/rackup +9 -5
- data/example/protectedlobster.ru +1 -1
- data/lib/rack.rb +7 -3
- data/lib/rack/auth/abstract/handler.rb +13 -4
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/auth/digest/request.rb +2 -2
- data/lib/rack/auth/openid.rb +344 -302
- data/lib/rack/builder.rb +1 -5
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/conditionalget.rb +4 -0
- data/lib/rack/content_length.rb +7 -3
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +83 -74
- data/lib/rack/directory.rb +5 -2
- data/lib/rack/file.rb +4 -1
- data/lib/rack/handler.rb +22 -1
- data/lib/rack/handler/cgi.rb +7 -3
- data/lib/rack/handler/fastcgi.rb +26 -24
- data/lib/rack/handler/lsws.rb +7 -4
- data/lib/rack/handler/mongrel.rb +5 -3
- data/lib/rack/handler/scgi.rb +5 -3
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +11 -5
- data/lib/rack/lint.rb +138 -66
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/mime.rb +4 -4
- data/lib/rack/mock.rb +3 -3
- data/lib/rack/reloader.rb +88 -46
- data/lib/rack/request.rb +46 -10
- data/lib/rack/response.rb +15 -3
- data/lib/rack/rewindable_input.rb +98 -0
- data/lib/rack/session/abstract/id.rb +71 -82
- data/lib/rack/session/cookie.rb +2 -0
- data/lib/rack/session/memcache.rb +59 -47
- data/lib/rack/session/pool.rb +56 -29
- data/lib/rack/showexceptions.rb +2 -1
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/urlmap.rb +12 -5
- data/lib/rack/utils.rb +115 -65
- data/rack.gemspec +54 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/text +10 -0
- data/test/spec_rack_auth_basic.rb +5 -1
- data/test/spec_rack_auth_digest.rb +93 -36
- data/test/spec_rack_auth_openid.rb +47 -100
- data/test/spec_rack_builder.rb +2 -2
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_conditionalget.rb +7 -7
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +36 -14
- data/test/spec_rack_directory.rb +1 -1
- data/test/spec_rack_file.rb +11 -0
- data/test/spec_rack_handler.rb +21 -2
- data/test/spec_rack_lint.rb +163 -44
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_mock.rb +6 -1
- data/test/spec_rack_request.rb +81 -12
- data/test/spec_rack_response.rb +46 -2
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_memcache.rb +170 -62
- data/test/spec_rack_session_pool.rb +129 -41
- data/test/spec_rack_static.rb +2 -2
- data/test/spec_rack_thin.rb +3 -2
- data/test/spec_rack_urlmap.rb +10 -0
- data/test/spec_rack_utils.rb +214 -49
- data/test/spec_rack_webrick.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +95 -6
- data/AUTHORS +0 -8
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
|
3
|
+
require 'rack/mock'
|
4
|
+
require 'rack/lock'
|
5
|
+
|
6
|
+
context "Rack::Lock" do
|
7
|
+
class Lock
|
8
|
+
attr_reader :synchronized
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@synchronized = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def synchronize
|
15
|
+
@synchronized = true
|
16
|
+
yield
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "should call synchronize on lock" do
|
21
|
+
lock = Lock.new
|
22
|
+
env = Rack::MockRequest.env_for("/")
|
23
|
+
app = Rack::Lock.new(lambda { |env| }, lock)
|
24
|
+
lock.synchronized.should.equal false
|
25
|
+
app.call(env)
|
26
|
+
lock.synchronized.should.equal true
|
27
|
+
end
|
28
|
+
|
29
|
+
specify "should set multithread flag to false" do
|
30
|
+
app = Rack::Lock.new(lambda { |env| env['rack.multithread'] })
|
31
|
+
app.call(Rack::MockRequest.env_for("/")).should.equal false
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "should reset original multithread flag when exiting lock" do
|
35
|
+
app = Rack::Lock.new(lambda { |env| env })
|
36
|
+
app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true
|
37
|
+
end
|
38
|
+
end
|
data/test/spec_rack_mock.rb
CHANGED
@@ -64,6 +64,11 @@ context "Rack::MockRequest" do
|
|
64
64
|
should.equal "OPTIONS"
|
65
65
|
end
|
66
66
|
|
67
|
+
specify "should set content length" do
|
68
|
+
env = Rack::MockRequest.env_for("/", :input => "foo")
|
69
|
+
env["CONTENT_LENGTH"].should.equal "3"
|
70
|
+
end
|
71
|
+
|
67
72
|
specify "should allow posting" do
|
68
73
|
res = Rack::MockRequest.new(app).get("", :input => "foo")
|
69
74
|
env = YAML.load(res.body)
|
@@ -125,7 +130,7 @@ context "Rack::MockResponse" do
|
|
125
130
|
res.original_headers["Content-Type"].should.equal "text/yaml"
|
126
131
|
res["Content-Type"].should.equal "text/yaml"
|
127
132
|
res.content_type.should.equal "text/yaml"
|
128
|
-
res.content_length.should.be
|
133
|
+
res.content_length.should.be 464 # needs change often.
|
129
134
|
res.location.should.be.nil
|
130
135
|
end
|
131
136
|
|
data/test/spec_rack_request.rb
CHANGED
@@ -25,7 +25,7 @@ context "Rack::Request" do
|
|
25
25
|
req.host.should.equal "example.com"
|
26
26
|
req.port.should.equal 8080
|
27
27
|
|
28
|
-
req.content_length.should.
|
28
|
+
req.content_length.should.equal "0"
|
29
29
|
req.content_type.should.be.nil
|
30
30
|
end
|
31
31
|
|
@@ -93,14 +93,10 @@ context "Rack::Request" do
|
|
93
93
|
input.read.should.equal "foo=bar&quux=bla"
|
94
94
|
end
|
95
95
|
|
96
|
-
specify "
|
97
|
-
input = StringIO.new("foo=bar&quux=bla")
|
98
|
-
input.instance_eval "undef :rewind"
|
96
|
+
specify "cleans up Safari's ajax POST body" do
|
99
97
|
req = Rack::Request.new \
|
100
|
-
Rack::MockRequest.env_for("/",
|
101
|
-
|
102
|
-
:input => input)
|
103
|
-
req.params.should.equal "foo" => "bar", "quux" => "bla"
|
98
|
+
Rack::MockRequest.env_for("/", :input => "foo=bar&quux=bla\0")
|
99
|
+
req.POST.should.equal "foo" => "bar", "quux" => "bla"
|
104
100
|
end
|
105
101
|
|
106
102
|
specify "can get value by key from params with #[]" do
|
@@ -354,6 +350,33 @@ EOF
|
|
354
350
|
lambda { req.POST }.should.raise(EOFError)
|
355
351
|
end
|
356
352
|
|
353
|
+
specify "shouldn't try to interpret binary as utf8" do
|
354
|
+
begin
|
355
|
+
original_kcode = $KCODE
|
356
|
+
$KCODE='UTF8'
|
357
|
+
|
358
|
+
input = <<EOF
|
359
|
+
--AaB03x\r
|
360
|
+
content-disposition: form-data; name="fileupload"; filename="junk.a"\r
|
361
|
+
content-type: application/octet-stream\r
|
362
|
+
\r
|
363
|
+
#{[0x36,0xCF,0x0A,0xF8].pack('c*')}\r
|
364
|
+
--AaB03x--\r
|
365
|
+
EOF
|
366
|
+
|
367
|
+
req = Rack::Request.new Rack::MockRequest.env_for("/",
|
368
|
+
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
|
369
|
+
"CONTENT_LENGTH" => input.size,
|
370
|
+
:input => input)
|
371
|
+
|
372
|
+
lambda{req.POST}.should.not.raise(EOFError)
|
373
|
+
req.POST["fileupload"][:tempfile].size.should.equal 4
|
374
|
+
ensure
|
375
|
+
$KCODE = original_kcode
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
|
357
380
|
specify "should work around buggy 1.8.* Tempfile equality" do
|
358
381
|
input = <<EOF
|
359
382
|
--AaB03x\r
|
@@ -380,7 +403,7 @@ EOF
|
|
380
403
|
app = lambda { |env|
|
381
404
|
content = Rack::Request.new(env).POST["file"].inspect
|
382
405
|
size = content.respond_to?(:bytesize) ? content.bytesize : content.size
|
383
|
-
[200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, content]
|
406
|
+
[200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, [content]]
|
384
407
|
}
|
385
408
|
|
386
409
|
input = <<EOF
|
@@ -429,18 +452,64 @@ EOF
|
|
429
452
|
|
430
453
|
mock = Rack::MockRequest.new(Rack::Lint.new(app))
|
431
454
|
res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
|
432
|
-
res.body.should
|
455
|
+
res.body.should.equal '123.123.123.123'
|
433
456
|
|
434
457
|
res = mock.get '/',
|
435
458
|
'REMOTE_ADDR' => '123.123.123.123',
|
436
459
|
'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
|
437
460
|
|
438
|
-
res.body.should
|
461
|
+
res.body.should.equal '234.234.234.234'
|
439
462
|
|
440
463
|
res = mock.get '/',
|
441
464
|
'REMOTE_ADDR' => '123.123.123.123',
|
442
465
|
'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
|
443
466
|
|
444
|
-
res.body.should
|
467
|
+
res.body.should.equal '212.212.212.212'
|
468
|
+
end
|
469
|
+
|
470
|
+
specify "memoizes itself to reduce the cost of repetitive initialization" do
|
471
|
+
env = Rack::MockRequest.env_for("http://example.com:8080/")
|
472
|
+
env['rack.request'].should.be.nil
|
473
|
+
|
474
|
+
req1 = Rack::Request.new(env)
|
475
|
+
env['rack.request'].should.not.be.nil
|
476
|
+
req1.should.equal env['rack.request']
|
477
|
+
|
478
|
+
rack_request_object_id = env['rack.request'].object_id
|
479
|
+
|
480
|
+
req2 = Rack::Request.new(env)
|
481
|
+
env['rack.request'].should.not.be.nil
|
482
|
+
rack_request_object_id.should.be.equal env['rack.request'].object_id
|
483
|
+
req2.should.equal env['rack.request']
|
484
|
+
end
|
485
|
+
|
486
|
+
class MyRequest < Rack::Request
|
487
|
+
def params
|
488
|
+
{:foo => "bar"}
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
specify "should allow subclass request to be instantiated after parent request" do
|
493
|
+
env = Rack::MockRequest.env_for("/?foo=bar")
|
494
|
+
|
495
|
+
req1 = Rack::Request.new(env)
|
496
|
+
req1.GET.should.equal "foo" => "bar"
|
497
|
+
req1.params.should.equal "foo" => "bar"
|
498
|
+
|
499
|
+
req2 = MyRequest.new(env)
|
500
|
+
req2.GET.should.equal "foo" => "bar"
|
501
|
+
req2.params.should.equal :foo => "bar"
|
502
|
+
end
|
503
|
+
|
504
|
+
specify "should allow parent request to be instantiated after subclass request" do
|
505
|
+
env = Rack::MockRequest.env_for("/?foo=bar")
|
506
|
+
|
507
|
+
req1 = MyRequest.new(env)
|
508
|
+
req1.GET.should.equal "foo" => "bar"
|
509
|
+
req1.params.should.equal :foo => "bar"
|
510
|
+
|
511
|
+
req2 = Rack::Request.new(env)
|
512
|
+
req2.GET.should.equal "foo" => "bar"
|
513
|
+
req2.params.should.equal "foo" => "bar"
|
445
514
|
end
|
446
515
|
end
|
data/test/spec_rack_response.rb
CHANGED
@@ -8,7 +8,7 @@ context "Rack::Response" do
|
|
8
8
|
response = Rack::Response.new
|
9
9
|
status, header, body = response.finish
|
10
10
|
status.should.equal 200
|
11
|
-
header.should.equal "Content-Type" => "text/html"
|
11
|
+
header.should.equal "Content-Type" => "text/html"
|
12
12
|
body.each { |part|
|
13
13
|
part.should.equal ""
|
14
14
|
}
|
@@ -16,7 +16,7 @@ context "Rack::Response" do
|
|
16
16
|
response = Rack::Response.new
|
17
17
|
status, header, body = *response
|
18
18
|
status.should.equal 200
|
19
|
-
header.should.equal "Content-Type" => "text/html"
|
19
|
+
header.should.equal "Content-Type" => "text/html"
|
20
20
|
body.each { |part|
|
21
21
|
part.should.equal ""
|
22
22
|
}
|
@@ -69,6 +69,12 @@ context "Rack::Response" do
|
|
69
69
|
response["Set-Cookie"].should.equal "foo=bar; secure"
|
70
70
|
end
|
71
71
|
|
72
|
+
specify "can set http only cookies" do
|
73
|
+
response = Rack::Response.new
|
74
|
+
response.set_cookie "foo", {:value => "bar", :httponly => true}
|
75
|
+
response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
|
76
|
+
end
|
77
|
+
|
72
78
|
specify "can delete cookies" do
|
73
79
|
response = Rack::Response.new
|
74
80
|
response.set_cookie "foo", "bar"
|
@@ -78,6 +84,21 @@ context "Rack::Response" do
|
|
78
84
|
"foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
|
79
85
|
end
|
80
86
|
|
87
|
+
specify "can do redirects" do
|
88
|
+
response = Rack::Response.new
|
89
|
+
response.redirect "/foo"
|
90
|
+
status, header, body = response.finish
|
91
|
+
|
92
|
+
status.should.equal 302
|
93
|
+
header["Location"].should.equal "/foo"
|
94
|
+
|
95
|
+
response = Rack::Response.new
|
96
|
+
response.redirect "/foo", 307
|
97
|
+
status, header, body = response.finish
|
98
|
+
|
99
|
+
status.should.equal 307
|
100
|
+
end
|
101
|
+
|
81
102
|
specify "has a useful constructor" do
|
82
103
|
r = Rack::Response.new("foo")
|
83
104
|
status, header, body = r.finish
|
@@ -171,4 +192,27 @@ context "Rack::Response" do
|
|
171
192
|
res.location.should.be.nil
|
172
193
|
end
|
173
194
|
|
195
|
+
specify "does not add or change Content-Length when #finish()ing" do
|
196
|
+
res = Rack::Response.new
|
197
|
+
res.status = 200
|
198
|
+
res.finish
|
199
|
+
res.headers["Content-Length"].should.be.nil
|
200
|
+
|
201
|
+
res = Rack::Response.new
|
202
|
+
res.status = 200
|
203
|
+
res.headers["Content-Length"] = "10"
|
204
|
+
res.finish
|
205
|
+
res.headers["Content-Length"].should.equal "10"
|
206
|
+
end
|
207
|
+
|
208
|
+
specify "updates Content-Length when body appended to using #write" do
|
209
|
+
res = Rack::Response.new
|
210
|
+
res.status = 200
|
211
|
+
res.headers["Content-Length"].should.be.nil
|
212
|
+
res.write "Hi"
|
213
|
+
res.headers["Content-Length"].should.equal "2"
|
214
|
+
res.write " there"
|
215
|
+
res.headers["Content-Length"].should.equal "8"
|
216
|
+
end
|
217
|
+
|
174
218
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'test/spec'
|
2
|
+
require 'stringio'
|
3
|
+
require 'rack/rewindable_input'
|
4
|
+
|
5
|
+
shared_context "a rewindable IO object" do
|
6
|
+
setup do
|
7
|
+
@rio = Rack::RewindableInput.new(@io)
|
8
|
+
end
|
9
|
+
|
10
|
+
teardown do
|
11
|
+
@rio.close
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "should be able to handle to read()" do
|
15
|
+
@rio.read.should.equal "hello world"
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "should be able to handle to read(nil)" do
|
19
|
+
@rio.read(nil).should.equal "hello world"
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "should be able to handle to read(length)" do
|
23
|
+
@rio.read(1).should.equal "h"
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "should be able to handle to read(length, buffer)" do
|
27
|
+
buffer = ""
|
28
|
+
result = @rio.read(1, buffer)
|
29
|
+
result.should.equal "h"
|
30
|
+
result.object_id.should.equal buffer.object_id
|
31
|
+
end
|
32
|
+
|
33
|
+
specify "should be able to handle to read(nil, buffer)" do
|
34
|
+
buffer = ""
|
35
|
+
result = @rio.read(nil, buffer)
|
36
|
+
result.should.equal "hello world"
|
37
|
+
result.object_id.should.equal buffer.object_id
|
38
|
+
end
|
39
|
+
|
40
|
+
specify "should rewind to the beginning when #rewind is called" do
|
41
|
+
@rio.read(1)
|
42
|
+
@rio.rewind
|
43
|
+
@rio.read.should.equal "hello world"
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "should be able to handle gets" do
|
47
|
+
@rio.gets.should == "hello world"
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "should be able to handle each" do
|
51
|
+
array = []
|
52
|
+
@rio.each do |data|
|
53
|
+
array << data
|
54
|
+
end
|
55
|
+
array.should.equal(["hello world"])
|
56
|
+
end
|
57
|
+
|
58
|
+
specify "should not buffer into a Tempfile if no data has been read yet" do
|
59
|
+
@rio.instance_variable_get(:@rewindable_io).should.be.nil
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "should buffer into a Tempfile when data has been consumed for the first time" do
|
63
|
+
@rio.read(1)
|
64
|
+
tempfile = @rio.instance_variable_get(:@rewindable_io)
|
65
|
+
tempfile.should.not.be.nil
|
66
|
+
@rio.read(1)
|
67
|
+
tempfile2 = @rio.instance_variable_get(:@rewindable_io)
|
68
|
+
tempfile2.should.equal tempfile
|
69
|
+
end
|
70
|
+
|
71
|
+
specify "should close the underlying tempfile upon calling #close" do
|
72
|
+
@rio.read(1)
|
73
|
+
tempfile = @rio.instance_variable_get(:@rewindable_io)
|
74
|
+
@rio.close
|
75
|
+
tempfile.should.be.closed
|
76
|
+
end
|
77
|
+
|
78
|
+
specify "should be possibel to call #close when no data has been buffered yet" do
|
79
|
+
@rio.close
|
80
|
+
end
|
81
|
+
|
82
|
+
specify "should be possible to call #close multiple times" do
|
83
|
+
@rio.close
|
84
|
+
@rio.close
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "Rack::RewindableInput" do
|
89
|
+
context "given an IO object that is already rewindable" do
|
90
|
+
setup do
|
91
|
+
@io = StringIO.new("hello world")
|
92
|
+
end
|
93
|
+
|
94
|
+
it_should_behave_like "a rewindable IO object"
|
95
|
+
end
|
96
|
+
|
97
|
+
context "given an IO object that is not rewindable" do
|
98
|
+
setup do
|
99
|
+
@io = StringIO.new("hello world")
|
100
|
+
@io.instance_eval do
|
101
|
+
undef :rewind
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it_should_behave_like "a rewindable IO object"
|
106
|
+
end
|
107
|
+
|
108
|
+
context "given an IO object whose rewind method raises Errno::ESPIPE" do
|
109
|
+
setup do
|
110
|
+
@io = StringIO.new("hello world")
|
111
|
+
def @io.rewind
|
112
|
+
raise Errno::ESPIPE, "You can't rewind this!"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it_should_behave_like "a rewindable IO object"
|
117
|
+
end
|
118
|
+
end
|
@@ -7,18 +7,28 @@ begin
|
|
7
7
|
require 'thread'
|
8
8
|
|
9
9
|
context "Rack::Session::Memcache" do
|
10
|
-
|
10
|
+
session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key]
|
11
|
+
session_match = /#{session_key}=[0-9a-fA-F]+;/
|
12
|
+
incrementor = lambda do |env|
|
11
13
|
env["rack.session"]["counter"] ||= 0
|
12
14
|
env["rack.session"]["counter"] += 1
|
13
15
|
Rack::Response.new(env["rack.session"].inspect).to_a
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
end
|
17
|
+
drop_session = proc do |env|
|
18
|
+
env['rack.session.options'][:drop] = true
|
19
|
+
incrementor.call(env)
|
20
|
+
end
|
21
|
+
renew_session = proc do |env|
|
22
|
+
env['rack.session.options'][:renew] = true
|
23
|
+
incrementor.call(env)
|
24
|
+
end
|
25
|
+
defer_session = proc do |env|
|
26
|
+
env['rack.session.options'][:defer] = true
|
27
|
+
incrementor.call(env)
|
28
|
+
end
|
29
|
+
|
30
|
+
specify "MemCache can connect to existing server" do
|
31
|
+
test_pool = MemCache.new :namespace => 'test:rack:session'
|
22
32
|
end
|
23
33
|
|
24
34
|
specify "faults on no connection" do
|
@@ -28,104 +38,202 @@ begin
|
|
28
38
|
end
|
29
39
|
|
30
40
|
specify "creates a new cookie" do
|
31
|
-
|
32
|
-
res = Rack::MockRequest.new(
|
33
|
-
res["Set-Cookie"].should.match("
|
41
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
42
|
+
res = Rack::MockRequest.new(pool).get("/")
|
43
|
+
res["Set-Cookie"].should.match("#{session_key}=")
|
34
44
|
res.body.should.equal '{"counter"=>1}'
|
35
45
|
end
|
36
46
|
|
37
47
|
specify "determines session from a cookie" do
|
38
|
-
|
39
|
-
|
48
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
49
|
+
req = Rack::MockRequest.new(pool)
|
50
|
+
res = req.get("/")
|
40
51
|
cookie = res["Set-Cookie"]
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
53
|
+
body.should.equal '{"counter"=>2}'
|
54
|
+
req.get("/", "HTTP_COOKIE" => cookie).
|
55
|
+
body.should.equal '{"counter"=>3}'
|
45
56
|
end
|
46
57
|
|
47
|
-
specify "survives
|
48
|
-
|
49
|
-
|
50
|
-
|
58
|
+
specify "survives nonexistant cookies" do
|
59
|
+
bad_cookie = "rack.session=blarghfasel"
|
60
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
61
|
+
res = Rack::MockRequest.new(pool).
|
62
|
+
get("/", "HTTP_COOKIE" => bad_cookie)
|
51
63
|
res.body.should.equal '{"counter"=>1}'
|
64
|
+
cookie = res["Set-Cookie"][session_match]
|
65
|
+
cookie.should.not.match(/#{bad_cookie}/)
|
52
66
|
end
|
53
67
|
|
54
68
|
specify "maintains freshness" do
|
55
|
-
|
56
|
-
res = Rack::MockRequest.new(
|
69
|
+
pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
|
70
|
+
res = Rack::MockRequest.new(pool).get('/')
|
57
71
|
res.body.should.include '"counter"=>1'
|
58
72
|
cookie = res["Set-Cookie"]
|
59
|
-
res = Rack::MockRequest.new(
|
73
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
|
60
74
|
res["Set-Cookie"].should.equal cookie
|
61
75
|
res.body.should.include '"counter"=>2'
|
62
76
|
puts 'Sleeping to expire session' if $DEBUG
|
63
77
|
sleep 4
|
64
|
-
res = Rack::MockRequest.new(
|
78
|
+
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
|
65
79
|
res["Set-Cookie"].should.not.equal cookie
|
66
80
|
res.body.should.include '"counter"=>1'
|
67
81
|
end
|
68
82
|
|
83
|
+
specify "deletes cookies with :drop option" do
|
84
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
85
|
+
req = Rack::MockRequest.new(pool)
|
86
|
+
drop = Rack::Utils::Context.new(pool, drop_session)
|
87
|
+
dreq = Rack::MockRequest.new(drop)
|
88
|
+
|
89
|
+
res0 = req.get("/")
|
90
|
+
session = (cookie = res0["Set-Cookie"])[session_match]
|
91
|
+
res0.body.should.equal '{"counter"=>1}'
|
92
|
+
|
93
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
94
|
+
res1["Set-Cookie"][session_match].should.equal session
|
95
|
+
res1.body.should.equal '{"counter"=>2}'
|
96
|
+
|
97
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
98
|
+
res2["Set-Cookie"].should.equal nil
|
99
|
+
res2.body.should.equal '{"counter"=>3}'
|
100
|
+
|
101
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
102
|
+
res3["Set-Cookie"][session_match].should.not.equal session
|
103
|
+
res3.body.should.equal '{"counter"=>1}'
|
104
|
+
end
|
105
|
+
|
106
|
+
specify "provides new session id with :renew option" do
|
107
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
108
|
+
req = Rack::MockRequest.new(pool)
|
109
|
+
renew = Rack::Utils::Context.new(pool, renew_session)
|
110
|
+
rreq = Rack::MockRequest.new(renew)
|
111
|
+
|
112
|
+
res0 = req.get("/")
|
113
|
+
session = (cookie = res0["Set-Cookie"])[session_match]
|
114
|
+
res0.body.should.equal '{"counter"=>1}'
|
115
|
+
|
116
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
117
|
+
res1["Set-Cookie"][session_match].should.equal session
|
118
|
+
res1.body.should.equal '{"counter"=>2}'
|
119
|
+
|
120
|
+
res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
|
121
|
+
new_cookie = res2["Set-Cookie"]
|
122
|
+
new_session = new_cookie[session_match]
|
123
|
+
new_session.should.not.equal session
|
124
|
+
res2.body.should.equal '{"counter"=>3}'
|
125
|
+
|
126
|
+
res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
|
127
|
+
res3["Set-Cookie"][session_match].should.equal new_session
|
128
|
+
res3.body.should.equal '{"counter"=>4}'
|
129
|
+
end
|
130
|
+
|
131
|
+
specify "omits cookie with :defer option" do
|
132
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
133
|
+
req = Rack::MockRequest.new(pool)
|
134
|
+
defer = Rack::Utils::Context.new(pool, defer_session)
|
135
|
+
dreq = Rack::MockRequest.new(defer)
|
136
|
+
|
137
|
+
res0 = req.get("/")
|
138
|
+
session = (cookie = res0["Set-Cookie"])[session_match]
|
139
|
+
res0.body.should.equal '{"counter"=>1}'
|
140
|
+
|
141
|
+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
|
142
|
+
res1["Set-Cookie"][session_match].should.equal session
|
143
|
+
res1.body.should.equal '{"counter"=>2}'
|
144
|
+
|
145
|
+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
|
146
|
+
res2["Set-Cookie"].should.equal nil
|
147
|
+
res2.body.should.equal '{"counter"=>3}'
|
148
|
+
|
149
|
+
res3 = req.get("/", "HTTP_COOKIE" => cookie)
|
150
|
+
res3["Set-Cookie"][session_match].should.equal session
|
151
|
+
res3.body.should.equal '{"counter"=>4}'
|
152
|
+
end
|
153
|
+
|
154
|
+
# anyone know how to do this better?
|
69
155
|
specify "multithread: should cleanly merge sessions" do
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
|
75
|
-
end)
|
156
|
+
next unless $DEBUG
|
157
|
+
warn 'Running multithread test for Session::Memcache'
|
158
|
+
pool = Rack::Session::Memcache.new(incrementor)
|
159
|
+
req = Rack::MockRequest.new(pool)
|
76
160
|
|
77
|
-
res =
|
161
|
+
res = req.get('/')
|
78
162
|
res.body.should.equal '{"counter"=>1}'
|
79
163
|
cookie = res["Set-Cookie"]
|
80
|
-
sess_id = cookie[/#{
|
164
|
+
sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
|
165
|
+
|
166
|
+
delta_incrementor = lambda do |env|
|
167
|
+
# emulate disconjoinment of threading
|
168
|
+
env['rack.session'] = env['rack.session'].dup
|
169
|
+
Thread.stop
|
170
|
+
env['rack.session'][(Time.now.usec*rand).to_i] = true
|
171
|
+
incrementor.call(env)
|
172
|
+
end
|
173
|
+
tses = Rack::Utils::Context.new pool, delta_incrementor
|
174
|
+
treq = Rack::MockRequest.new(tses)
|
175
|
+
tnum = rand(7).to_i+5
|
176
|
+
r = Array.new(tnum) do
|
177
|
+
Thread.new(treq) do |run|
|
178
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
179
|
+
end
|
180
|
+
end.reverse.map{|t| t.run.join.value }
|
181
|
+
r.each do |res|
|
182
|
+
res['Set-Cookie'].should.equal cookie
|
183
|
+
res.body.should.include '"counter"=>2'
|
184
|
+
end
|
81
185
|
|
82
|
-
|
83
|
-
|
186
|
+
session = pool.pool.get(sess_id)
|
187
|
+
session.size.should.be tnum+1 # counter
|
188
|
+
session['counter'].should.be 2 # meeeh
|
84
189
|
|
85
|
-
|
86
|
-
|
190
|
+
tnum = rand(7).to_i+5
|
191
|
+
r = Array.new(tnum) do |i|
|
192
|
+
delta_time = proc do |env|
|
87
193
|
env['rack.session'][i] = Time.now
|
88
|
-
|
194
|
+
Thread.stop
|
89
195
|
env['rack.session'] = env['rack.session'].dup
|
90
196
|
env['rack.session'][i] -= Time.now
|
91
197
|
incrementor.call(env)
|
92
198
|
end
|
93
|
-
|
94
|
-
|
95
|
-
|
199
|
+
app = Rack::Utils::Context.new pool, time_delta
|
200
|
+
req = Rack::MockRequest.new app
|
201
|
+
Thread.new(req) do |run|
|
202
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
96
203
|
end
|
97
|
-
end
|
98
|
-
|
99
|
-
r.reverse!
|
100
|
-
|
101
|
-
r.map! do |t|
|
102
|
-
p t if $DEBUG
|
103
|
-
t.join.value
|
104
|
-
end
|
105
|
-
|
204
|
+
end.reverse.map{|t| t.run.join.value }
|
106
205
|
r.each do |res|
|
107
206
|
res['Set-Cookie'].should.equal cookie
|
108
207
|
res.body.should.include '"counter"=>3'
|
109
208
|
end
|
110
209
|
|
111
|
-
session =
|
112
|
-
session.size.should.be
|
210
|
+
session = pool.pool.get(sess_id)
|
211
|
+
session.size.should.be tnum+1
|
113
212
|
session['counter'].should.be 3
|
114
213
|
|
115
|
-
|
116
|
-
|
214
|
+
drop_counter = proc do |env|
|
215
|
+
env['rack.session'].delete 'counter'
|
216
|
+
env['rack.session']['foo'] = 'bar'
|
217
|
+
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
|
218
|
+
end
|
219
|
+
tses = Rack::Utils::Context.new pool, drop_counter
|
220
|
+
treq = Rack::MockRequest.new(tses)
|
221
|
+
tnum = rand(7).to_i+5
|
222
|
+
r = Array.new(tnum) do
|
223
|
+
Thread.new(treq) do |run|
|
224
|
+
run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
|
225
|
+
end
|
226
|
+
end.reverse.map{|t| t.run.join.value }
|
227
|
+
r.each do |res|
|
228
|
+
res['Set-Cookie'].should.equal cookie
|
229
|
+
res.body.should.include '"foo"=>"bar"'
|
230
|
+
end
|
117
231
|
|
118
|
-
session =
|
232
|
+
session = pool.pool.get(sess_id)
|
119
233
|
session.size.should.be r.size+1
|
120
234
|
session['counter'].should.be.nil?
|
121
235
|
session['foo'].should.equal 'bar'
|
122
236
|
end
|
123
|
-
|
124
|
-
# Keep this last.
|
125
|
-
specify "shutdown" do
|
126
|
-
Process.kill 15, $pid
|
127
|
-
Process.wait($pid).should.equal $pid
|
128
|
-
end
|
129
237
|
end
|
130
238
|
rescue LoadError
|
131
239
|
$stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again."
|