rack 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- data/COPYING +1 -1
- data/RDOX +115 -16
- data/README +54 -7
- data/Rakefile +61 -85
- data/SPEC +50 -17
- data/bin/rackup +9 -5
- data/example/protectedlobster.ru +1 -1
- data/lib/rack.rb +7 -3
- data/lib/rack/auth/abstract/handler.rb +13 -4
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/auth/digest/request.rb +2 -2
- data/lib/rack/auth/openid.rb +344 -302
- data/lib/rack/builder.rb +1 -5
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/conditionalget.rb +4 -0
- data/lib/rack/content_length.rb +7 -3
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +83 -74
- data/lib/rack/directory.rb +5 -2
- data/lib/rack/file.rb +4 -1
- data/lib/rack/handler.rb +22 -1
- data/lib/rack/handler/cgi.rb +7 -3
- data/lib/rack/handler/fastcgi.rb +26 -24
- data/lib/rack/handler/lsws.rb +7 -4
- data/lib/rack/handler/mongrel.rb +5 -3
- data/lib/rack/handler/scgi.rb +5 -3
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +11 -5
- data/lib/rack/lint.rb +138 -66
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/mime.rb +4 -4
- data/lib/rack/mock.rb +3 -3
- data/lib/rack/reloader.rb +88 -46
- data/lib/rack/request.rb +46 -10
- data/lib/rack/response.rb +15 -3
- data/lib/rack/rewindable_input.rb +98 -0
- data/lib/rack/session/abstract/id.rb +71 -82
- data/lib/rack/session/cookie.rb +2 -0
- data/lib/rack/session/memcache.rb +59 -47
- data/lib/rack/session/pool.rb +56 -29
- data/lib/rack/showexceptions.rb +2 -1
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/urlmap.rb +12 -5
- data/lib/rack/utils.rb +115 -65
- data/rack.gemspec +54 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/text +10 -0
- data/test/spec_rack_auth_basic.rb +5 -1
- data/test/spec_rack_auth_digest.rb +93 -36
- data/test/spec_rack_auth_openid.rb +47 -100
- data/test/spec_rack_builder.rb +2 -2
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_conditionalget.rb +7 -7
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +36 -14
- data/test/spec_rack_directory.rb +1 -1
- data/test/spec_rack_file.rb +11 -0
- data/test/spec_rack_handler.rb +21 -2
- data/test/spec_rack_lint.rb +163 -44
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_mock.rb +6 -1
- data/test/spec_rack_request.rb +81 -12
- data/test/spec_rack_response.rb +46 -2
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_memcache.rb +170 -62
- data/test/spec_rack_session_pool.rb +129 -41
- data/test/spec_rack_static.rb +2 -2
- data/test/spec_rack_thin.rb +3 -2
- data/test/spec_rack_urlmap.rb +10 -0
- data/test/spec_rack_utils.rb +214 -49
- data/test/spec_rack_webrick.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +95 -6
- data/AUTHORS +0 -8
data/lib/rack/builder.rb
CHANGED
@@ -34,11 +34,7 @@ module Rack
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def use(middleware, *args, &block)
|
37
|
-
@ins <<
|
38
|
-
lambda { |app| middleware.new(app, *args, &block) }
|
39
|
-
else
|
40
|
-
lambda { |app| middleware.new(app, *args) }
|
41
|
-
end
|
37
|
+
@ins << lambda { |app| middleware.new(app, *args, &block) }
|
42
38
|
end
|
43
39
|
|
44
40
|
def run(app)
|
data/lib/rack/chunked.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
# Middleware that applies chunked transfer encoding to response bodies
|
6
|
+
# when the response does not include a Content-Length header.
|
7
|
+
class Chunked
|
8
|
+
include Rack::Utils
|
9
|
+
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
status, headers, body = @app.call(env)
|
16
|
+
headers = HeaderHash.new(headers)
|
17
|
+
|
18
|
+
if env['HTTP_VERSION'] == 'HTTP/1.0' ||
|
19
|
+
STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
20
|
+
headers['Content-Length'] ||
|
21
|
+
headers['Transfer-Encoding']
|
22
|
+
[status, headers.to_hash, body]
|
23
|
+
else
|
24
|
+
dup.chunk(status, headers, body)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def chunk(status, headers, body)
|
29
|
+
@body = body
|
30
|
+
headers.delete('Content-Length')
|
31
|
+
headers['Transfer-Encoding'] = 'chunked'
|
32
|
+
[status, headers.to_hash, self]
|
33
|
+
end
|
34
|
+
|
35
|
+
def each
|
36
|
+
term = "\r\n"
|
37
|
+
@body.each do |chunk|
|
38
|
+
size = bytesize(chunk)
|
39
|
+
next if size == 0
|
40
|
+
yield [size.to_s(16), term, chunk, term].join
|
41
|
+
end
|
42
|
+
yield ["0", term, "", term].join
|
43
|
+
end
|
44
|
+
|
45
|
+
def close
|
46
|
+
@body.close if @body.respond_to?(:close)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/rack/conditionalget.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
# Middleware that enables conditional GET using If-None-Match and
|
@@ -24,6 +26,8 @@ module Rack
|
|
24
26
|
headers = Utils::HeaderHash.new(headers)
|
25
27
|
if etag_matches?(env, headers) || modified_since?(env, headers)
|
26
28
|
status = 304
|
29
|
+
headers.delete('Content-Type')
|
30
|
+
headers.delete('Content-Length')
|
27
31
|
body = []
|
28
32
|
end
|
29
33
|
[status, headers, body]
|
data/lib/rack/content_length.rb
CHANGED
@@ -1,21 +1,25 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Sets the Content-Length header on responses with fixed-length bodies.
|
3
5
|
class ContentLength
|
6
|
+
include Rack::Utils
|
7
|
+
|
4
8
|
def initialize(app)
|
5
9
|
@app = app
|
6
10
|
end
|
7
11
|
|
8
12
|
def call(env)
|
9
13
|
status, headers, body = @app.call(env)
|
10
|
-
headers =
|
14
|
+
headers = HeaderHash.new(headers)
|
11
15
|
|
12
|
-
if !
|
16
|
+
if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
|
13
17
|
!headers['Content-Length'] &&
|
14
18
|
!headers['Transfer-Encoding'] &&
|
15
19
|
(body.respond_to?(:to_ary) || body.respond_to?(:to_str))
|
16
20
|
|
17
21
|
body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
|
18
|
-
length = body.to_ary.inject(0) { |len, part| len + part
|
22
|
+
length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
|
19
23
|
headers['Content-Length'] = length.to_s
|
20
24
|
end
|
21
25
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
|
5
|
+
# Sets the Content-Type header on responses which don't have one.
|
6
|
+
#
|
7
|
+
# Builder Usage:
|
8
|
+
# use Rack::ContentType, "text/plain"
|
9
|
+
#
|
10
|
+
# When no content type argument is provided, "text/html" is assumed.
|
11
|
+
class ContentType
|
12
|
+
def initialize(app, content_type = "text/html")
|
13
|
+
@app, @content_type = app, content_type
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
status, headers, body = @app.call(env)
|
18
|
+
headers = Utils::HeaderHash.new(headers)
|
19
|
+
headers['Content-Type'] ||= @content_type
|
20
|
+
[status, headers.to_hash, body]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rack/deflater.rb
CHANGED
@@ -1,87 +1,96 @@
|
|
1
1
|
require "zlib"
|
2
2
|
require "stringio"
|
3
3
|
require "time" # for Time.httpdate
|
4
|
+
require 'rack/utils'
|
4
5
|
|
5
6
|
module Rack
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@app = app
|
10
|
-
end
|
11
|
-
|
12
|
-
def call(env)
|
13
|
-
status, headers, body = @app.call(env)
|
14
|
-
headers = Utils::HeaderHash.new(headers)
|
15
|
-
|
16
|
-
# Skip compressing empty entity body responses and responses with
|
17
|
-
# no-transform set.
|
18
|
-
if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
19
|
-
headers['Cache-Control'].to_s =~ /\bno-transform\b/
|
20
|
-
return [status, headers, body]
|
7
|
+
class Deflater
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
21
10
|
end
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
12
|
+
def call(env)
|
13
|
+
status, headers, body = @app.call(env)
|
14
|
+
headers = Utils::HeaderHash.new(headers)
|
15
|
+
|
16
|
+
# Skip compressing empty entity body responses and responses with
|
17
|
+
# no-transform set.
|
18
|
+
if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
|
19
|
+
headers['Cache-Control'].to_s =~ /\bno-transform\b/
|
20
|
+
return [status, headers, body]
|
21
|
+
end
|
22
|
+
|
23
|
+
request = Request.new(env)
|
24
|
+
|
25
|
+
encoding = Utils.select_best_encoding(%w(gzip deflate identity),
|
26
|
+
request.accept_encoding)
|
27
|
+
|
28
|
+
# Set the Vary HTTP header.
|
29
|
+
vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
|
30
|
+
unless vary.include?("*") || vary.include?("Accept-Encoding")
|
31
|
+
headers["Vary"] = vary.push("Accept-Encoding").join(",")
|
32
|
+
end
|
33
|
+
|
34
|
+
case encoding
|
35
|
+
when "gzip"
|
36
|
+
headers['Content-Encoding'] = "gzip"
|
37
|
+
headers.delete('Content-Length')
|
38
|
+
mtime = headers.key?("Last-Modified") ?
|
39
|
+
Time.httpdate(headers["Last-Modified"]) : Time.now
|
40
|
+
[status, headers, GzipStream.new(body, mtime)]
|
41
|
+
when "deflate"
|
42
|
+
headers['Content-Encoding'] = "deflate"
|
43
|
+
headers.delete('Content-Length')
|
44
|
+
[status, headers, DeflateStream.new(body)]
|
45
|
+
when "identity"
|
46
|
+
[status, headers, body]
|
47
|
+
when nil
|
48
|
+
message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
|
49
|
+
[406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
|
50
|
+
end
|
32
51
|
end
|
33
52
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
+
class GzipStream
|
54
|
+
def initialize(body, mtime)
|
55
|
+
@body = body
|
56
|
+
@mtime = mtime
|
57
|
+
end
|
58
|
+
|
59
|
+
def each(&block)
|
60
|
+
@writer = block
|
61
|
+
gzip =::Zlib::GzipWriter.new(self)
|
62
|
+
gzip.mtime = @mtime
|
63
|
+
@body.each { |part| gzip << part }
|
64
|
+
@body.close if @body.respond_to?(:close)
|
65
|
+
gzip.close
|
66
|
+
@writer = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def write(data)
|
70
|
+
@writer.call(data)
|
71
|
+
end
|
53
72
|
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.gzip(body, mtime)
|
57
|
-
io = StringIO.new
|
58
|
-
gzip = Zlib::GzipWriter.new(io)
|
59
|
-
gzip.mtime = mtime
|
60
|
-
|
61
|
-
# TODO: Add streaming
|
62
|
-
body.each { |part| gzip << part }
|
63
73
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
74
|
+
class DeflateStream
|
75
|
+
DEFLATE_ARGS = [
|
76
|
+
Zlib::DEFAULT_COMPRESSION,
|
77
|
+
# drop the zlib header which causes both Safari and IE to choke
|
78
|
+
-Zlib::MAX_WBITS,
|
79
|
+
Zlib::DEF_MEM_LEVEL,
|
80
|
+
Zlib::DEFAULT_STRATEGY
|
81
|
+
]
|
82
|
+
|
83
|
+
def initialize(body)
|
84
|
+
@body = body
|
85
|
+
end
|
86
|
+
|
87
|
+
def each
|
88
|
+
deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
|
89
|
+
@body.each { |part| yield deflater.deflate(part) }
|
90
|
+
@body.close if @body.respond_to?(:close)
|
91
|
+
yield deflater.finish
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
84
95
|
end
|
85
96
|
end
|
86
|
-
|
87
|
-
end
|
data/lib/rack/directory.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'time'
|
2
|
+
require 'rack/utils'
|
2
3
|
require 'rack/mime'
|
3
4
|
|
4
5
|
module Rack
|
@@ -69,7 +70,7 @@ table { width:100%%; }
|
|
69
70
|
return unless @path_info.include? ".."
|
70
71
|
|
71
72
|
body = "Forbidden\n"
|
72
|
-
size =
|
73
|
+
size = Rack::Utils.bytesize(body)
|
73
74
|
return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
|
74
75
|
end
|
75
76
|
|
@@ -88,6 +89,8 @@ table { width:100%%; }
|
|
88
89
|
type = stat.directory? ? 'directory' : Mime.mime_type(ext)
|
89
90
|
size = stat.directory? ? '-' : filesize_format(size)
|
90
91
|
mtime = stat.mtime.httpdate
|
92
|
+
url << '/' if stat.directory?
|
93
|
+
basename << '/' if stat.directory?
|
91
94
|
|
92
95
|
@files << [ url, basename, size, type, mtime ]
|
93
96
|
end
|
@@ -119,7 +122,7 @@ table { width:100%%; }
|
|
119
122
|
|
120
123
|
def entity_not_found
|
121
124
|
body = "Entity not found: #{@path_info}\n"
|
122
|
-
size =
|
125
|
+
size = Rack::Utils.bytesize(body)
|
123
126
|
return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
|
124
127
|
end
|
125
128
|
|
data/lib/rack/file.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'time'
|
2
|
+
require 'rack/utils'
|
2
3
|
require 'rack/mime'
|
3
4
|
|
4
5
|
module Rack
|
@@ -12,6 +13,8 @@ module Rack
|
|
12
13
|
attr_accessor :root
|
13
14
|
attr_accessor :path
|
14
15
|
|
16
|
+
alias :to_path :path
|
17
|
+
|
15
18
|
def initialize(root)
|
16
19
|
@root = root
|
17
20
|
end
|
@@ -57,7 +60,7 @@ module Rack
|
|
57
60
|
body = self
|
58
61
|
else
|
59
62
|
body = [F.read(@path)]
|
60
|
-
size = body.first
|
63
|
+
size = Utils.bytesize(body.first)
|
61
64
|
end
|
62
65
|
|
63
66
|
[200, {
|
data/lib/rack/handler.rb
CHANGED
@@ -10,16 +10,37 @@ module Rack
|
|
10
10
|
module Handler
|
11
11
|
def self.get(server)
|
12
12
|
return unless server
|
13
|
+
server = server.to_s
|
13
14
|
|
14
15
|
if klass = @handlers[server]
|
15
16
|
obj = Object
|
16
17
|
klass.split("::").each { |x| obj = obj.const_get(x) }
|
17
18
|
obj
|
18
19
|
else
|
19
|
-
|
20
|
+
try_require('rack/handler', server)
|
21
|
+
const_get(server)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
25
|
+
# Transforms server-name constants to their canonical form as filenames,
|
26
|
+
# then tries to require them but silences the LoadError if not found
|
27
|
+
#
|
28
|
+
# Naming convention:
|
29
|
+
#
|
30
|
+
# Foo # => 'foo'
|
31
|
+
# FooBar # => 'foo_bar.rb'
|
32
|
+
# FooBAR # => 'foobar.rb'
|
33
|
+
# FOObar # => 'foobar.rb'
|
34
|
+
# FOOBAR # => 'foobar.rb'
|
35
|
+
# FooBarBaz # => 'foo_bar_baz.rb'
|
36
|
+
def self.try_require(prefix, const_name)
|
37
|
+
file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
|
38
|
+
gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
|
39
|
+
|
40
|
+
require(::File.join(prefix, file))
|
41
|
+
rescue LoadError
|
42
|
+
end
|
43
|
+
|
23
44
|
def self.register(server, klass)
|
24
45
|
@handlers ||= {}
|
25
46
|
@handlers[server] = klass
|
data/lib/rack/handler/cgi.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rack/content_length'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Handler
|
3
5
|
class CGI
|
@@ -6,14 +8,16 @@ module Rack
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def self.serve(app)
|
11
|
+
app = ContentLength.new(app)
|
12
|
+
|
9
13
|
env = ENV.to_hash
|
10
14
|
env.delete "HTTP_CONTENT_LENGTH"
|
11
15
|
|
12
16
|
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
13
17
|
|
14
18
|
env.update({"rack.version" => [0,1],
|
15
|
-
"rack.input" =>
|
16
|
-
"rack.errors" =>
|
19
|
+
"rack.input" => $stdin,
|
20
|
+
"rack.errors" => $stderr,
|
17
21
|
|
18
22
|
"rack.multithread" => false,
|
19
23
|
"rack.multiprocess" => true,
|
@@ -38,7 +42,7 @@ module Rack
|
|
38
42
|
def self.send_headers(status, headers)
|
39
43
|
STDOUT.print "Status: #{status}\r\n"
|
40
44
|
headers.each { |k, vs|
|
41
|
-
vs.each { |v|
|
45
|
+
vs.split("\n").each { |v|
|
42
46
|
STDOUT.print "#{k}: #{v}\r\n"
|
43
47
|
}
|
44
48
|
}
|
data/lib/rack/handler/fastcgi.rb
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
require 'fcgi'
|
2
2
|
require 'socket'
|
3
|
+
require 'rack/content_length'
|
4
|
+
require 'rack/rewindable_input'
|
5
|
+
|
6
|
+
class FCGI::Stream
|
7
|
+
alias _rack_read_without_buffer read
|
8
|
+
|
9
|
+
def read(n, buffer=nil)
|
10
|
+
buf = _rack_read_without_buffer n
|
11
|
+
buffer.replace(buf.to_s) if buffer
|
12
|
+
buf
|
13
|
+
end
|
14
|
+
end
|
3
15
|
|
4
16
|
module Rack
|
5
17
|
module Handler
|
@@ -12,32 +24,18 @@ module Rack
|
|
12
24
|
}
|
13
25
|
end
|
14
26
|
|
15
|
-
module ProperStream # :nodoc:
|
16
|
-
def each # This is missing by default.
|
17
|
-
while line = gets
|
18
|
-
yield line
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def read(*args)
|
23
|
-
if args.empty?
|
24
|
-
super || "" # Empty string on EOF.
|
25
|
-
else
|
26
|
-
super
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
27
|
def self.serve(request, app)
|
28
|
+
app = Rack::ContentLength.new(app)
|
29
|
+
|
32
30
|
env = request.env
|
33
31
|
env.delete "HTTP_CONTENT_LENGTH"
|
34
32
|
|
35
|
-
request.in.extend ProperStream
|
36
|
-
|
37
33
|
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
34
|
+
|
35
|
+
rack_input = RewindableInput.new(request.in)
|
38
36
|
|
39
37
|
env.update({"rack.version" => [0,1],
|
40
|
-
"rack.input" =>
|
38
|
+
"rack.input" => rack_input,
|
41
39
|
"rack.errors" => request.err,
|
42
40
|
|
43
41
|
"rack.multithread" => false,
|
@@ -54,12 +52,16 @@ module Rack
|
|
54
52
|
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
|
55
53
|
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
|
56
54
|
|
57
|
-
status, headers, body = app.call(env)
|
58
55
|
begin
|
59
|
-
|
60
|
-
|
56
|
+
status, headers, body = app.call(env)
|
57
|
+
begin
|
58
|
+
send_headers request.out, status, headers
|
59
|
+
send_body request.out, body
|
60
|
+
ensure
|
61
|
+
body.close if body.respond_to? :close
|
62
|
+
end
|
61
63
|
ensure
|
62
|
-
|
64
|
+
rack_input.close
|
63
65
|
request.finish
|
64
66
|
end
|
65
67
|
end
|
@@ -67,7 +69,7 @@ module Rack
|
|
67
69
|
def self.send_headers(out, status, headers)
|
68
70
|
out.print "Status: #{status}\r\n"
|
69
71
|
headers.each { |k, vs|
|
70
|
-
vs.each { |v|
|
72
|
+
vs.split("\n").each { |v|
|
71
73
|
out.print "#{k}: #{v}\r\n"
|
72
74
|
}
|
73
75
|
}
|