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,167 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/response'
4
+
5
+ context "Rack::Response" do
6
+ specify "has sensible default values" do
7
+ response = Rack::Response.new
8
+ status, header, body = response.finish
9
+ status.should.equal 200
10
+ header.should.equal "Content-Type" => "text/html"
11
+ body.each { |part|
12
+ part.should.equal ""
13
+ }
14
+
15
+ response = Rack::Response.new
16
+ status, header, body = *response
17
+ status.should.equal 200
18
+ header.should.equal "Content-Type" => "text/html"
19
+ body.each { |part|
20
+ part.should.equal ""
21
+ }
22
+ end
23
+
24
+ specify "can be written to" do
25
+ response = Rack::Response.new
26
+
27
+ status, header, body = response.finish do
28
+ response.write "foo"
29
+ response.write "bar"
30
+ response.write "baz"
31
+ end
32
+
33
+ parts = []
34
+ body.each { |part| parts << part }
35
+
36
+ parts.should.equal ["foo", "bar", "baz"]
37
+ end
38
+
39
+ specify "can set and read headers" do
40
+ response = Rack::Response.new
41
+ response["Content-Type"].should.equal "text/html"
42
+ response["Content-Type"] = "text/plain"
43
+ response["Content-Type"].should.equal "text/plain"
44
+ end
45
+
46
+ specify "can set cookies" do
47
+ response = Rack::Response.new
48
+
49
+ response.set_cookie "foo", "bar"
50
+ response["Set-Cookie"].should.equal "foo=bar"
51
+ response.set_cookie "foo2", "bar2"
52
+ response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"]
53
+ response.set_cookie "foo3", "bar3"
54
+ response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
55
+ end
56
+
57
+ specify "formats the Cookie expiration date accordingly to RFC 2109" do
58
+ response = Rack::Response.new
59
+
60
+ response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
61
+ response["Set-Cookie"].should.match(
62
+ /expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../)
63
+ end
64
+
65
+ specify "can delete cookies" do
66
+ response = Rack::Response.new
67
+ response.set_cookie "foo", "bar"
68
+ response.set_cookie "foo2", "bar2"
69
+ response.delete_cookie "foo"
70
+ response["Set-Cookie"].should.equal ["foo2=bar2",
71
+ "foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
72
+ end
73
+
74
+ specify "has a useful constructor" do
75
+ r = Rack::Response.new("foo")
76
+ status, header, body = r.finish
77
+ str = ""; body.each { |part| str << part }
78
+ str.should.equal "foo"
79
+
80
+ r = Rack::Response.new(["foo", "bar"])
81
+ status, header, body = r.finish
82
+ str = ""; body.each { |part| str << part }
83
+ str.should.equal "foobar"
84
+
85
+ r = Rack::Response.new({"foo", "bar"})
86
+ r.write "foo"
87
+ status, header, body = r.finish
88
+ str = ""; body.each { |part| str << part }
89
+ str.should.equal "foobarfoo"
90
+
91
+ r = Rack::Response.new([], 500)
92
+ r.status.should.equal 500
93
+ end
94
+
95
+ specify "has a constructor that can take a block" do
96
+ r = Rack::Response.new { |res|
97
+ res.status = 404
98
+ res.write "foo"
99
+ }
100
+ status, header, body = r.finish
101
+ str = ""; body.each { |part| str << part }
102
+ str.should.equal "foo"
103
+ status.should.equal 404
104
+ end
105
+
106
+ specify "doesn't return invalid responses" do
107
+ r = Rack::Response.new(["foo", "bar"], 204)
108
+ status, header, body = r.finish
109
+ str = ""; body.each { |part| str << part }
110
+ str.should.be.empty
111
+ header["Content-Type"].should.equal nil
112
+
113
+ lambda {
114
+ Rack::Response.new(Object.new)
115
+ }.should.raise(TypeError).
116
+ message.should =~ /stringable or iterable required/
117
+ end
118
+
119
+ specify "knows if it's empty" do
120
+ r = Rack::Response.new
121
+ r.should.be.empty
122
+ r.write "foo"
123
+ r.should.not.be.empty
124
+
125
+ r = Rack::Response.new
126
+ r.should.be.empty
127
+ r.finish
128
+ r.should.be.empty
129
+
130
+ r = Rack::Response.new
131
+ r.should.be.empty
132
+ r.finish { }
133
+ r.should.not.be.empty
134
+ end
135
+
136
+ specify "should provide access to the HTTP status" do
137
+ res = Rack::Response.new
138
+ res.status = 200
139
+ res.should.be.successful
140
+ res.should.be.ok
141
+
142
+ res.status = 404
143
+ res.should.not.be.successful
144
+ res.should.be.client_error
145
+ res.should.be.not_found
146
+
147
+ res.status = 501
148
+ res.should.not.be.successful
149
+ res.should.be.server_error
150
+
151
+ res.status = 307
152
+ res.should.be.redirect
153
+ end
154
+
155
+ specify "should provide access to the HTTP headers" do
156
+ res = Rack::Response.new
157
+ res["Content-Type"] = "text/yaml"
158
+
159
+ res.should.include "Content-Type"
160
+ res.headers["Content-Type"].should.equal "text/yaml"
161
+ res["Content-Type"].should.equal "text/yaml"
162
+ res.content_type.should.equal "text/yaml"
163
+ res.content_length.should.be.nil
164
+ res.location.should.be.nil
165
+ end
166
+
167
+ end
@@ -0,0 +1,49 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/session/cookie'
4
+ require 'rack/mock'
5
+ require 'rack/response'
6
+
7
+ context "Rack::Session::Cookie" do
8
+ incrementor = lambda { |env|
9
+ env["rack.session"]["counter"] ||= 0
10
+ env["rack.session"]["counter"] += 1
11
+ Rack::Response.new(env["rack.session"].inspect).to_a
12
+ }
13
+
14
+ specify "creates a new cookie" do
15
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
16
+ res["Set-Cookie"].should.match("rack.session=")
17
+ res.body.should.equal '{"counter"=>1}'
18
+ end
19
+
20
+ specify "loads from a cookie" do
21
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
22
+ cookie = res["Set-Cookie"]
23
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
24
+ get("/", "HTTP_COOKIE" => cookie)
25
+ res.body.should.equal '{"counter"=>2}'
26
+ cookie = res["Set-Cookie"]
27
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
28
+ get("/", "HTTP_COOKIE" => cookie)
29
+ res.body.should.equal '{"counter"=>3}'
30
+ end
31
+
32
+ specify "survives broken cookies" do
33
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
34
+ get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
35
+ res.body.should.equal '{"counter"=>1}'
36
+ end
37
+
38
+ bigcookie = lambda { |env|
39
+ env["rack.session"]["cookie"] = "big" * 3000
40
+ Rack::Response.new(env["rack.session"].inspect).to_a
41
+ }
42
+
43
+ specify "barks on too big cookies" do
44
+ lambda {
45
+ Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
46
+ get("/", :fatal => true)
47
+ }.should.raise(Rack::MockRequest::FatalWarning)
48
+ end
49
+ end
@@ -0,0 +1,100 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/session/memcache'
4
+ require 'rack/mock'
5
+ require 'rack/response'
6
+ require 'thread'
7
+
8
+ context "Rack::Session::Memcache" do
9
+ incrementor = lambda { |env|
10
+ env["rack.session"]["counter"] ||= 0
11
+ env["rack.session"]["counter"] += 1
12
+ Rack::Response.new(env["rack.session"].inspect).to_a
13
+ }
14
+
15
+ specify "creates a new cookie" do
16
+ cache = Rack::Session::Memcache.new(incrementor)
17
+ res = Rack::MockRequest.new(cache).get("/")
18
+ res["Set-Cookie"].should.match("rack.session=")
19
+ res.body.should.equal '{"counter"=>1}'
20
+ end
21
+
22
+ specify "determines session from a cookie" do
23
+ cache = Rack::Session::Memcache.new(incrementor)
24
+ res = Rack::MockRequest.new(cache).get("/")
25
+ cookie = res["Set-Cookie"]
26
+ res = Rack::MockRequest.new(cache).get("/", "HTTP_COOKIE" => cookie)
27
+ res.body.should.equal '{"counter"=>2}'
28
+ res = Rack::MockRequest.new(cache).get("/", "HTTP_COOKIE" => cookie)
29
+ res.body.should.equal '{"counter"=>3}'
30
+ end
31
+
32
+ specify "survives broken cookies" do
33
+ cache = Rack::Session::Memcache.new(incrementor)
34
+ res = Rack::MockRequest.new(cache).
35
+ get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
36
+ res.body.should.equal '{"counter"=>1}'
37
+ end
38
+
39
+ specify "maintains freshness" do
40
+ cache = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
41
+ res = Rack::MockRequest.new(cache).get('/')
42
+ res.body.should.include '"counter"=>1'
43
+ cookie = res["Set-Cookie"]
44
+ res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
45
+ res["Set-Cookie"].should.equal cookie
46
+ res.body.should.include '"counter"=>2'
47
+ sleep 4
48
+ res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
49
+ res["Set-Cookie"].should.not.equal cookie
50
+ res.body.should.include '"counter"=>1'
51
+ end
52
+
53
+ specify "multithread: should cleanly merge sessions" do
54
+ cache = Rack::Session::Memcache.new(incrementor)
55
+ drop_counter = Rack::Session::Memcache.new(proc do |env|
56
+ env['rack.session'].delete 'counter'
57
+ env['rack.session']['foo'] = 'bar'
58
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
59
+ end)
60
+
61
+ res = Rack::MockRequest.new(cache).get('/')
62
+ res.body.should.equal '{"counter"=>1}'
63
+ cookie = res["Set-Cookie"]
64
+ sess_id = cookie[/#{cache.key}=([^,;]+)/, 1]
65
+
66
+ res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
67
+ res.body.should.equal '{"counter"=>2}'
68
+
69
+ r = Array.new(rand(7).to_i+2) do |i|
70
+ app = proc do |env|
71
+ env['rack.session'][i] = Time.now
72
+ sleep 2
73
+ env['rack.session'] = env['rack.session'].dup
74
+ env['rack.session'][i] -= Time.now
75
+ incrementor.call(env)
76
+ end
77
+ Thread.new(cache.context(app)) do |run|
78
+ Rack::MockRequest.new(run).
79
+ get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
80
+ end
81
+ end.reverse.map{|t| t.join.value }
82
+
83
+ r.each do |res|
84
+ res['Set-Cookie'].should.equal cookie
85
+ res.body.should.include '"counter"=>3'
86
+ end
87
+
88
+ session = cache.pool[sess_id]
89
+ session.size.should.be r.size+1
90
+ session['counter'].should.be 3
91
+
92
+ res = Rack::MockRequest.new(drop_counter).get('/', "HTTP_COOKIE" => cookie)
93
+ res.body.should.include '"foo"=>"bar"'
94
+
95
+ session = cache.pool[sess_id]
96
+ session.size.should.be r.size+1
97
+ session['counter'].should.be.nil?
98
+ session['foo'].should.equal 'bar'
99
+ end
100
+ end
@@ -0,0 +1,84 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/session/pool'
4
+ require 'rack/mock'
5
+ require 'rack/response'
6
+ require 'thread'
7
+
8
+ context "Rack::Session::Pool" do
9
+ incrementor = lambda { |env|
10
+ env["rack.session"]["counter"] ||= 0
11
+ env["rack.session"]["counter"] += 1
12
+ Rack::Response.new(env["rack.session"].inspect).to_a
13
+ }
14
+
15
+ specify "creates a new cookie" do
16
+ pool = Rack::Session::Pool.new(incrementor)
17
+ res = Rack::MockRequest.new(pool).get("/")
18
+ res["Set-Cookie"].should.match("rack.session=")
19
+ res.body.should.equal '{"counter"=>1}'
20
+ end
21
+
22
+ specify "determines session from a cookie" do
23
+ pool = Rack::Session::Pool.new(incrementor)
24
+ res = Rack::MockRequest.new(pool).get("/")
25
+ cookie = res["Set-Cookie"]
26
+ res = Rack::MockRequest.new(pool).get("/", "HTTP_COOKIE" => cookie)
27
+ res.body.should.equal '{"counter"=>2}'
28
+ res = Rack::MockRequest.new(pool).get("/", "HTTP_COOKIE" => cookie)
29
+ res.body.should.equal '{"counter"=>3}'
30
+ end
31
+
32
+ specify "survives broken cookies" do
33
+ pool = Rack::Session::Pool.new(incrementor)
34
+ res = Rack::MockRequest.new(pool).
35
+ get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
36
+ res.body.should.equal '{"counter"=>1}'
37
+ end
38
+
39
+ specify "maintains freshness" do
40
+ pool = Rack::Session::Pool.new(incrementor, :expire_after => 3)
41
+ res = Rack::MockRequest.new(pool).get('/')
42
+ res.body.should.include '"counter"=>1'
43
+ cookie = res["Set-Cookie"]
44
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
45
+ res["Set-Cookie"].should.equal cookie
46
+ res.body.should.include '"counter"=>2'
47
+ sleep 4
48
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
49
+ res["Set-Cookie"].should.not.equal cookie
50
+ res.body.should.include '"counter"=>1'
51
+ end
52
+
53
+ specify "multithread: should merge sessions" do
54
+ delta_incrementor = lambda do |env|
55
+ # emulate disconjoinment of threading
56
+ env['rack.session'] = env['rack.session'].dup
57
+ Thread.stop
58
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
59
+ incrementor.call(env)
60
+ end
61
+ pool = Rack::Session::Pool.new(incrementor)
62
+ res = Rack::MockRequest.new(pool).get('/')
63
+ res.body.should.equal '{"counter"=>1}'
64
+ cookie = res["Set-Cookie"]
65
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
66
+
67
+ pool = pool.context(delta_incrementor)
68
+ r = Array.new(rand(7).to_i+3).
69
+ map! do
70
+ Thread.new do
71
+ Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
72
+ end
73
+ end.
74
+ reverse!.
75
+ map!{|t| t.run.join.value }
76
+ session = pool.for.pool[sess_id] # for is needed by Utils::Context
77
+ session.size.should.be r.size+1 # counter
78
+ session['counter'].should.be 2 # meeeh
79
+ r.each do |res|
80
+ res['Set-Cookie'].should.equal cookie
81
+ res.body.should.include '"counter"=>2'
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,21 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/showexceptions'
4
+ require 'rack/mock'
5
+
6
+ context "Rack::ShowExceptions" do
7
+ specify "catches exceptions" do
8
+ res = nil
9
+ req = Rack::MockRequest.new(Rack::ShowExceptions.new(lambda { |env|
10
+ raise RuntimeError
11
+ }))
12
+ lambda {
13
+ res = req.get("/")
14
+ }.should.not.raise
15
+ res.should.be.a.server_error
16
+ res.status.should.equal 500
17
+
18
+ res.should =~ /RuntimeError/
19
+ res.should =~ /ShowExceptions/
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/showstatus'
4
+ require 'rack/mock'
5
+
6
+ context "Rack::ShowStatus" do
7
+ specify "should provide a default status message" do
8
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
9
+ [404, {"Content-Type" => "text/plain"}, []]
10
+ }))
11
+
12
+ res = req.get("/", :lint => true)
13
+ res.should.be.not_found
14
+ res.should.be.not.empty
15
+
16
+ res["Content-Type"].should.equal("text/html")
17
+ res.should =~ /404/
18
+ res.should =~ /Not Found/
19
+ end
20
+
21
+ specify "should let the app provide additional information" do
22
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
23
+ env["rack.showstatus.detail"] = "gone too meta."
24
+ [404, {"Content-Type" => "text/plain"}, []]
25
+ }))
26
+
27
+ res = req.get("/", :lint => true)
28
+ res.should.be.not_found
29
+ res.should.be.not.empty
30
+
31
+ res["Content-Type"].should.equal("text/html")
32
+ res.should =~ /404/
33
+ res.should =~ /Not Found/
34
+ res.should =~ /too meta/
35
+ end
36
+
37
+ specify "should not replace existing messages" do
38
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
39
+ [404, {"Content-Type" => "text/plain"}, ["foo!"]]
40
+ }))
41
+ res = req.get("/", :lint => true)
42
+ res.should.be.not_found
43
+
44
+ res.body.should == "foo!"
45
+ end
46
+
47
+ specify "should pass on original headers" do
48
+ headers = {"WWW-Authenticate" => "Basic blah"}
49
+
50
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| [401, headers, []] }))
51
+ res = req.get("/", :lint => true)
52
+
53
+ res["WWW-Authenticate"].should.equal("Basic blah")
54
+ end
55
+
56
+ specify "should replace existing messages if there is detail" do
57
+ req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env|
58
+ env["rack.showstatus.detail"] = "gone too meta."
59
+ [404, {"Content-Type" => "text/plain"}, ["foo!"]]
60
+ }))
61
+
62
+ res = req.get("/", :lint => true)
63
+ res.should.be.not_found
64
+ res.should.be.not.empty
65
+
66
+ res["Content-Type"].should.equal("text/html")
67
+ res.should =~ /404/
68
+ res.should =~ /too meta/
69
+ res.body.should.not =~ /foo/
70
+ end
71
+ end