rack 0.4.0 → 0.9.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 (59) hide show
  1. data/RDOX +61 -3
  2. data/README +52 -37
  3. data/Rakefile +9 -0
  4. data/SPEC +6 -3
  5. data/bin/rackup +0 -0
  6. data/lib/rack.rb +7 -2
  7. data/lib/rack/adapter/camping.rb +1 -1
  8. data/lib/rack/auth/openid.rb +4 -3
  9. data/lib/rack/builder.rb +12 -1
  10. data/lib/rack/conditionalget.rb +43 -0
  11. data/lib/rack/content_length.rb +25 -0
  12. data/lib/rack/deflater.rb +29 -5
  13. data/lib/rack/directory.rb +82 -91
  14. data/lib/rack/file.rb +45 -76
  15. data/lib/rack/handler.rb +4 -0
  16. data/lib/rack/handler/evented_mongrel.rb +1 -1
  17. data/lib/rack/handler/fastcgi.rb +2 -0
  18. data/lib/rack/handler/mongrel.rb +6 -2
  19. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  20. data/lib/rack/handler/thin.rb +15 -0
  21. data/lib/rack/handler/webrick.rb +8 -4
  22. data/lib/rack/head.rb +19 -0
  23. data/lib/rack/lint.rb +74 -10
  24. data/lib/rack/lobster.rb +13 -13
  25. data/lib/rack/methodoverride.rb +27 -0
  26. data/lib/rack/mime.rb +204 -0
  27. data/lib/rack/request.rb +10 -1
  28. data/lib/rack/response.rb +7 -2
  29. data/lib/rack/session/abstract/id.rb +14 -1
  30. data/lib/rack/session/cookie.rb +19 -1
  31. data/lib/rack/session/memcache.rb +1 -1
  32. data/lib/rack/session/pool.rb +1 -1
  33. data/lib/rack/showexceptions.rb +5 -1
  34. data/lib/rack/showstatus.rb +3 -2
  35. data/lib/rack/urlmap.rb +1 -1
  36. data/lib/rack/utils.rb +42 -13
  37. data/test/cgi/lighttpd.conf +1 -1
  38. data/test/cgi/test +0 -0
  39. data/test/cgi/test.fcgi +0 -0
  40. data/test/cgi/test.ru +0 -0
  41. data/test/spec_rack_builder.rb +34 -0
  42. data/test/spec_rack_conditionalget.rb +41 -0
  43. data/test/spec_rack_content_length.rb +43 -0
  44. data/test/spec_rack_deflater.rb +49 -14
  45. data/test/spec_rack_file.rb +7 -0
  46. data/test/spec_rack_handler.rb +3 -3
  47. data/test/spec_rack_head.rb +30 -0
  48. data/test/spec_rack_lint.rb +79 -2
  49. data/test/spec_rack_methodoverride.rb +60 -0
  50. data/test/spec_rack_mock.rb +1 -1
  51. data/test/spec_rack_mongrel.rb +20 -1
  52. data/test/spec_rack_request.rb +46 -1
  53. data/test/spec_rack_response.rb +10 -3
  54. data/test/spec_rack_session_cookie.rb +33 -0
  55. data/test/spec_rack_thin.rb +90 -0
  56. data/test/spec_rack_utils.rb +20 -18
  57. data/test/spec_rack_webrick.rb +17 -0
  58. data/test/testrequest.rb +12 -0
  59. metadata +91 -5
@@ -0,0 +1,43 @@
1
+ require 'rack/mock'
2
+ require 'rack/content_length'
3
+
4
+ context "Rack::ContentLength" do
5
+ specify "sets Content-Length on String bodies if none is set" do
6
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
7
+ response = Rack::ContentLength.new(app).call({})
8
+ response[1]['Content-Length'].should.equal '13'
9
+ end
10
+
11
+ specify "sets Content-Length on Array bodies if none is set" do
12
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
13
+ response = Rack::ContentLength.new(app).call({})
14
+ response[1]['Content-Length'].should.equal '13'
15
+ end
16
+
17
+ specify "does not set Content-Length on variable length bodies" do
18
+ body = lambda { "Hello World!" }
19
+ def body.each ; yield call ; end
20
+
21
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
22
+ response = Rack::ContentLength.new(app).call({})
23
+ response[1]['Content-Length'].should.be.nil
24
+ end
25
+
26
+ specify "does not change Content-Length if it is already set" do
27
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
28
+ response = Rack::ContentLength.new(app).call({})
29
+ response[1]['Content-Length'].should.equal '1'
30
+ end
31
+
32
+ specify "does not set Content-Length on 304 responses" do
33
+ app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] }
34
+ response = Rack::ContentLength.new(app).call({})
35
+ response[1]['Content-Length'].should.equal nil
36
+ end
37
+
38
+ specify "does not set Content-Length when Transfer-Encoding is chunked" do
39
+ app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] }
40
+ response = Rack::ContentLength.new(app).call({})
41
+ response[1]['Content-Length'].should.equal nil
42
+ end
43
+ end
@@ -3,10 +3,11 @@ require 'test/spec'
3
3
  require 'rack/mock'
