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.

Files changed (79) hide show
  1. data/COPYING +1 -1
  2. data/RDOX +115 -16
  3. data/README +54 -7
  4. data/Rakefile +61 -85
  5. data/SPEC +50 -17
  6. data/bin/rackup +9 -5
  7. data/example/protectedlobster.ru +1 -1
  8. data/lib/rack.rb +7 -3
  9. data/lib/rack/auth/abstract/handler.rb +13 -4
  10. data/lib/rack/auth/digest/md5.rb +1 -1
  11. data/lib/rack/auth/digest/request.rb +2 -2
  12. data/lib/rack/auth/openid.rb +344 -302
  13. data/lib/rack/builder.rb +1 -5
  14. data/lib/rack/chunked.rb +49 -0
  15. data/lib/rack/conditionalget.rb +4 -0
  16. data/lib/rack/content_length.rb +7 -3
  17. data/lib/rack/content_type.rb +23 -0
  18. data/lib/rack/deflater.rb +83 -74
  19. data/lib/rack/directory.rb +5 -2
  20. data/lib/rack/file.rb +4 -1
  21. data/lib/rack/handler.rb +22 -1
  22. data/lib/rack/handler/cgi.rb +7 -3
  23. data/lib/rack/handler/fastcgi.rb +26 -24
  24. data/lib/rack/handler/lsws.rb +7 -4
  25. data/lib/rack/handler/mongrel.rb +5 -3
  26. data/lib/rack/handler/scgi.rb +5 -3
  27. data/lib/rack/handler/thin.rb +3 -0
  28. data/lib/rack/handler/webrick.rb +11 -5
  29. data/lib/rack/lint.rb +138 -66
  30. data/lib/rack/lock.rb +16 -0
  31. data/lib/rack/mime.rb +4 -4
  32. data/lib/rack/mock.rb +3 -3
  33. data/lib/rack/reloader.rb +88 -46
  34. data/lib/rack/request.rb +46 -10
  35. data/lib/rack/response.rb +15 -3
  36. data/lib/rack/rewindable_input.rb +98 -0
  37. data/lib/rack/session/abstract/id.rb +71 -82
  38. data/lib/rack/session/cookie.rb +2 -0
  39. data/lib/rack/session/memcache.rb +59 -47
  40. data/lib/rack/session/pool.rb +56 -29
  41. data/lib/rack/showexceptions.rb +2 -1
  42. data/lib/rack/showstatus.rb +1 -1
  43. data/lib/rack/urlmap.rb +12 -5
  44. data/lib/rack/utils.rb +115 -65
  45. data/rack.gemspec +54 -0
  46. data/test/multipart/binary +0 -0
  47. data/test/multipart/empty +10 -0
  48. data/test/multipart/ie +6 -0
  49. data/test/multipart/nested +10 -0
  50. data/test/multipart/none +9 -0
  51. data/test/multipart/text +10 -0
  52. data/test/spec_rack_auth_basic.rb +5 -1
  53. data/test/spec_rack_auth_digest.rb +93 -36
  54. data/test/spec_rack_auth_openid.rb +47 -100
  55. data/test/spec_rack_builder.rb +2 -2
  56. data/test/spec_rack_chunked.rb +62 -0
  57. data/test/spec_rack_conditionalget.rb +7 -7
  58. data/test/spec_rack_content_type.rb +30 -0
  59. data/test/spec_rack_deflater.rb +36 -14
  60. data/test/spec_rack_directory.rb +1 -1
  61. data/test/spec_rack_file.rb +11 -0
  62. data/test/spec_rack_handler.rb +21 -2
  63. data/test/spec_rack_lint.rb +163 -44
  64. data/test/spec_rack_lock.rb +38 -0
  65. data/test/spec_rack_mock.rb +6 -1
  66. data/test/spec_rack_request.rb +81 -12
  67. data/test/spec_rack_response.rb +46 -2
  68. data/test/spec_rack_rewindable_input.rb +118 -0
  69. data/test/spec_rack_session_memcache.rb +170 -62
  70. data/test/spec_rack_session_pool.rb +129 -41
  71. data/test/spec_rack_static.rb +2 -2
  72. data/test/spec_rack_thin.rb +3 -2
  73. data/test/spec_rack_urlmap.rb +10 -0
  74. data/test/spec_rack_utils.rb +214 -49
  75. data/test/spec_rack_webrick.rb +7 -0
  76. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  77. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  78. metadata +95 -6
  79. 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
@@ -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 381 # needs change often.
133
+ res.content_length.should.be 464 # needs change often.
129
134
  res.location.should.be.nil
130
135
  end
131
136
 
@@ -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.be.nil
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 "does not rewind unwindable CGI input" do
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
- "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
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 == '123.123.123.123'
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 == '234.234.234.234'
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 == '212.212.212.212'
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
@@ -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", "Content-Length" => "0"
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", "Content-Length" => "0"
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
- incrementor = lambda { |env|
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
- # Keep this first.
17
- specify "startup" do
18
- $pid = fork {
19
- exec "memcached"
20
- }
21
- sleep 1
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
- cache = Rack::Session::Memcache.new(incrementor)
32
- res = Rack::MockRequest.new(cache).get("/")
33
- res["Set-Cookie"].should.match("rack.session=")
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
- cache = Rack::Session::Memcache.new(incrementor)
39
- res = Rack::MockRequest.new(cache).get("/")
48
+ pool = Rack::Session::Memcache.new(incrementor)
49
+ req = Rack::MockRequest.new(pool)
50
+ res = req.get("/")
40
51
  cookie = res["Set-Cookie"]
41
- res = Rack::MockRequest.new(cache).get("/", "HTTP_COOKIE" => cookie)
42
- res.body.should.equal '{"counter"=>2}'
43
- res = Rack::MockRequest.new(cache).get("/", "HTTP_COOKIE" => cookie)
44
- res.body.should.equal '{"counter"=>3}'
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 broken cookies" do
48
- cache = Rack::Session::Memcache.new(incrementor)
49
- res = Rack::MockRequest.new(cache).
50
- get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
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
- cache = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
56
- res = Rack::MockRequest.new(cache).get('/')
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(cache).get('/', "HTTP_COOKIE" => cookie)
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(cache).get('/', "HTTP_COOKIE" => cookie)
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
- cache = Rack::Session::Memcache.new(incrementor)
71
- drop_counter = Rack::Session::Memcache.new(proc do |env|
72
- env['rack.session'].delete 'counter'
73
- env['rack.session']['foo'] = 'bar'
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 = Rack::MockRequest.new(cache).get('/')
161
+ res = req.get('/')
78
162
  res.body.should.equal '{"counter"=>1}'
79
163
  cookie = res["Set-Cookie"]
80
- sess_id = cookie[/#{cache.key}=([^,;]+)/, 1]
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
- res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
83
- res.body.should.equal '{"counter"=>2}'
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
- r = Array.new(rand(7).to_i+2) do |i|
86
- app = proc do |env|
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
- sleep 2
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
- Thread.new(cache.context(app)) do |run|
94
- Rack::MockRequest.new(run).
95
- get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
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 = cache.pool[sess_id]
112
- session.size.should.be r.size+1
210
+ session = pool.pool.get(sess_id)
211
+ session.size.should.be tnum+1
113
212
  session['counter'].should.be 3
114
213
 
115
- res = Rack::MockRequest.new(drop_counter).get('/', "HTTP_COOKIE" => cookie)
116
- res.body.should.include '"foo"=>"bar"'
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 = cache.pool[sess_id]
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."