rack 1.0.1 → 2.2.21
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +829 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +355 -0
- data/Rakefile +64 -98
- data/SPEC.rdoc +292 -0
- data/bin/rackup +3 -174
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rack_logo.svg +164 -111
- data/contrib/rdoc.css +412 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +7 -5
- data/lib/rack/auth/abstract/request.rb +12 -2
- data/lib/rack/auth/basic.rb +6 -4
- data/lib/rack/auth/digest/md5.rb +20 -13
- data/lib/rack/auth/digest/nonce.rb +6 -4
- data/lib/rack/auth/digest/params.rb +11 -12
- data/lib/rack/auth/digest/request.rb +15 -12
- data/lib/rack/body_proxy.rb +45 -0
- data/lib/rack/builder.rb +213 -19
- data/lib/rack/cascade.rb +50 -18
- data/lib/rack/chunked.rb +95 -27
- data/lib/rack/common_logger.rb +84 -0
- data/lib/rack/conditional_get.rb +83 -0
- data/lib/rack/config.rb +22 -0
- data/lib/rack/content_length.rb +19 -10
- data/lib/rack/content_type.rb +12 -5
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +90 -42
- data/lib/rack/directory.rb +118 -72
- data/lib/rack/etag.rb +77 -0
- data/lib/rack/events.rb +153 -0
- data/lib/rack/file.rb +4 -85
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +22 -24
- data/lib/rack/handler/fastcgi.rb +34 -24
- data/lib/rack/handler/lsws.rb +16 -15
- data/lib/rack/handler/scgi.rb +38 -29
- data/lib/rack/handler/thin.rb +23 -7
- data/lib/rack/handler/webrick.rb +95 -37
- data/lib/rack/handler.rb +49 -14
- data/lib/rack/head.rb +19 -13
- data/lib/rack/lint.rb +466 -217
- data/lib/rack/lobster.rb +19 -14
- data/lib/rack/lock.rb +24 -8
- data/lib/rack/logger.rb +20 -0
- data/lib/rack/media_type.rb +53 -0
- data/lib/rack/method_override.rb +52 -0
- data/lib/rack/mime.rb +652 -171
- data/lib/rack/mock.rb +203 -64
- data/lib/rack/multipart/generator.rb +97 -0
- data/lib/rack/multipart/parser.rb +440 -0
- data/lib/rack/multipart/uploaded_file.rb +41 -0
- data/lib/rack/multipart.rb +64 -0
- data/lib/rack/null_logger.rb +39 -0
- data/lib/rack/query_parser.rb +266 -0
- data/lib/rack/recursive.rb +18 -11
- data/lib/rack/reloader.rb +13 -5
- data/lib/rack/request.rb +607 -191
- data/lib/rack/response.rb +245 -110
- data/lib/rack/rewindable_input.rb +15 -21
- data/lib/rack/runtime.rb +34 -0
- data/lib/rack/sendfile.rb +192 -0
- data/lib/rack/server.rb +466 -0
- data/lib/rack/session/abstract/id.rb +449 -68
- data/lib/rack/session/cookie.rb +159 -47
- data/lib/rack/session/memcache.rb +4 -103
- data/lib/rack/session/pool.rb +39 -49
- data/lib/rack/show_exceptions.rb +390 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
- data/lib/rack/static.rb +164 -14
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +60 -18
- data/lib/rack/utils.rb +508 -277
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +85 -34
- data/rack.gemspec +43 -51
- metadata +119 -207
- data/KNOWN-ISSUES +0 -18
- data/RDOX +0 -428
- data/README +0 -364
- data/SPEC +0 -164
- data/lib/rack/adapter/camping.rb +0 -22
- data/lib/rack/auth/openid.rb +0 -480
- data/lib/rack/commonlogger.rb +0 -61
- data/lib/rack/conditionalget.rb +0 -47
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -87
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/methodoverride.rb +0 -27
- data/lib/rack/showexceptions.rb +0 -349
- data/test/cgi/lighttpd.conf +0 -20
- data/test/cgi/test +0 -9
- data/test/cgi/test.fcgi +0 -8
- data/test/cgi/test.ru +0 -7
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +0 -10
- data/test/multipart/ie +0 -6
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -10
- data/test/spec_rack_auth_basic.rb +0 -73
- data/test/spec_rack_auth_digest.rb +0 -226
- data/test/spec_rack_auth_openid.rb +0 -84
- data/test/spec_rack_builder.rb +0 -84
- data/test/spec_rack_camping.rb +0 -51
- data/test/spec_rack_cascade.rb +0 -50
- data/test/spec_rack_cgi.rb +0 -89
- data/test/spec_rack_chunked.rb +0 -62
- data/test/spec_rack_commonlogger.rb +0 -32
- data/test/spec_rack_conditionalget.rb +0 -41
- data/test/spec_rack_content_length.rb +0 -43
- data/test/spec_rack_content_type.rb +0 -30
- data/test/spec_rack_deflater.rb +0 -127
- data/test/spec_rack_directory.rb +0 -61
- data/test/spec_rack_fastcgi.rb +0 -89
- data/test/spec_rack_file.rb +0 -75
- data/test/spec_rack_handler.rb +0 -43
- data/test/spec_rack_head.rb +0 -30
- data/test/spec_rack_lint.rb +0 -521
- data/test/spec_rack_lobster.rb +0 -45
- data/test/spec_rack_lock.rb +0 -38
- data/test/spec_rack_methodoverride.rb +0 -60
- data/test/spec_rack_mock.rb +0 -157
- data/test/spec_rack_mongrel.rb +0 -189
- data/test/spec_rack_recursive.rb +0 -77
- data/test/spec_rack_request.rb +0 -504
- data/test/spec_rack_response.rb +0 -218
- data/test/spec_rack_rewindable_input.rb +0 -118
- data/test/spec_rack_session_cookie.rb +0 -82
- data/test/spec_rack_session_memcache.rb +0 -240
- data/test/spec_rack_session_pool.rb +0 -172
- data/test/spec_rack_showexceptions.rb +0 -21
- data/test/spec_rack_showstatus.rb +0 -72
- data/test/spec_rack_static.rb +0 -37
- data/test/spec_rack_thin.rb +0 -91
- data/test/spec_rack_urlmap.rb +0 -185
- data/test/spec_rack_utils.rb +0 -387
- data/test/spec_rack_webrick.rb +0 -130
- data/test/testrequest.rb +0 -57
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/response.rb
CHANGED
|
@@ -1,183 +1,318 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
3
4
|
|
|
4
5
|
module Rack
|
|
5
6
|
# Rack::Response provides a convenient interface to create a Rack
|
|
6
7
|
# response.
|
|
7
8
|
#
|
|
8
9
|
# It allows setting of headers and cookies, and provides useful
|
|
9
|
-
# defaults (
|
|
10
|
+
# defaults (an OK response with empty headers and body).
|
|
10
11
|
#
|
|
11
12
|
# You can use Response#write to iteratively generate your response,
|
|
12
13
|
# but note that this is buffered by Rack::Response until you call
|
|
13
14
|
# +finish+. +finish+ however can take a block inside which calls to
|
|
14
|
-
# +write+ are
|
|
15
|
+
# +write+ are synchronous with the Rack response.
|
|
15
16
|
#
|
|
16
17
|
# Your application's +call+ should end returning Response#finish.
|
|
17
|
-
|
|
18
18
|
class Response
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@status = status
|
|
23
|
-
@header = Utils::HeaderHash.new({"Content-Type" => "text/html"}.
|
|
24
|
-
merge(header))
|
|
25
|
-
|
|
26
|
-
@writer = lambda { |x| @body << x }
|
|
27
|
-
@block = nil
|
|
28
|
-
@length = 0
|
|
19
|
+
def self.[](status, headers, body)
|
|
20
|
+
self.new(body, status, headers)
|
|
21
|
+
end
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
CHUNKED = 'chunked'
|
|
24
|
+
STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
elsif body.respond_to?(:each)
|
|
35
|
-
body.each { |part|
|
|
36
|
-
write part.to_s
|
|
37
|
-
}
|
|
38
|
-
else
|
|
39
|
-
raise TypeError, "stringable or iterable required"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
yield self if block_given?
|
|
43
|
-
end
|
|
26
|
+
attr_accessor :length, :status, :body
|
|
27
|
+
attr_reader :headers
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
# @deprecated Use {#headers} instead.
|
|
30
|
+
alias header headers
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
32
|
+
# Initialize the response object with the specified body, status
|
|
33
|
+
# and headers.
|
|
34
|
+
#
|
|
35
|
+
# @param body [nil, #each, #to_str] the response body.
|
|
36
|
+
# @param status [Integer] the integer status as defined by the
|
|
37
|
+
# HTTP protocol RFCs.
|
|
38
|
+
# @param headers [#each] a list of key-value header pairs which
|
|
39
|
+
# conform to the HTTP protocol RFCs.
|
|
40
|
+
#
|
|
41
|
+
# Providing a body which responds to #to_str is legacy behaviour.
|
|
42
|
+
def initialize(body = nil, status = 200, headers = {})
|
|
43
|
+
@status = status.to_i
|
|
44
|
+
@headers = Utils::HeaderHash[headers]
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
header[key] = value
|
|
54
|
-
end
|
|
46
|
+
@writer = self.method(:append)
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
case value
|
|
58
|
-
when Hash
|
|
59
|
-
domain = "; domain=" + value[:domain] if value[:domain]
|
|
60
|
-
path = "; path=" + value[:path] if value[:path]
|
|
61
|
-
# According to RFC 2109, we need dashes here.
|
|
62
|
-
# N.B.: cgi.rb uses spaces...
|
|
63
|
-
expires = "; expires=" + value[:expires].clone.gmtime.
|
|
64
|
-
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
|
|
65
|
-
secure = "; secure" if value[:secure]
|
|
66
|
-
httponly = "; HttpOnly" if value[:httponly]
|
|
67
|
-
value = value[:value]
|
|
68
|
-
end
|
|
69
|
-
value = [value] unless Array === value
|
|
70
|
-
cookie = Utils.escape(key) + "=" +
|
|
71
|
-
value.map { |v| Utils.escape v }.join("&") +
|
|
72
|
-
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
|
73
|
-
|
|
74
|
-
case self["Set-Cookie"]
|
|
75
|
-
when Array
|
|
76
|
-
self["Set-Cookie"] << cookie
|
|
77
|
-
when String
|
|
78
|
-
self["Set-Cookie"] = [self["Set-Cookie"], cookie]
|
|
79
|
-
when nil
|
|
80
|
-
self["Set-Cookie"] = cookie
|
|
81
|
-
end
|
|
82
|
-
end
|
|
48
|
+
@block = nil
|
|
83
49
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
50
|
+
# Keep track of whether we have expanded the user supplied body.
|
|
51
|
+
if body.nil?
|
|
52
|
+
@body = []
|
|
53
|
+
@buffered = true
|
|
54
|
+
@length = 0
|
|
55
|
+
elsif body.respond_to?(:to_str)
|
|
56
|
+
@body = [body]
|
|
57
|
+
@buffered = true
|
|
58
|
+
@length = body.to_str.bytesize
|
|
59
|
+
else
|
|
60
|
+
@body = body
|
|
61
|
+
@buffered = false
|
|
62
|
+
@length = 0
|
|
87
63
|
end
|
|
88
64
|
|
|
89
|
-
self
|
|
90
|
-
cookie =~ /\A#{Utils.escape(key)}=/
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
set_cookie(key,
|
|
94
|
-
{:value => '', :path => nil, :domain => nil,
|
|
95
|
-
:expires => Time.at(0) }.merge(value))
|
|
65
|
+
yield self if block_given?
|
|
96
66
|
end
|
|
97
67
|
|
|
98
|
-
def redirect(target, status=302)
|
|
68
|
+
def redirect(target, status = 302)
|
|
99
69
|
self.status = status
|
|
100
|
-
self
|
|
70
|
+
self.location = target
|
|
101
71
|
end
|
|
102
72
|
|
|
103
|
-
def
|
|
104
|
-
|
|
73
|
+
def chunked?
|
|
74
|
+
CHUNKED == get_header(TRANSFER_ENCODING)
|
|
75
|
+
end
|
|
105
76
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
77
|
+
# Generate a response array consistent with the requirements of the SPEC.
|
|
78
|
+
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
|
79
|
+
# which is suitable to be returned from the middleware `#call(env)` method.
|
|
80
|
+
def finish(&block)
|
|
81
|
+
if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
|
|
82
|
+
delete_header CONTENT_TYPE
|
|
83
|
+
delete_header CONTENT_LENGTH
|
|
84
|
+
close
|
|
85
|
+
return [@status, @headers, []]
|
|
109
86
|
else
|
|
110
|
-
|
|
87
|
+
if block_given?
|
|
88
|
+
@block = block
|
|
89
|
+
return [@status, @headers, self]
|
|
90
|
+
else
|
|
91
|
+
return [@status, @headers, @body]
|
|
92
|
+
end
|
|
111
93
|
end
|
|
112
94
|
end
|
|
95
|
+
|
|
113
96
|
alias to_a finish # For *response
|
|
114
97
|
|
|
115
98
|
def each(&callback)
|
|
116
99
|
@body.each(&callback)
|
|
117
|
-
@
|
|
118
|
-
|
|
100
|
+
@buffered = true
|
|
101
|
+
|
|
102
|
+
if @block
|
|
103
|
+
@writer = callback
|
|
104
|
+
@block.call(self)
|
|
105
|
+
end
|
|
119
106
|
end
|
|
120
107
|
|
|
121
108
|
# Append to body and update Content-Length.
|
|
122
109
|
#
|
|
123
110
|
# NOTE: Do not mix #write and direct #body access!
|
|
124
111
|
#
|
|
125
|
-
def write(
|
|
126
|
-
|
|
127
|
-
@length += Rack::Utils.bytesize(s)
|
|
128
|
-
@writer.call s
|
|
112
|
+
def write(chunk)
|
|
113
|
+
buffered_body!
|
|
129
114
|
|
|
130
|
-
|
|
131
|
-
str
|
|
115
|
+
@writer.call(chunk.to_s)
|
|
132
116
|
end
|
|
133
117
|
|
|
134
118
|
def close
|
|
135
|
-
body.close if body.respond_to?(:close)
|
|
119
|
+
@body.close if @body.respond_to?(:close)
|
|
136
120
|
end
|
|
137
121
|
|
|
138
122
|
def empty?
|
|
139
123
|
@block == nil && @body.empty?
|
|
140
124
|
end
|
|
141
125
|
|
|
142
|
-
|
|
126
|
+
def has_header?(key); headers.key? key; end
|
|
127
|
+
def get_header(key); headers[key]; end
|
|
128
|
+
def set_header(key, v); headers[key] = v; end
|
|
129
|
+
def delete_header(key); headers.delete key; end
|
|
130
|
+
|
|
131
|
+
alias :[] :get_header
|
|
132
|
+
alias :[]= :set_header
|
|
143
133
|
|
|
144
134
|
module Helpers
|
|
145
|
-
def invalid?;
|
|
135
|
+
def invalid?; status < 100 || status >= 600; end
|
|
136
|
+
|
|
137
|
+
def informational?; status >= 100 && status < 200; end
|
|
138
|
+
def successful?; status >= 200 && status < 300; end
|
|
139
|
+
def redirection?; status >= 300 && status < 400; end
|
|
140
|
+
def client_error?; status >= 400 && status < 500; end
|
|
141
|
+
def server_error?; status >= 500 && status < 600; end
|
|
142
|
+
|
|
143
|
+
def ok?; status == 200; end
|
|
144
|
+
def created?; status == 201; end
|
|
145
|
+
def accepted?; status == 202; end
|
|
146
|
+
def no_content?; status == 204; end
|
|
147
|
+
def moved_permanently?; status == 301; end
|
|
148
|
+
def bad_request?; status == 400; end
|
|
149
|
+
def unauthorized?; status == 401; end
|
|
150
|
+
def forbidden?; status == 403; end
|
|
151
|
+
def not_found?; status == 404; end
|
|
152
|
+
def method_not_allowed?; status == 405; end
|
|
153
|
+
def precondition_failed?; status == 412; end
|
|
154
|
+
def unprocessable?; status == 422; end
|
|
155
|
+
|
|
156
|
+
def redirect?; [301, 302, 303, 307, 308].include? status; end
|
|
146
157
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def client_error?; @status >= 400 && @status < 500; end
|
|
151
|
-
def server_error?; @status >= 500 && @status < 600; end
|
|
158
|
+
def include?(header)
|
|
159
|
+
has_header? header
|
|
160
|
+
end
|
|
152
161
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
# Add a header that may have multiple values.
|
|
163
|
+
#
|
|
164
|
+
# Example:
|
|
165
|
+
# response.add_header 'Vary', 'Accept-Encoding'
|
|
166
|
+
# response.add_header 'Vary', 'Cookie'
|
|
167
|
+
#
|
|
168
|
+
# assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
|
|
169
|
+
#
|
|
170
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
171
|
+
def add_header(key, v)
|
|
172
|
+
if v.nil?
|
|
173
|
+
get_header key
|
|
174
|
+
elsif has_header? key
|
|
175
|
+
set_header key, "#{get_header key},#{v}"
|
|
176
|
+
else
|
|
177
|
+
set_header key, v
|
|
178
|
+
end
|
|
179
|
+
end
|
|
156
180
|
|
|
157
|
-
|
|
158
|
-
def
|
|
181
|
+
# Get the content type of the response.
|
|
182
|
+
def content_type
|
|
183
|
+
get_header CONTENT_TYPE
|
|
184
|
+
end
|
|
159
185
|
|
|
160
|
-
#
|
|
161
|
-
|
|
186
|
+
# Set the content type of the response.
|
|
187
|
+
def content_type=(content_type)
|
|
188
|
+
set_header CONTENT_TYPE, content_type
|
|
189
|
+
end
|
|
162
190
|
|
|
163
|
-
def
|
|
164
|
-
|
|
191
|
+
def media_type
|
|
192
|
+
MediaType.type(content_type)
|
|
165
193
|
end
|
|
166
194
|
|
|
167
|
-
def
|
|
168
|
-
|
|
195
|
+
def media_type_params
|
|
196
|
+
MediaType.params(content_type)
|
|
169
197
|
end
|
|
170
198
|
|
|
171
199
|
def content_length
|
|
172
|
-
cl =
|
|
200
|
+
cl = get_header CONTENT_LENGTH
|
|
173
201
|
cl ? cl.to_i : cl
|
|
174
202
|
end
|
|
175
203
|
|
|
176
204
|
def location
|
|
177
|
-
|
|
205
|
+
get_header "Location"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def location=(location)
|
|
209
|
+
set_header "Location", location
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def set_cookie(key, value)
|
|
213
|
+
cookie_header = get_header SET_COOKIE
|
|
214
|
+
set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def delete_cookie(key, value = {})
|
|
218
|
+
set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def set_cookie_header
|
|
222
|
+
get_header SET_COOKIE
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def set_cookie_header=(v)
|
|
226
|
+
set_header SET_COOKIE, v
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def cache_control
|
|
230
|
+
get_header CACHE_CONTROL
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def cache_control=(v)
|
|
234
|
+
set_header CACHE_CONTROL, v
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
|
238
|
+
def do_not_cache!
|
|
239
|
+
set_header CACHE_CONTROL, "no-cache, must-revalidate"
|
|
240
|
+
set_header EXPIRES, Time.now.httpdate
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Specify that the content should be cached.
|
|
244
|
+
# @param duration [Integer] The number of seconds until the cache expires.
|
|
245
|
+
# @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
|
|
246
|
+
def cache!(duration = 3600, directive: "public")
|
|
247
|
+
unless headers[CACHE_CONTROL] =~ /no-cache/
|
|
248
|
+
set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
|
|
249
|
+
set_header EXPIRES, (Time.now + duration).httpdate
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def etag
|
|
254
|
+
get_header ETAG
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def etag=(v)
|
|
258
|
+
set_header ETAG, v
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
protected
|
|
262
|
+
|
|
263
|
+
def buffered_body!
|
|
264
|
+
return if @buffered
|
|
265
|
+
|
|
266
|
+
if @body.is_a?(Array)
|
|
267
|
+
# The user supplied body was an array:
|
|
268
|
+
@body = @body.compact
|
|
269
|
+
@body.each do |part|
|
|
270
|
+
@length += part.to_s.bytesize
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
# Turn the user supplied body into a buffered array:
|
|
274
|
+
body = @body
|
|
275
|
+
@body = Array.new
|
|
276
|
+
|
|
277
|
+
body.each do |part|
|
|
278
|
+
@writer.call(part.to_s)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
body.close if body.respond_to?(:close)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
@buffered = true
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def append(chunk)
|
|
288
|
+
@body << chunk
|
|
289
|
+
|
|
290
|
+
unless chunked?
|
|
291
|
+
@length += chunk.bytesize
|
|
292
|
+
set_header(CONTENT_LENGTH, @length.to_s)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
return chunk
|
|
178
296
|
end
|
|
179
297
|
end
|
|
180
298
|
|
|
181
299
|
include Helpers
|
|
300
|
+
|
|
301
|
+
class Raw
|
|
302
|
+
include Helpers
|
|
303
|
+
|
|
304
|
+
attr_reader :headers
|
|
305
|
+
attr_accessor :status
|
|
306
|
+
|
|
307
|
+
def initialize(status, headers)
|
|
308
|
+
@status = status
|
|
309
|
+
@headers = headers
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def has_header?(key); headers.key? key; end
|
|
313
|
+
def get_header(key); headers[key]; end
|
|
314
|
+
def set_header(key, v); headers[key] = v; end
|
|
315
|
+
def delete_header(key); headers.delete key; end
|
|
316
|
+
end
|
|
182
317
|
end
|
|
183
318
|
end
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
1
4
|
require 'tempfile'
|
|
2
5
|
|
|
3
6
|
module Rack
|
|
@@ -16,29 +19,29 @@ module Rack
|
|
|
16
19
|
@rewindable_io = nil
|
|
17
20
|
@unlinked = false
|
|
18
21
|
end
|
|
19
|
-
|
|
22
|
+
|
|
20
23
|
def gets
|
|
21
24
|
make_rewindable unless @rewindable_io
|
|
22
25
|
@rewindable_io.gets
|
|
23
26
|
end
|
|
24
|
-
|
|
27
|
+
|
|
25
28
|
def read(*args)
|
|
26
29
|
make_rewindable unless @rewindable_io
|
|
27
30
|
@rewindable_io.read(*args)
|
|
28
31
|
end
|
|
29
|
-
|
|
32
|
+
|
|
30
33
|
def each(&block)
|
|
31
34
|
make_rewindable unless @rewindable_io
|
|
32
35
|
@rewindable_io.each(&block)
|
|
33
36
|
end
|
|
34
|
-
|
|
37
|
+
|
|
35
38
|
def rewind
|
|
36
39
|
make_rewindable unless @rewindable_io
|
|
37
40
|
@rewindable_io.rewind
|
|
38
41
|
end
|
|
39
|
-
|
|
42
|
+
|
|
40
43
|
# Closes this RewindableInput object without closing the originally
|
|
41
|
-
# wrapped IO
|
|
44
|
+
# wrapped IO object. Cleans up any temporary resources that this RewindableInput
|
|
42
45
|
# has created.
|
|
43
46
|
#
|
|
44
47
|
# This method may be called multiple times. It does nothing on subsequent calls.
|
|
@@ -52,17 +55,8 @@ module Rack
|
|
|
52
55
|
@rewindable_io = nil
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
|
-
|
|
58
|
+
|
|
56
59
|
private
|
|
57
|
-
|
|
58
|
-
# Ruby's Tempfile class has a bug. Subclass it and fix it.
|
|
59
|
-
class Tempfile < ::Tempfile
|
|
60
|
-
def _close
|
|
61
|
-
@tmpfile.close if @tmpfile
|
|
62
|
-
@data[1] = nil if @data
|
|
63
|
-
@tmpfile = nil
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
60
|
|
|
67
61
|
def make_rewindable
|
|
68
62
|
# Buffer all data into a tempfile. Since this tempfile is private to this
|
|
@@ -75,16 +69,16 @@ module Rack
|
|
|
75
69
|
@rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
|
|
76
70
|
@rewindable_io.binmode
|
|
77
71
|
if filesystem_has_posix_semantics?
|
|
78
|
-
@rewindable_io.
|
|
72
|
+
raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
|
|
79
73
|
@unlinked = true
|
|
80
74
|
end
|
|
81
|
-
|
|
82
|
-
buffer = ""
|
|
75
|
+
|
|
76
|
+
buffer = "".dup
|
|
83
77
|
while @io.read(1024 * 4, buffer)
|
|
84
78
|
entire_buffer_written_out = false
|
|
85
79
|
while !entire_buffer_written_out
|
|
86
80
|
written = @rewindable_io.write(buffer)
|
|
87
|
-
entire_buffer_written_out = written == buffer.
|
|
81
|
+
entire_buffer_written_out = written == buffer.bytesize
|
|
88
82
|
if !entire_buffer_written_out
|
|
89
83
|
buffer.slice!(0 .. written - 1)
|
|
90
84
|
end
|
|
@@ -92,7 +86,7 @@ module Rack
|
|
|
92
86
|
end
|
|
93
87
|
@rewindable_io.rewind
|
|
94
88
|
end
|
|
95
|
-
|
|
89
|
+
|
|
96
90
|
def filesystem_has_posix_semantics?
|
|
97
91
|
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
|
|
98
92
|
end
|
data/lib/rack/runtime.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
# Sets an "X-Runtime" response header, indicating the response
|
|
5
|
+
# time of the request, in seconds
|
|
6
|
+
#
|
|
7
|
+
# You can put it right before the application to see the processing
|
|
8
|
+
# time, or before all the other middlewares to include time for them,
|
|
9
|
+
# too.
|
|
10
|
+
class Runtime
|
|
11
|
+
FORMAT_STRING = "%0.6f" # :nodoc:
|
|
12
|
+
HEADER_NAME = "X-Runtime" # :nodoc:
|
|
13
|
+
|
|
14
|
+
def initialize(app, name = nil)
|
|
15
|
+
@app = app
|
|
16
|
+
@header_name = HEADER_NAME
|
|
17
|
+
@header_name += "-#{name}" if name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(env)
|
|
21
|
+
start_time = Utils.clock_time
|
|
22
|
+
status, headers, body = @app.call(env)
|
|
23
|
+
headers = Utils::HeaderHash[headers]
|
|
24
|
+
|
|
25
|
+
request_time = Utils.clock_time - start_time
|
|
26
|
+
|
|
27
|
+
unless headers.key?(@header_name)
|
|
28
|
+
headers[@header_name] = FORMAT_STRING % request_time
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
[status, headers, body]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|