4
4
  require 'rack/deflater'
5
5
  require 'stringio'
6
+ require 'time' # for Time#httpdate
6
7
 
7
8
  context "Rack::Deflater" do
8
- def build_response(body, accept_encoding, headers = {})
9
- app = lambda { |env| [200, {}, body] }
9
+ def build_response(status, body, accept_encoding, headers = {})
10
+ app = lambda { |env| [status, {}, body] }
10
11
  request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
11
12
  response = Rack::Deflater.new(app).call(request)
12
13
 
@@ -17,19 +18,19 @@ context "Rack::Deflater" do
17
18
  body = Object.new
18
19
  class << body; def each; yield("foo"); yield("bar"); end; end
19
20
 
20
- response = build_response(body, "deflate")
21
+ response = build_response(200, body, "deflate")
21
22
 
22
23
  response[0].should.equal(200)
23
- response[1].should.equal({ "Content-Encoding" => "deflate" })
24
+ response[1].should.equal({ "Content-Encoding" => "deflate", "Vary" => "Accept-Encoding" })
24
25
  response[2].to_s.should.equal("K\313\317OJ,\002\000")
25
26
  end
26
27
 
27
28
  # TODO: This is really just a special case of the above...
28
29
  specify "should be able to deflate String bodies" do
29
- response = build_response("Hello world!", "deflate")
30
+ response = build_response(200, "Hello world!", "deflate")
30
31
 
31
32
  response[0].should.equal(200)
32
- response[1].should.equal({ "Content-Encoding" => "deflate" })
33
+ response[1].should.equal({ "Content-Encoding" => "deflate", "Vary" => "Accept-Encoding" })
33
34
  response[2].to_s.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
34
35
  end
35
36
 
@@ -37,10 +38,10 @@ context "Rack::Deflater" do
37
38
  body = Object.new
38
39
  class << body; def each; yield("foo"); yield("bar"); end; end
39
40
 
40
- response = build_response(body, "gzip")
41
+ response = build_response(200, body, "gzip")
41
42
 
42
43
  response[0].should.equal(200)
43
- response[1].should.equal({ "Content-Encoding" => "gzip" })
44
+ response[1].should.equal({ "Content-Encoding" => "gzip", "Vary" => "Accept-Encoding" })
44
45
 
45
46
  io = StringIO.new(response[2].to_s)
46
47
  gz = Zlib::GzipReader.new(io)
@@ -49,22 +50,56 @@ context "Rack::Deflater" do
49
50
  end
50
51
 
51
52
  specify "should be able to fallback to no deflation" do
52
- response = build_response("Hello world!", "superzip")
53
+ response = build_response(200, "Hello world!", "superzip")
53
54
 
54
55
  response[0].should.equal(200)
55
- response[1].should.equal({})
56
+ response[1].should.equal({ "Vary" => "Accept-Encoding" })
56
57
  response[2].should.equal("Hello world!")
57
58
  end
58
59
 
60
+ specify "should be able to skip when there is no response entity body" do
61
+ response = build_response(304, [], "gzip")
62
+
63
+ response[0].should.equal(304)
64
+ response[1].should.equal({})
65
+ response[2].should.equal([])
66
+ end
67
+
59
68
  specify "should handle the lack of an acceptable encoding" do
60
- response1 = build_response("Hello world!", "identity;q=0", "PATH_INFO" => "/")
69
+ response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
61
70
  response1[0].should.equal(406)
62
71
  response1[1].should.equal({"Content-Type" => "text/plain"})
