edgar-rack 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +18 -0
- data/KNOWN-ISSUES +21 -0
- data/README +401 -0
- data/Rakefile +101 -0
- data/SPEC +171 -0
- data/bin/rackup +4 -0
- data/contrib/rack_logo.svg +111 -0
- data/example/lobster.ru +4 -0
- data/example/protectedlobster.rb +14 -0
- data/example/protectedlobster.ru +8 -0
- data/lib/rack.rb +81 -0
- data/lib/rack/auth/abstract/handler.rb +37 -0
- data/lib/rack/auth/abstract/request.rb +43 -0
- data/lib/rack/auth/basic.rb +58 -0
- data/lib/rack/auth/digest/md5.rb +124 -0
- data/lib/rack/auth/digest/nonce.rb +51 -0
- data/lib/rack/auth/digest/params.rb +53 -0
- data/lib/rack/auth/digest/request.rb +40 -0
- data/lib/rack/builder.rb +80 -0
- data/lib/rack/cascade.rb +41 -0
- data/lib/rack/chunked.rb +52 -0
- data/lib/rack/commonlogger.rb +49 -0
- data/lib/rack/conditionalget.rb +63 -0
- data/lib/rack/config.rb +15 -0
- data/lib/rack/content_length.rb +29 -0
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +96 -0
- data/lib/rack/directory.rb +157 -0
- data/lib/rack/etag.rb +59 -0
- data/lib/rack/file.rb +118 -0
- data/lib/rack/handler.rb +88 -0
- data/lib/rack/handler/cgi.rb +61 -0
- data/lib/rack/handler/evented_mongrel.rb +8 -0
- data/lib/rack/handler/fastcgi.rb +90 -0
- data/lib/rack/handler/lsws.rb +61 -0
- data/lib/rack/handler/mongrel.rb +90 -0
- data/lib/rack/handler/scgi.rb +59 -0
- data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
- data/lib/rack/handler/thin.rb +17 -0
- data/lib/rack/handler/webrick.rb +73 -0
- data/lib/rack/head.rb +19 -0
- data/lib/rack/lint.rb +567 -0
- data/lib/rack/lobster.rb +65 -0
- data/lib/rack/lock.rb +44 -0
- data/lib/rack/logger.rb +18 -0
- data/lib/rack/methodoverride.rb +27 -0
- data/lib/rack/mime.rb +210 -0
- data/lib/rack/mock.rb +185 -0
- data/lib/rack/nulllogger.rb +18 -0
- data/lib/rack/recursive.rb +61 -0
- data/lib/rack/reloader.rb +109 -0
- data/lib/rack/request.rb +307 -0
- data/lib/rack/response.rb +151 -0
- data/lib/rack/rewindable_input.rb +104 -0
- data/lib/rack/runtime.rb +27 -0
- data/lib/rack/sendfile.rb +139 -0
- data/lib/rack/server.rb +289 -0
- data/lib/rack/session/abstract/id.rb +348 -0
- data/lib/rack/session/cookie.rb +152 -0
- data/lib/rack/session/memcache.rb +93 -0
- data/lib/rack/session/pool.rb +79 -0
- data/lib/rack/showexceptions.rb +378 -0
- data/lib/rack/showstatus.rb +113 -0
- data/lib/rack/static.rb +53 -0
- data/lib/rack/urlmap.rb +55 -0
- data/lib/rack/utils.rb +698 -0
- data/rack.gemspec +39 -0
- data/test/cgi/lighttpd.conf +25 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +6 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/rackup/config.ru +31 -0
- data/test/spec_auth_basic.rb +70 -0
- data/test/spec_auth_digest.rb +241 -0
- data/test/spec_builder.rb +123 -0
- data/test/spec_cascade.rb +45 -0
- data/test/spec_cgi.rb +102 -0
- data/test/spec_chunked.rb +60 -0
- data/test/spec_commonlogger.rb +56 -0
- data/test/spec_conditionalget.rb +86 -0
- data/test/spec_config.rb +23 -0
- data/test/spec_content_length.rb +36 -0
- data/test/spec_content_type.rb +29 -0
- data/test/spec_deflater.rb +125 -0
- data/test/spec_directory.rb +57 -0
- data/test/spec_etag.rb +75 -0
- data/test/spec_fastcgi.rb +107 -0
- data/test/spec_file.rb +92 -0
- data/test/spec_handler.rb +49 -0
- data/test/spec_head.rb +30 -0
- data/test/spec_lint.rb +515 -0
- data/test/spec_lobster.rb +43 -0
- data/test/spec_lock.rb +142 -0
- data/test/spec_logger.rb +28 -0
- data/test/spec_methodoverride.rb +58 -0
- data/test/spec_mock.rb +241 -0
- data/test/spec_mongrel.rb +182 -0
- data/test/spec_nulllogger.rb +12 -0
- data/test/spec_recursive.rb +69 -0
- data/test/spec_request.rb +774 -0
- data/test/spec_response.rb +245 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +39 -0
- data/test/spec_sendfile.rb +83 -0
- data/test/spec_server.rb +8 -0
- data/test/spec_session_abstract_id.rb +43 -0
- data/test/spec_session_cookie.rb +171 -0
- data/test/spec_session_memcache.rb +289 -0
- data/test/spec_session_pool.rb +200 -0
- data/test/spec_showexceptions.rb +87 -0
- data/test/spec_showstatus.rb +79 -0
- data/test/spec_static.rb +48 -0
- data/test/spec_thin.rb +86 -0
- data/test/spec_urlmap.rb +213 -0
- data/test/spec_utils.rb +678 -0
- data/test/spec_webrick.rb +141 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +329 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'rack/cascade'
|
|
2
|
+
require 'rack/file'
|
|
3
|
+
require 'rack/urlmap'
|
|
4
|
+
require 'rack/mock'
|
|
5
|
+
|
|
6
|
+
describe Rack::Cascade do
|
|
7
|
+
docroot = File.expand_path(File.dirname(__FILE__))
|
|
8
|
+
app1 = Rack::File.new(docroot)
|
|
9
|
+
|
|
10
|
+
app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" })
|
|
11
|
+
|
|
12
|
+
app3 = Rack::URLMap.new("/foo" => lambda { |env|
|
|
13
|
+
[200, { "Content-Type" => "text/plain"}, [""]]})
|
|
14
|
+
|
|
15
|
+
should "dispatch onward on 404 by default" do
|
|
16
|
+
cascade = Rack::Cascade.new([app1, app2, app3])
|
|
17
|
+
Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
|
|
18
|
+
Rack::MockRequest.new(cascade).get("/foo").should.be.ok
|
|
19
|
+
Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
|
|
20
|
+
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
should "dispatch onward on whatever is passed" do
|
|
24
|
+
cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
|
|
25
|
+
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
should "return 404 if empty" do
|
|
29
|
+
Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
should "append new app" do
|
|
33
|
+
cascade = Rack::Cascade.new([], [404, 403])
|
|
34
|
+
Rack::MockRequest.new(cascade).get('/').should.be.not_found
|
|
35
|
+
cascade << app2
|
|
36
|
+
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found
|
|
37
|
+
Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
|
|
38
|
+
cascade << app1
|
|
39
|
+
Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
|
|
40
|
+
Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden
|
|
41
|
+
Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
|
|
42
|
+
cascade << app3
|
|
43
|
+
Rack::MockRequest.new(cascade).get('/foo').should.be.ok
|
|
44
|
+
end
|
|
45
|
+
end
|
data/test/spec_cgi.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require File.expand_path('../testrequest', __FILE__)
|
|
3
|
+
require 'rack/handler/cgi'
|
|
4
|
+
|
|
5
|
+
describe Rack::Handler::CGI do
|
|
6
|
+
extend TestRequest::Helpers
|
|
7
|
+
|
|
8
|
+
@host = '0.0.0.0'
|
|
9
|
+
@port = 9203
|
|
10
|
+
|
|
11
|
+
if `which lighttpd` && !$?.success?
|
|
12
|
+
raise "lighttpd not found"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Keep this first.
|
|
16
|
+
$pid = fork {
|
|
17
|
+
ENV['RACK_ENV'] = 'deployment'
|
|
18
|
+
ENV['RUBYLIB'] = [
|
|
19
|
+
File.expand_path('../../lib', __FILE__),
|
|
20
|
+
ENV['RUBYLIB'],
|
|
21
|
+
].compact.join(':')
|
|
22
|
+
|
|
23
|
+
Dir.chdir(File.expand_path("../cgi", __FILE__)) do
|
|
24
|
+
exec "lighttpd -D -f lighttpd.conf"
|
|
25
|
+
end
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
should "respond" do
|
|
29
|
+
sleep 1
|
|
30
|
+
GET("/test")
|
|
31
|
+
response.should.not.be.nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
should "be a lighttpd" do
|
|
35
|
+
GET("/test")
|
|
36
|
+
status.should.equal 200
|
|
37
|
+
response["SERVER_SOFTWARE"].should =~ /lighttpd/
|
|
38
|
+
response["HTTP_VERSION"].should.equal "HTTP/1.1"
|
|
39
|
+
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
|
|
40
|
+
response["SERVER_PORT"].should.equal @port.to_s
|
|
41
|
+
response["SERVER_NAME"].should.equal @host
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
should "have rack headers" do
|
|
45
|
+
GET("/test")
|
|
46
|
+
response["rack.version"].should.equal([1,1])
|
|
47
|
+
response["rack.multithread"].should.be.false
|
|
48
|
+
response["rack.multiprocess"].should.be.true
|
|
49
|
+
response["rack.run_once"].should.be.true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
should "have CGI headers on GET" do
|
|
53
|
+
GET("/test")
|
|
54
|
+
response["REQUEST_METHOD"].should.equal "GET"
|
|
55
|
+
response["SCRIPT_NAME"].should.equal "/test"
|
|
56
|
+
response["REQUEST_PATH"].should.equal "/"
|
|
57
|
+
response["PATH_INFO"].should.be.nil
|
|
58
|
+
response["QUERY_STRING"].should.equal ""
|
|
59
|
+
response["test.postdata"].should.equal ""
|
|
60
|
+
|
|
61
|
+
GET("/test/foo?quux=1")
|
|
62
|
+
response["REQUEST_METHOD"].should.equal "GET"
|
|
63
|
+
response["SCRIPT_NAME"].should.equal "/test"
|
|
64
|
+
response["REQUEST_PATH"].should.equal "/"
|
|
65
|
+
response["PATH_INFO"].should.equal "/foo"
|
|
66
|
+
response["QUERY_STRING"].should.equal "quux=1"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
should "have CGI headers on POST" do
|
|
70
|
+
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
|
|
71
|
+
status.should.equal 200
|
|
72
|
+
response["REQUEST_METHOD"].should.equal "POST"
|
|
73
|
+
response["SCRIPT_NAME"].should.equal "/test"
|
|
74
|
+
response["REQUEST_PATH"].should.equal "/"
|
|
75
|
+
response["QUERY_STRING"].should.equal ""
|
|
76
|
+
response["HTTP_X_TEST_HEADER"].should.equal "42"
|
|
77
|
+
response["test.postdata"].should.equal "rack-form-data=23"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
should "support HTTP auth" do
|
|
81
|
+
GET("/test", {:user => "ruth", :passwd => "secret"})
|
|
82
|
+
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
should "set status" do
|
|
86
|
+
GET("/test?secret")
|
|
87
|
+
status.should.equal 403
|
|
88
|
+
response["rack.url_scheme"].should.equal "http"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Keep this last.
|
|
92
|
+
should "shutdown" do
|
|
93
|
+
Process.kill 15, $pid
|
|
94
|
+
Process.wait($pid).should == $pid
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
rescue RuntimeError
|
|
99
|
+
$stderr.puts "Skipping Rack::Handler::CGI tests (lighttpd is required). Install lighttpd and try again."
|
|
100
|
+
rescue NotImplementedError
|
|
101
|
+
$stderr.puts "Your Ruby implemenation or platform does not support fork. Skipping Rack::Handler::CGI tests."
|
|
102
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'rack/chunked'
|
|
2
|
+
require 'rack/mock'
|
|
3
|
+
|
|
4
|
+
describe Rack::Chunked do
|
|
5
|
+
before do
|
|
6
|
+
@env = Rack::MockRequest.
|
|
7
|
+
env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
should 'chunk responses with no Content-Length' do
|
|
11
|
+
app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
|
|
12
|
+
response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
|
|
13
|
+
response.headers.should.not.include 'Content-Length'
|
|
14
|
+
response.headers['Transfer-Encoding'].should.equal 'chunked'
|
|
15
|
+
response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
should 'chunks empty bodies properly' do
|
|
19
|
+
app = lambda { |env| [200, {}, []] }
|
|
20
|
+
response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
|
|
21
|
+
response.headers.should.not.include 'Content-Length'
|
|
22
|
+
response.headers['Transfer-Encoding'].should.equal 'chunked'
|
|
23
|
+
response.body.should.equal "0\r\n\r\n"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
should 'not modify response when Content-Length header present' do
|
|
27
|
+
app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] }
|
|
28
|
+
status, headers, body = Rack::Chunked.new(app).call(@env)
|
|
29
|
+
status.should.equal 200
|
|
30
|
+
headers.should.not.include 'Transfer-Encoding'
|
|
31
|
+
headers.should.include 'Content-Length'
|
|
32
|
+
body.join.should.equal 'Hello World!'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
should 'not modify response when client is HTTP/1.0' do
|
|
36
|
+
app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
|
|
37
|
+
@env['HTTP_VERSION'] = 'HTTP/1.0'
|
|
38
|
+
status, headers, body = Rack::Chunked.new(app).call(@env)
|
|
39
|
+
status.should.equal 200
|
|
40
|
+
headers.should.not.include 'Transfer-Encoding'
|
|
41
|
+
body.join.should.equal 'Hello World!'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
should 'not modify response when Transfer-Encoding header already present' do
|
|
45
|
+
app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] }
|
|
46
|
+
status, headers, body = Rack::Chunked.new(app).call(@env)
|
|
47
|
+
status.should.equal 200
|
|
48
|
+
headers['Transfer-Encoding'].should.equal 'identity'
|
|
49
|
+
body.join.should.equal 'Hello World!'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
[100, 204, 304].each do |status_code|
|
|
53
|
+
should "not modify response when status code is #{status_code}" do
|
|
54
|
+
app = lambda { |env| [status_code, {}, []] }
|
|
55
|
+
status, headers, _ = Rack::Chunked.new(app).call(@env)
|
|
56
|
+
status.should.equal status_code
|
|
57
|
+
headers.should.not.include 'Transfer-Encoding'
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'rack/commonlogger'
|
|
2
|
+
require 'rack/mock'
|
|
3
|
+
|
|
4
|
+
describe Rack::CommonLogger do
|
|
5
|
+
obj = 'foobar'
|
|
6
|
+
length = obj.size
|
|
7
|
+
|
|
8
|
+
app = lambda { |env|
|
|
9
|
+
[200,
|
|
10
|
+
{"Content-Type" => "text/html", "Content-Length" => length.to_s},
|
|
11
|
+
[obj]]}
|
|
12
|
+
app_without_length = lambda { |env|
|
|
13
|
+
[200,
|
|
14
|
+
{"Content-Type" => "text/html"},
|
|
15
|
+
[]]}
|
|
16
|
+
app_with_zero_length = lambda { |env|
|
|
17
|
+
[200,
|
|
18
|
+
{"Content-Type" => "text/html", "Content-Length" => "0"},
|
|
19
|
+
[]]}
|
|
20
|
+
|
|
21
|
+
should "log to rack.errors by default" do
|
|
22
|
+
res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
|
|
23
|
+
|
|
24
|
+
res.errors.should.not.be.empty
|
|
25
|
+
res.errors.should =~ /"GET \/ " 200 #{length} /
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
should "log to anything with +write+" do
|
|
29
|
+
log = StringIO.new
|
|
30
|
+
Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
|
|
31
|
+
|
|
32
|
+
log.string.should =~ /"GET \/ " 200 #{length} /
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
should "log - content length if header is missing" do
|
|
36
|
+
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/")
|
|
37
|
+
|
|
38
|
+
res.errors.should.not.be.empty
|
|
39
|
+
res.errors.should =~ /"GET \/ " 200 - /
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
should "log - content length if header is zero" do
|
|
43
|
+
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/")
|
|
44
|
+
|
|
45
|
+
res.errors.should.not.be.empty
|
|
46
|
+
res.errors.should =~ /"GET \/ " 200 - /
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def length
|
|
50
|
+
123
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.obj
|
|
54
|
+
"hello world"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
require 'rack/conditionalget'
|
|
3
|
+
require 'rack/mock'
|
|
4
|
+
|
|
5
|
+
describe Rack::ConditionalGet do
|
|
6
|
+
should "set a 304 status and truncate body when If-Modified-Since hits" do
|
|
7
|
+
timestamp = Time.now.httpdate
|
|
8
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
9
|
+
[200, {'Last-Modified'=>timestamp}, ['TEST']] })
|
|
10
|
+
|
|
11
|
+
response = Rack::MockRequest.new(app).
|
|
12
|
+
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)
|
|
13
|
+
|
|
14
|
+
response.status.should.equal 304
|
|
15
|
+
response.body.should.be.empty
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
should "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
|
|
19
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
20
|
+
[200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
|
|
21
|
+
|
|
22
|
+
response = Rack::MockRequest.new(app).
|
|
23
|
+
get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
|
|
24
|
+
|
|
25
|
+
response.status.should.equal 304
|
|
26
|
+
response.body.should.be.empty
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
should "set a 304 status and truncate body when If-None-Match hits" do
|
|
30
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
31
|
+
[200, {'Etag'=>'1234'}, ['TEST']] })
|
|
32
|
+
|
|
33
|
+
response = Rack::MockRequest.new(app).
|
|
34
|
+
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
|
35
|
+
|
|
36
|
+
response.status.should.equal 304
|
|
37
|
+
response.body.should.be.empty
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
should "not set a 304 status if If-Modified-Since hits but Etag does not" do
|
|
41
|
+
timestamp = Time.now.httpdate
|
|
42
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
43
|
+
[200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
|
|
44
|
+
|
|
45
|
+
response = Rack::MockRequest.new(app).
|
|
46
|
+
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
|
|
47
|
+
|
|
48
|
+
response.status.should.equal 200
|
|
49
|
+
response.body.should.equal 'TEST'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
|
|
53
|
+
timestamp = Time.now.httpdate
|
|
54
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
55
|
+
[200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
|
|
56
|
+
|
|
57
|
+
response = Rack::MockRequest.new(app).
|
|
58
|
+
get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234')
|
|
59
|
+
|
|
60
|
+
response.status.should.equal 304
|
|
61
|
+
response.body.should.be.empty
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
should "not affect non-GET/HEAD requests" do
|
|
65
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
66
|
+
[200, {'Etag'=>'1234'}, ['TEST']] })
|
|
67
|
+
|
|
68
|
+
response = Rack::MockRequest.new(app).
|
|
69
|
+
post("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
|
70
|
+
|
|
71
|
+
response.status.should.equal 200
|
|
72
|
+
response.body.should.equal 'TEST'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
should "not affect non-200 requests" do
|
|
76
|
+
app = Rack::ConditionalGet.new(lambda { |env|
|
|
77
|
+
[302, {'Etag'=>'1234'}, ['TEST']] })
|
|
78
|
+
|
|
79
|
+
response = Rack::MockRequest.new(app).
|
|
80
|
+
get("/", 'HTTP_IF_NONE_MATCH' => '1234')
|
|
81
|
+
|
|
82
|
+
response.status.should.equal 302
|
|
83
|
+
response.body.should.equal 'TEST'
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
data/test/spec_config.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rack/builder'
|
|
2
|
+
require 'rack/config'
|
|
3
|
+
require 'rack/content_length'
|
|
4
|
+
require 'rack/lint'
|
|
5
|
+
require 'rack/mock'
|
|
6
|
+
|
|
7
|
+
describe Rack::Config do
|
|
8
|
+
should "accept a block that modifies the environment" do
|
|
9
|
+
app = Rack::Builder.new do
|
|
10
|
+
use Rack::Lint
|
|
11
|
+
use Rack::ContentLength
|
|
12
|
+
use Rack::Config do |env|
|
|
13
|
+
env['greeting'] = 'hello'
|
|
14
|
+
end
|
|
15
|
+
run lambda { |env|
|
|
16
|
+
[200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
response = Rack::MockRequest.new(app).get('/')
|
|
21
|
+
response.body.should.equal('hello')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'rack/content_length'
|
|
2
|
+
|
|
3
|
+
describe Rack::ContentLength do
|
|
4
|
+
should "set Content-Length on Array bodies if none is set" do
|
|
5
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
|
|
6
|
+
response = Rack::ContentLength.new(app).call({})
|
|
7
|
+
response[1]['Content-Length'].should.equal '13'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
should "not set Content-Length on variable length bodies" do
|
|
11
|
+
body = lambda { "Hello World!" }
|
|
12
|
+
def body.each ; yield call ; end
|
|
13
|
+
|
|
14
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
|
|
15
|
+
response = Rack::ContentLength.new(app).call({})
|
|
16
|
+
response[1]['Content-Length'].should.be.nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
should "not change Content-Length if it is already set" do
|
|
20
|
+
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
|
|
21
|
+
response = Rack::ContentLength.new(app).call({})
|
|
22
|
+
response[1]['Content-Length'].should.equal '1'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
should "not set Content-Length on 304 responses" do
|
|
26
|
+
app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] }
|
|
27
|
+
response = Rack::ContentLength.new(app).call({})
|
|
28
|
+
response[1]['Content-Length'].should.equal nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
should "not set Content-Length when Transfer-Encoding is chunked" do
|
|
32
|
+
app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] }
|
|
33
|
+
response = Rack::ContentLength.new(app).call({})
|
|
34
|
+
response[1]['Content-Length'].should.equal nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rack/content_type'
|
|
2
|
+
|
|
3
|
+
describe Rack::ContentType do
|
|
4
|
+
should "set Content-Type to default text/html if none is set" do
|
|
5
|
+
app = lambda { |env| [200, {}, "Hello, World!"] }
|
|
6
|
+
headers = Rack::ContentType.new(app).call({})[1]
|
|
7
|
+
headers['Content-Type'].should.equal 'text/html'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
should "set Content-Type to chosen default if none is set" do
|
|
11
|
+
app = lambda { |env| [200, {}, "Hello, World!"] }
|
|
12
|
+
headers =
|
|
13
|
+
Rack::ContentType.new(app, 'application/octet-stream').call({})[1]
|
|
14
|
+
headers['Content-Type'].should.equal 'application/octet-stream'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
should "not change Content-Type if it is already set" do
|
|
18
|
+
app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
|
|
19
|
+
headers = Rack::ContentType.new(app).call({})[1]
|
|
20
|
+
headers['Content-Type'].should.equal 'foo/bar'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
should "detect Content-Type case insensitive" do
|
|
24
|
+
app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
|
|
25
|
+
headers = Rack::ContentType.new(app).call({})[1]
|
|
26
|
+
headers.to_a.select { |k,v| k.downcase == "content-type" }.
|
|
27
|
+
should.equal [["CONTENT-Type","foo/bar"]]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
require 'time' # for Time#httpdate
|
|
3
|
+
require 'rack/deflater'
|
|
4
|
+
require 'rack/mock'
|
|
5
|
+
|
|
6
|
+
describe Rack::Deflater do
|
|
7
|
+
def build_response(status, body, accept_encoding, headers = {})
|
|
8
|
+
body = [body] if body.respond_to? :to_str
|
|
9
|
+
app = lambda { |env| [status, {}, body] }
|
|
10
|
+
request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
|
|
11
|
+
response = Rack::Deflater.new(app).call(request)
|
|
12
|
+
|
|
13
|
+
return response
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
should "be able to deflate bodies that respond to each" do
|
|
17
|
+
body = Object.new
|
|
18
|
+
class << body; def each; yield("foo"); yield("bar"); end; end
|
|
19
|
+
|
|
20
|
+
response = build_response(200, body, "deflate")
|
|
21
|
+
|
|
22
|
+
response[0].should.equal(200)
|
|
23
|
+
response[1].should.equal({
|
|
24
|
+
"Content-Encoding" => "deflate",
|
|
25
|
+
"Vary" => "Accept-Encoding"
|
|
26
|
+
})
|
|
27
|
+
buf = ''
|
|
28
|
+
response[2].each { |part| buf << part }
|
|
29
|
+
buf.should.equal("K\313\317OJ,\002\000")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO: This is really just a special case of the above...
|
|
33
|
+
should "be able to deflate String bodies" do
|
|
34
|
+
response = build_response(200, "Hello world!", "deflate")
|
|
35
|
+
|
|
36
|
+
response[0].should.equal(200)
|
|
37
|
+
response[1].should.equal({
|
|
38
|
+
"Content-Encoding" => "deflate",
|
|
39
|
+
"Vary" => "Accept-Encoding"
|
|
40
|
+
})
|
|
41
|
+
buf = ''
|
|
42
|
+
response[2].each { |part| buf << part }
|
|
43
|
+
buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
should "be able to gzip bodies that respond to each" do
|
|
47
|
+
body = Object.new
|
|
48
|
+
class << body; def each; yield("foo"); yield("bar"); end; end
|
|
49
|
+
|
|
50
|
+
response = build_response(200, body, "gzip")
|
|
51
|
+
|
|
52
|
+
response[0].should.equal(200)
|
|
53
|
+
response[1].should.equal({
|
|
54
|
+
"Content-Encoding" => "gzip",
|
|
55
|
+
"Vary" => "Accept-Encoding",
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
buf = ''
|
|
59
|
+
response[2].each { |part| buf << part }
|
|
60
|
+
io = StringIO.new(buf)
|
|
61
|
+
gz = Zlib::GzipReader.new(io)
|
|
62
|
+
gz.read.should.equal("foobar")
|
|
63
|
+
gz.close
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
should "be able to fallback to no deflation" do
|
|
67
|
+
response = build_response(200, "Hello world!", "superzip")
|
|
68
|
+
|
|
69
|
+
response[0].should.equal(200)
|
|
70
|
+
response[1].should.equal({ "Vary" => "Accept-Encoding" })
|
|
71
|
+
response[2].should.equal(["Hello world!"])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
should "be able to skip when there is no response entity body" do
|
|
75
|
+
response = build_response(304, [], "gzip")
|
|
76
|
+
|
|
77
|
+
response[0].should.equal(304)
|
|
78
|
+
response[1].should.equal({})
|
|
79
|
+
response[2].should.equal([])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
should "handle the lack of an acceptable encoding" do
|
|
83
|
+
response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
|
|
84
|
+
response1[0].should.equal(406)
|
|
85
|
+
response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
|
|
86
|
+
response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])
|
|
87
|
+
|
|
88
|
+
response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
|
|
89
|
+
response2[0].should.equal(406)
|
|
90
|
+
response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"})
|
|
91
|
+
response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
should "handle gzip response with Last-Modified header" do
|
|
95
|
+
last_modified = Time.now.httpdate
|
|
96
|
+
|
|
97
|
+
app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["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.equal({
|
|
103
|
+
"Content-Encoding" => "gzip",
|
|
104
|
+
"Vary" => "Accept-Encoding",
|
|
105
|
+
"Last-Modified" => last_modified
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
buf = ''
|
|
109
|
+
response[2].each { |part| buf << part }
|
|
110
|
+
io = StringIO.new(buf)
|
|
111
|
+
gz = Zlib::GzipReader.new(io)
|
|
112
|
+
gz.read.should.equal("Hello World!")
|
|
113
|
+
gz.close
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
should "do nothing when no-transform Cache-Control directive present" do
|
|
117
|
+
app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
|
|
118
|
+
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
|
|
119
|
+
response = Rack::Deflater.new(app).call(request)
|
|
120
|
+
|
|
121
|
+
response[0].should.equal(200)
|
|
122
|
+
response[1].should.not.include "Content-Encoding"
|
|
123
|
+
response[2].join.should.equal("Hello World!")
|
|
124
|
+
end
|
|
125
|
+
end
|