63
- response1[2].should.equal("An acceptable encoding for the requested resource / could not be found.")
72
+ response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])
64
73
 
65
- response2 = build_response("Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
74
+ response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
66
75
  response2[0].should.equal(406)
67
76
  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.")
77
+ response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
78
+ end
79
+
80
+ specify "should handle gzip response with Last-Modified header" do
81
+ last_modified = Time.now.httpdate
82
+
83
+ app = lambda { |env| [200, { "Last-Modified" => last_modified }, "Hello World!"] }
84
+ request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
85
+ response = Rack::Deflater.new(app).call(request)
86
+
87
+ response[0].should.equal(200)
88
+ response[1].should.equal({ "Content-Encoding" => "gzip", "Vary" => "Accept-Encoding", "Last-Modified" => last_modified })
89
+
90
+ io = StringIO.new(response[2].to_s)
91
+ gz = Zlib::GzipReader.new(io)
92
+ gz.read.should.equal("Hello World!")
93
+ gz.close
94
+ end
95
+
96
+ specify "should do nothing when no-transform Cache-Control directive present" do
97
+ app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
98
+ request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
99
+ response = Rack::Deflater.new(app).call(request)
100
+
101
+ response[0].should.equal(200)
102
+ response[1].should.not.include "Content-Encoding"
103
+ response[2].join.should.equal("Hello World!")
69
104
  end
70
105
  end
@@ -47,4 +47,11 @@ context "Rack::File" do
47
47
 
48
48
  res.should.be.not_found
49
49
  end
50
+
51
+ specify "detects SystemCallErrors" do
52
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
53
+ get("/cgi")
54
+
55
+ res.should.be.not_found
56
+ end
50
57
  end
@@ -2,10 +2,10 @@ require 'test/spec'
2
2
 
3
3
  require 'rack/handler'
4
4
 
5
- context "Rack::Handler" do
6
- class Rack::Handler::Lobster; end
7
- class RockLobster; end
5
+ class Rack::Handler::Lobster; end
6
+ class RockLobster; end
8
7
 
8
+ context "Rack::Handler" do
9
9
  specify "has registered default handlers" do
10
10
  Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
11
11
  Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
@@ -0,0 +1,30 @@
1
+ require 'rack/head'
2
+ require 'rack/mock'
3
+
4
+ context "Rack::Head" do
5
+ def test_response(headers = {})
6
+ app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
7
+ request = Rack::MockRequest.env_for("/", headers)
8
+ response = Rack::Head.new(app).call(request)
9
+
10
+ return response
11
+ end
12
+
13
+ specify "passes GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
14
+ %w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
15
+ resp = test_response("REQUEST_METHOD" => type)
16
+
17
+ resp[0].should.equal(200)
18
+ resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
19
+ resp[2].should.equal(["foo"])
20
+ end
21
+ end
22
+
23
+ specify "removes body from HEAD requests" do
24
+ resp = test_response("REQUEST_METHOD" => "HEAD")
25
+
26
+ resp[0].should.equal(200)
27
+ resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
28
+ resp[2].should.equal([])
29
+ end
30
+ end
@@ -67,9 +67,9 @@ context "Rack::Lint" do
67
67
  message.should.match(/url_scheme unknown/)
68
68
 
69
69
  lambda {
70
- Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP"))
70
+ Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
71
71
  }.should.raise(Rack::Lint::LintError).
72
- message.should.match(/REQUEST_METHOD unknown/)
72
+ message.should.match(/REQUEST_METHOD/)
73
73
 
74
74
  lambda {
75
75
  Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
@@ -203,6 +203,47 @@ context "Rack::Lint" do
203
203
  }).call(env({}))
204
204
  }.should.raise(Rack::Lint::LintError).
205
205
  message.should.match(/No Content-Type/)
206
+
207
+ [100, 101, 204, 304].each do |status|
208
+ lambda {
209
+ Rack::Lint.new(lambda { |env|
210
+ [status, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
211
+ }).call(env({}))
212
+ }.should.raise(Rack::Lint::LintError).
213
+ message.should.match(/Content-Type header found/)
214
+ end
215
+ end
216
+
217
+ specify "notices content-length errors" do
218
+ lambda {
219
+ Rack::Lint.new(lambda { |env|
220
+ [200, {"Content-type" => "text/plain"}, ""]
221
+ }).call(env({}))
222
+ }.should.raise(Rack::Lint::LintError).
223
+ message.should.match(/No Content-Length/)
224
+
225
+ [100, 101, 204, 304].each do |status|
226
+ lambda {
227
+ Rack::Lint.new(lambda { |env|
228
+ [status, {"Content-length" => "0"}, ""]
229
+ }).call(env({}))
230
+ }.should.raise(Rack::Lint::LintError).
231
+ message.should.match(/Content-Length header found/)
232
+ end
233
+
234
+ lambda {
235
+ Rack::Lint.new(lambda { |env|
236
+ [200, {"Content-type" => "text/plain", "Content-Length" => "0", "Transfer-Encoding" => "chunked"}, ""]
237
+ }).call(env({}))
238
+ }.should.raise(Rack::Lint::LintError).
239
+ message.should.match(/Content-Length header should not be used/)
240
+
241
+ lambda {
242
+ Rack::Lint.new(lambda { |env|
243
+ [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, ""]
244
+ }).call(env({}))
245
+ }.should.raise(Rack::Lint::LintError).
246
+ message.should.match(/Content-Length header was 1, but should be 0/)
206
247
  end
207
248
 
208
249
  specify "notices body errors" do
@@ -300,4 +341,40 @@ context "Rack::Lint" do
300
341
  message.should.match(/close must not be called/)
301
342
  end
302
343
 
344
+ specify "notices HEAD errors" do
345
+ lambda {
346
+ Rack::Lint.new(lambda { |env|
347
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
348
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
349
+ }.should.not.raise
350
+
351
+ lambda {
352
+ Rack::Lint.new(lambda { |env|
353
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
354
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
355
+ }.should.raise(Rack::Lint::LintError).
356
+ message.should.match(/body was given for HEAD/)
357
+ end
358
+ end
359
+
360
+ context "Rack::Lint::InputWrapper" do
361
+ specify "delegates :size to underlying IO object" do
362
+ class IOMock
363
+ def size
364
+ 101
365
+ end
366
+ end
367
+
368
+ wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
369
+ wrapper.size.should == 101
370
+ end
371
+
372
+ specify "delegates :rewind to underlying IO object" do
373
+ io = StringIO.new("123")
374
+ wrapper = Rack::Lint::InputWrapper.new(io)
375
+ wrapper.read.should == "123"
376
+ wrapper.read.should == ""
377
+ wrapper.rewind
378
+ wrapper.read.should == "123"
379
+ end
303
380
  end
@@ -0,0 +1,60 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/mock'
4
+ require 'rack/methodoverride'
5
+ require 'stringio'
6
+
7
+ context "Rack::MethodOverride" do
8
+ specify "should not affect GET requests" do
9
+ env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET")
10
+ app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
11
+ req = app.call(env)
12
+
13
+ req.env["REQUEST_METHOD"].should.equal "GET"
14
+ end
15
+
16
+ specify "_method parameter should modify REQUEST_METHOD for POST requests" do
17
+ env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put")
18
+ app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
19
+ req = app.call(env)
20
+
21
+ req.env["REQUEST_METHOD"].should.equal "PUT"
22
+ end
23
+
24
+ specify "X-HTTP-Method-Override header should modify REQUEST_METHOD for POST requests" do
25
+ env = Rack::MockRequest.env_for("/",
26
+ :method => "POST",
27
+ "HTTP_X_HTTP_METHOD_OVERRIDE" => "PUT"
28
+ )
29
+ app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
30
+ req = app.call(env)
31
+
32
+ req.env["REQUEST_METHOD"].should.equal "PUT"
33
+ end
34
+
35
+ specify "should not modify REQUEST_METHOD if the method is unknown" do
36
+ env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo")
37
+ app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
38
+ req = app.call(env)
39
+
40
+ req.env["REQUEST_METHOD"].should.equal "POST"
41
+ end
42
+
43
+ specify "should not modify REQUEST_METHOD when _method is nil" do
44
+ env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar")
45
+ app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
46
+ req = app.call(env)
47
+
48
+ req.env["REQUEST_METHOD"].should.equal "POST"
49
+ end
50
+
51
+ specify "should store the original REQUEST_METHOD prior to overriding" do
52
+ env = Rack::MockRequest.env_for("/",
53
+ :method => "POST",
54
+ :input => "_method=options")
55
+ app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) })
56
+ req = app.call(env)
57
+
58
+ req.env["rack.methodoverride.original_method"].should.equal "POST"
59
+ end
60
+ end
@@ -125,7 +125,7 @@ context "Rack::MockResponse" do
125
125
  res.original_headers["Content-Type"].should.equal "text/yaml"
126
126
  res["Content-Type"].should.equal "text/yaml"
127
127
  res.content_type.should.equal "text/yaml"
128
- res.content_length.should.be.nil
128
+ res.content_length.should.be 381 # needs change often.
129
129
  res.location.should.be.nil
130
130
  end
131
131
 
@@ -5,7 +5,8 @@ require 'rack/handler/mongrel'
5
5
  require 'rack/urlmap'
6
6
  require 'rack/lint'
7
7
  require 'testrequest'
8
-
8
+ require 'timeout'
9
+
9
10
  Thread.abort_on_exception = true
10
11
  $tcp_defer_accept_opts = nil
11
12
  $tcp_cork_opts = nil
@@ -17,6 +18,8 @@ context "Rack::Handler::Mongrel" do
17
18
  server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
18
19
  server.register('/test',
19
20
  Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
21
+ server.register('/stream',
22
+ Rack::Handler::Mongrel.new(Rack::Lint.new(StreamingRequest)))
20
23
  @acc = server.run
21
24
  end
22
25
 
@@ -160,6 +163,22 @@ context "Rack::Handler::Mongrel" do
160
163
  block_ran.should.be true
161
164
  end
162
165
 
166
+ specify "should stream #each part of the response" do
167
+ body = ''
168
+ begin
169
+ Timeout.timeout(1) do
170
+ Net::HTTP.start(@host, @port) do |http|
171
+ get = Net::HTTP::Get.new('/stream')
172
+ http.request(get) do |response|
173
+ response.read_body { |part| body << part }
174
+ end
175
+ end
176
+ end
177
+ rescue Timeout::Error
178
+ end
179
+ body.should.not.be.empty
180
+ end
181
+
163
182
  teardown do
164
183
  @acc.raise Mongrel::StopServer
165
184
  end
@@ -83,6 +83,26 @@ context "Rack::Request" do
83
83
  req.body.read.should.equal "foo=bar&quux=bla"
84
84
  end
85
85
 
86
+ specify "rewinds input after parsing POST data" do
87
+ input = StringIO.new("foo=bar&quux=bla")
88
+ req = Rack::Request.new \
89
+ Rack::MockRequest.env_for("/",
90
+ "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
91
+ :input => input)
92
+ req.params.should.equal "foo" => "bar", "quux" => "bla"
93
+ input.read.should.equal "foo=bar&quux=bla"
94
+ end
95
+
96
+ specify "does not rewind unwindable CGI input" do
97
+ input = StringIO.new("foo=bar&quux=bla")
98
+ input.instance_eval "undef :rewind"
99
+ 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"
104
+ end
105
+
86
106
  specify "can get value by key from params with #[]" do
87
107
  req = Rack::Request.new \
88
108
  Rack::MockRequest.env_for("?foo=quux")
@@ -289,7 +309,7 @@ EOF
289
309
  "CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
290
310
  "CONTENT_LENGTH" => input.size,
291
311
  :input => input)
292
-
312
+
293
313
  req.POST["huge"][:tempfile].size.should.equal 32768
294
314
  req.POST["mean"][:tempfile].size.should.equal 10
295
315
  req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
@@ -398,4 +418,29 @@ EOF
398
418
 
399
419
  lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError)
400
420
  end
421
+
422
+ specify 'should provide ip information' do
423
+ app = lambda { |env|
424
+ request = Rack::Request.new(env)
425
+ response = Rack::Response.new
426
+ response.write request.ip
427
+ response.finish
428
+ }
429
+
430
+ mock = Rack::MockRequest.new(Rack::Lint.new(app))
431
+ res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
432
+ res.body.should == '123.123.123.123'
433
+
434
+ res = mock.get '/',
435
+ 'REMOTE_ADDR' => '123.123.123.123',
436
+ 'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
437
+
438
+ res.body.should == '234.234.234.234'
439
+
440
+ res = mock.get '/',
441
+ 'REMOTE_ADDR' => '123.123.123.123',
442
+ 'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
443
+
444
+ res.body.should == '212.212.212.212'
445
+ end
401
446
  end