homura-runtime 0.3.3 → 0.3.4
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/homura/runtime/version.rb +1 -1
- data/vendor/rack/auth/abstract/handler.rb +41 -0
- data/vendor/rack/auth/abstract/request.rb +51 -0
- data/vendor/rack/auth/basic.rb +58 -0
- data/vendor/rack/bad_request.rb +8 -0
- data/vendor/rack/body_proxy.rb +63 -0
- data/vendor/rack/builder.rb +315 -0
- data/vendor/rack/cascade.rb +67 -0
- data/vendor/rack/common_logger.rb +94 -0
- data/vendor/rack/conditional_get.rb +87 -0
- data/vendor/rack/config.rb +22 -0
- data/vendor/rack/constants.rb +68 -0
- data/vendor/rack/content_length.rb +34 -0
- data/vendor/rack/content_type.rb +33 -0
- data/vendor/rack/deflater.rb +159 -0
- data/vendor/rack/directory.rb +210 -0
- data/vendor/rack/etag.rb +71 -0
- data/vendor/rack/events.rb +172 -0
- data/vendor/rack/files.rb +224 -0
- data/vendor/rack/head.rb +25 -0
- data/vendor/rack/headers.rb +238 -0
- data/vendor/rack/lint.rb +1000 -0
- data/vendor/rack/lock.rb +29 -0
- data/vendor/rack/media_type.rb +42 -0
- data/vendor/rack/method_override.rb +56 -0
- data/vendor/rack/mime.rb +694 -0
- data/vendor/rack/mock.rb +3 -0
- data/vendor/rack/mock_request.rb +161 -0
- data/vendor/rack/mock_response.rb +147 -0
- data/vendor/rack/multipart/generator.rb +99 -0
- data/vendor/rack/multipart/parser.rb +586 -0
- data/vendor/rack/multipart/uploaded_file.rb +82 -0
- data/vendor/rack/multipart.rb +77 -0
- data/vendor/rack/null_logger.rb +48 -0
- data/vendor/rack/protection/authenticity_token.rb +256 -0
- data/vendor/rack/protection/base.rb +140 -0
- data/vendor/rack/protection/content_security_policy.rb +80 -0
- data/vendor/rack/protection/cookie_tossing.rb +77 -0
- data/vendor/rack/protection/escaped_params.rb +93 -0
- data/vendor/rack/protection/form_token.rb +25 -0
- data/vendor/rack/protection/frame_options.rb +39 -0
- data/vendor/rack/protection/http_origin.rb +43 -0
- data/vendor/rack/protection/ip_spoofing.rb +27 -0
- data/vendor/rack/protection/json_csrf.rb +60 -0
- data/vendor/rack/protection/path_traversal.rb +45 -0
- data/vendor/rack/protection/referrer_policy.rb +27 -0
- data/vendor/rack/protection/remote_referrer.rb +22 -0
- data/vendor/rack/protection/remote_token.rb +24 -0
- data/vendor/rack/protection/session_hijacking.rb +37 -0
- data/vendor/rack/protection/strict_transport.rb +41 -0
- data/vendor/rack/protection/version.rb +7 -0
- data/vendor/rack/protection/xss_header.rb +27 -0
- data/vendor/rack/protection.rb +58 -0
- data/vendor/rack/query_parser.rb +261 -0
- data/vendor/rack/recursive.rb +66 -0
- data/vendor/rack/reloader.rb +112 -0
- data/vendor/rack/request.rb +818 -0
- data/vendor/rack/response.rb +403 -0
- data/vendor/rack/rewindable_input.rb +116 -0
- data/vendor/rack/runtime.rb +35 -0
- data/vendor/rack/sendfile.rb +197 -0
- data/vendor/rack/session/abstract/id.rb +533 -0
- data/vendor/rack/session/constants.rb +13 -0
- data/vendor/rack/session/cookie.rb +292 -0
- data/vendor/rack/session/encryptor.rb +415 -0
- data/vendor/rack/session/pool.rb +76 -0
- data/vendor/rack/session/version.rb +10 -0
- data/vendor/rack/session.rb +12 -0
- data/vendor/rack/show_exceptions.rb +433 -0
- data/vendor/rack/show_status.rb +121 -0
- data/vendor/rack/static.rb +188 -0
- data/vendor/rack/tempfile_reaper.rb +44 -0
- data/vendor/rack/urlmap.rb +99 -0
- data/vendor/rack/utils.rb +631 -0
- data/vendor/rack/version.rb +17 -0
- data/vendor/rack.rb +66 -0
- metadata +76 -1
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
require_relative 'constants'
|
|
6
|
+
require_relative 'utils'
|
|
7
|
+
require_relative 'media_type'
|
|
8
|
+
require_relative 'headers'
|
|
9
|
+
|
|
10
|
+
module Rack
|
|
11
|
+
# Rack::Response provides a convenient interface to create a Rack
|
|
12
|
+
# response.
|
|
13
|
+
#
|
|
14
|
+
# It allows setting of headers and cookies, and provides useful
|
|
15
|
+
# defaults (an OK response with empty headers and body).
|
|
16
|
+
#
|
|
17
|
+
# You can use Response#write to iteratively generate your response,
|
|
18
|
+
# but note that this is buffered by Rack::Response until you call
|
|
19
|
+
# +finish+. +finish+ however can take a block inside which calls to
|
|
20
|
+
# +write+ are synchronous with the Rack response.
|
|
21
|
+
#
|
|
22
|
+
# Your application's +call+ should end returning Response#finish.
|
|
23
|
+
class Response
|
|
24
|
+
def self.[](status, headers, body)
|
|
25
|
+
self.new(body, status, headers)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
CHUNKED = 'chunked'
|
|
29
|
+
STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
|
|
30
|
+
|
|
31
|
+
attr_accessor :length, :status, :body
|
|
32
|
+
attr_reader :headers
|
|
33
|
+
|
|
34
|
+
# Initialize the response object with the specified +body+, +status+
|
|
35
|
+
# and +headers+.
|
|
36
|
+
#
|
|
37
|
+
# If the +body+ is +nil+, construct an empty response object with internal
|
|
38
|
+
# buffering.
|
|
39
|
+
#
|
|
40
|
+
# If the +body+ responds to +to_str+, assume it's a string-like object and
|
|
41
|
+
# construct a buffered response object containing using that string as the
|
|
42
|
+
# initial contents of the buffer.
|
|
43
|
+
#
|
|
44
|
+
# Otherwise it is expected +body+ conforms to the normal requirements of a
|
|
45
|
+
# Rack response body, typically implementing one of +each+ (enumerable
|
|
46
|
+
# body) or +call+ (streaming body).
|
|
47
|
+
#
|
|
48
|
+
# The +status+ defaults to +200+ which is the "OK" HTTP status code. You
|
|
49
|
+
# can provide any other valid status code.
|
|
50
|
+
#
|
|
51
|
+
# The +headers+ must be a +Hash+ of key-value header pairs which conform to
|
|
52
|
+
# the Rack specification for response headers. The key must be a +String+
|
|
53
|
+
# instance and the value can be either a +String+ or +Array+ instance.
|
|
54
|
+
def initialize(body = nil, status = 200, headers = {})
|
|
55
|
+
@status = status.to_i
|
|
56
|
+
|
|
57
|
+
unless headers.is_a?(Hash)
|
|
58
|
+
raise ArgumentError, "Headers must be a Hash!"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@headers = Headers.new
|
|
62
|
+
# Convert headers input to a plain hash with lowercase keys.
|
|
63
|
+
headers.each do |k, v|
|
|
64
|
+
@headers[k] = v
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@writer = self.method(:append)
|
|
68
|
+
|
|
69
|
+
@block = nil
|
|
70
|
+
|
|
71
|
+
# Keep track of whether we have expanded the user supplied body.
|
|
72
|
+
if body.nil?
|
|
73
|
+
@body = []
|
|
74
|
+
@buffered = true
|
|
75
|
+
# Body is unspecified - it may be a buffered response, or it may be a HEAD response.
|
|
76
|
+
@length = nil
|
|
77
|
+
elsif body.respond_to?(:to_str)
|
|
78
|
+
@body = [body]
|
|
79
|
+
@buffered = true
|
|
80
|
+
@length = body.to_str.bytesize
|
|
81
|
+
else
|
|
82
|
+
@body = body
|
|
83
|
+
@buffered = nil # undetermined as of yet.
|
|
84
|
+
@length = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
yield self if block_given?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def redirect(target, status = 302)
|
|
91
|
+
self.status = status
|
|
92
|
+
self.location = target
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def chunked?
|
|
96
|
+
CHUNKED == get_header(TRANSFER_ENCODING)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def no_entity_body?
|
|
100
|
+
# The response body is an enumerable body and it is not allowed to have an entity body.
|
|
101
|
+
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Generate a response array consistent with the requirements of the SPEC.
|
|
105
|
+
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
|
106
|
+
# which is suitable to be returned from the middleware `#call(env)` method.
|
|
107
|
+
def finish(&block)
|
|
108
|
+
if no_entity_body?
|
|
109
|
+
delete_header CONTENT_TYPE
|
|
110
|
+
delete_header CONTENT_LENGTH
|
|
111
|
+
close
|
|
112
|
+
return [@status, @headers, []]
|
|
113
|
+
else
|
|
114
|
+
if block_given?
|
|
115
|
+
# We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
|
|
116
|
+
@block = block
|
|
117
|
+
return [@status, @headers, self]
|
|
118
|
+
else
|
|
119
|
+
# If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
|
|
120
|
+
if @length && !chunked?
|
|
121
|
+
@headers[CONTENT_LENGTH] = @length.to_s
|
|
122
|
+
end
|
|
123
|
+
return [@status, @headers, @body]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
alias to_a finish # For *response
|
|
129
|
+
|
|
130
|
+
def each(&callback)
|
|
131
|
+
@body.each(&callback)
|
|
132
|
+
@buffered = true
|
|
133
|
+
|
|
134
|
+
if @block
|
|
135
|
+
@writer = callback
|
|
136
|
+
@block.call(self)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Append a chunk to the response body.
|
|
141
|
+
#
|
|
142
|
+
# Converts the response into a buffered response if it wasn't already.
|
|
143
|
+
#
|
|
144
|
+
# NOTE: Do not mix #write and direct #body access!
|
|
145
|
+
#
|
|
146
|
+
def write(chunk)
|
|
147
|
+
buffered_body!
|
|
148
|
+
|
|
149
|
+
@writer.call(chunk.to_s)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def close
|
|
153
|
+
@body.close if @body.respond_to?(:close)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def empty?
|
|
157
|
+
@block == nil && @body.empty?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def has_header?(key)
|
|
161
|
+
raise ArgumentError unless key.is_a?(String)
|
|
162
|
+
@headers.key?(key)
|
|
163
|
+
end
|
|
164
|
+
def get_header(key)
|
|
165
|
+
raise ArgumentError unless key.is_a?(String)
|
|
166
|
+
@headers[key]
|
|
167
|
+
end
|
|
168
|
+
def set_header(key, value)
|
|
169
|
+
raise ArgumentError unless key.is_a?(String)
|
|
170
|
+
@headers[key] = value
|
|
171
|
+
end
|
|
172
|
+
def delete_header(key)
|
|
173
|
+
raise ArgumentError unless key.is_a?(String)
|
|
174
|
+
@headers.delete key
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
alias :[] :get_header
|
|
178
|
+
alias :[]= :set_header
|
|
179
|
+
|
|
180
|
+
module Helpers
|
|
181
|
+
def invalid?; status < 100 || status >= 600; end
|
|
182
|
+
|
|
183
|
+
def informational?; status >= 100 && status < 200; end
|
|
184
|
+
def successful?; status >= 200 && status < 300; end
|
|
185
|
+
def redirection?; status >= 300 && status < 400; end
|
|
186
|
+
def client_error?; status >= 400 && status < 500; end
|
|
187
|
+
def server_error?; status >= 500 && status < 600; end
|
|
188
|
+
|
|
189
|
+
def ok?; status == 200; end
|
|
190
|
+
def created?; status == 201; end
|
|
191
|
+
def accepted?; status == 202; end
|
|
192
|
+
def no_content?; status == 204; end
|
|
193
|
+
def moved_permanently?; status == 301; end
|
|
194
|
+
def bad_request?; status == 400; end
|
|
195
|
+
def unauthorized?; status == 401; end
|
|
196
|
+
def forbidden?; status == 403; end
|
|
197
|
+
def not_found?; status == 404; end
|
|
198
|
+
def method_not_allowed?; status == 405; end
|
|
199
|
+
def not_acceptable?; status == 406; end
|
|
200
|
+
def request_timeout?; status == 408; end
|
|
201
|
+
def precondition_failed?; status == 412; end
|
|
202
|
+
def unprocessable?; status == 422; end
|
|
203
|
+
|
|
204
|
+
def redirect?; [301, 302, 303, 307, 308].include? status; end
|
|
205
|
+
|
|
206
|
+
def include?(header)
|
|
207
|
+
has_header?(header)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Add a header that may have multiple values.
|
|
211
|
+
#
|
|
212
|
+
# Example:
|
|
213
|
+
# response.add_header 'vary', 'accept-encoding'
|
|
214
|
+
# response.add_header 'vary', 'cookie'
|
|
215
|
+
#
|
|
216
|
+
# assert_equal 'accept-encoding,cookie', response.get_header('vary')
|
|
217
|
+
#
|
|
218
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
219
|
+
def add_header(key, value)
|
|
220
|
+
raise ArgumentError unless key.is_a?(String)
|
|
221
|
+
|
|
222
|
+
if value.nil?
|
|
223
|
+
return get_header(key)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
value = value.to_s
|
|
227
|
+
|
|
228
|
+
if header = get_header(key)
|
|
229
|
+
if header.is_a?(Array)
|
|
230
|
+
header << value
|
|
231
|
+
else
|
|
232
|
+
set_header(key, [header, value])
|
|
233
|
+
end
|
|
234
|
+
else
|
|
235
|
+
set_header(key, value)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Get the content type of the response.
|
|
240
|
+
def content_type
|
|
241
|
+
get_header CONTENT_TYPE
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Set the content type of the response.
|
|
245
|
+
def content_type=(content_type)
|
|
246
|
+
set_header CONTENT_TYPE, content_type
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def media_type
|
|
250
|
+
MediaType.type(content_type)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def media_type_params
|
|
254
|
+
MediaType.params(content_type)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def content_length
|
|
258
|
+
cl = get_header CONTENT_LENGTH
|
|
259
|
+
cl ? cl.to_i : cl
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def location
|
|
263
|
+
get_header "location"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def location=(location)
|
|
267
|
+
set_header "location", location
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def set_cookie(key, value)
|
|
271
|
+
add_header SET_COOKIE, Utils.set_cookie_header(key, value)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def delete_cookie(key, value = {})
|
|
275
|
+
set_header(SET_COOKIE,
|
|
276
|
+
Utils.delete_set_cookie_header!(
|
|
277
|
+
get_header(SET_COOKIE), key, value
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def set_cookie_header
|
|
283
|
+
get_header SET_COOKIE
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def set_cookie_header=(value)
|
|
287
|
+
set_header SET_COOKIE, value
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def cache_control
|
|
291
|
+
get_header CACHE_CONTROL
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def cache_control=(value)
|
|
295
|
+
set_header CACHE_CONTROL, value
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
|
299
|
+
def do_not_cache!
|
|
300
|
+
set_header CACHE_CONTROL, "no-cache, must-revalidate"
|
|
301
|
+
set_header EXPIRES, Time.now.httpdate
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Specify that the content should be cached.
|
|
305
|
+
# @param duration [Integer] The number of seconds until the cache expires.
|
|
306
|
+
# @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
|
|
307
|
+
def cache!(duration = 3600, directive: "public")
|
|
308
|
+
unless headers[CACHE_CONTROL] =~ /no-cache/
|
|
309
|
+
set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
|
|
310
|
+
set_header EXPIRES, (Time.now + duration).httpdate
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def etag
|
|
315
|
+
get_header ETAG
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def etag=(value)
|
|
319
|
+
set_header ETAG, value
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
protected
|
|
323
|
+
|
|
324
|
+
# Convert the body of this response into an internally buffered Array if possible.
|
|
325
|
+
#
|
|
326
|
+
# `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
|
|
327
|
+
# * `nil` - The body has not been buffered yet.
|
|
328
|
+
# * `true` - The body is buffered as an Array instance.
|
|
329
|
+
# * `false` - The body is not buffered and cannot be buffered.
|
|
330
|
+
#
|
|
331
|
+
# @return [Boolean] whether the body is buffered as an Array instance.
|
|
332
|
+
def buffered_body!
|
|
333
|
+
if @buffered.nil?
|
|
334
|
+
if @body.is_a?(Array)
|
|
335
|
+
# The user supplied body was an array:
|
|
336
|
+
@body = @body.compact
|
|
337
|
+
@length = @body.sum{|part| part.bytesize}
|
|
338
|
+
@buffered = true
|
|
339
|
+
elsif @body.respond_to?(:each)
|
|
340
|
+
# Turn the user supplied body into a buffered array:
|
|
341
|
+
body = @body
|
|
342
|
+
@body = Array.new
|
|
343
|
+
@buffered = true
|
|
344
|
+
|
|
345
|
+
body.each do |part|
|
|
346
|
+
@writer.call(part.to_s)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
body.close if body.respond_to?(:close)
|
|
350
|
+
else
|
|
351
|
+
# We don't know how to buffer the user-supplied body:
|
|
352
|
+
@buffered = false
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
return @buffered
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def append(chunk)
|
|
360
|
+
chunk = chunk.dup unless chunk.frozen?
|
|
361
|
+
@body << chunk
|
|
362
|
+
|
|
363
|
+
if @length
|
|
364
|
+
@length += chunk.bytesize
|
|
365
|
+
elsif @buffered
|
|
366
|
+
@length = chunk.bytesize
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
return chunk
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
include Helpers
|
|
374
|
+
|
|
375
|
+
class Raw
|
|
376
|
+
include Helpers
|
|
377
|
+
|
|
378
|
+
attr_reader :headers
|
|
379
|
+
attr_accessor :status
|
|
380
|
+
|
|
381
|
+
def initialize(status, headers)
|
|
382
|
+
@status = status
|
|
383
|
+
@headers = headers
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def has_header?(key)
|
|
387
|
+
headers.key?(key)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def get_header(key)
|
|
391
|
+
headers[key]
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def set_header(key, value)
|
|
395
|
+
headers[key] = value
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def delete_header(key)
|
|
399
|
+
headers.delete(key)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
require_relative 'constants'
|
|
7
|
+
|
|
8
|
+
module Rack
|
|
9
|
+
# Class which can make any IO object rewindable, including non-rewindable ones. It does
|
|
10
|
+
# this by buffering the data into a tempfile, which is rewindable.
|
|
11
|
+
#
|
|
12
|
+
# Don't forget to call #close when you're done. This frees up temporary resources that
|
|
13
|
+
# RewindableInput uses, though it does *not* close the original IO object.
|
|
14
|
+
class RewindableInput
|
|
15
|
+
# Makes rack.input rewindable, for compatibility with applications and middleware
|
|
16
|
+
# designed for earlier versions of Rack (where rack.input was required to be
|
|
17
|
+
# rewindable).
|
|
18
|
+
class Middleware
|
|
19
|
+
def initialize(app)
|
|
20
|
+
@app = app
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
if (input = env[RACK_INPUT])
|
|
25
|
+
env[RACK_INPUT] = RewindableInput.new(input)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@app.call(env)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(io)
|
|
33
|
+
@io = io
|
|
34
|
+
@rewindable_io = nil
|
|
35
|
+
@unlinked = false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def gets
|
|
39
|
+
make_rewindable unless @rewindable_io
|
|
40
|
+
@rewindable_io.gets
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def read(*args)
|
|
44
|
+
make_rewindable unless @rewindable_io
|
|
45
|
+
@rewindable_io.read(*args)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def each(&block)
|
|
49
|
+
make_rewindable unless @rewindable_io
|
|
50
|
+
@rewindable_io.each(&block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def rewind
|
|
54
|
+
make_rewindable unless @rewindable_io
|
|
55
|
+
@rewindable_io.rewind
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def size
|
|
59
|
+
make_rewindable unless @rewindable_io
|
|
60
|
+
@rewindable_io.size
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Closes this RewindableInput object without closing the originally
|
|
64
|
+
# wrapped IO object. Cleans up any temporary resources that this RewindableInput
|
|
65
|
+
# has created.
|
|
66
|
+
#
|
|
67
|
+
# This method may be called multiple times. It does nothing on subsequent calls.
|
|
68
|
+
def close
|
|
69
|
+
if @rewindable_io
|
|
70
|
+
if @unlinked
|
|
71
|
+
@rewindable_io.close
|
|
72
|
+
else
|
|
73
|
+
@rewindable_io.close!
|
|
74
|
+
end
|
|
75
|
+
@rewindable_io = nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def make_rewindable
|
|
82
|
+
# Buffer all data into a tempfile. Since this tempfile is private to this
|
|
83
|
+
# RewindableInput object, we chmod it so that nobody else can read or write
|
|
84
|
+
# it. On POSIX filesystems we also unlink the file so that it doesn't
|
|
85
|
+
# even have a file entry on the filesystem anymore, though we can still
|
|
86
|
+
# access it because we have the file handle open.
|
|
87
|
+
@rewindable_io = Tempfile.new('RackRewindableInput')
|
|
88
|
+
@rewindable_io.chmod(0000)
|
|
89
|
+
@rewindable_io.set_encoding(Encoding::BINARY)
|
|
90
|
+
@rewindable_io.binmode
|
|
91
|
+
# :nocov:
|
|
92
|
+
if filesystem_has_posix_semantics?
|
|
93
|
+
raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
|
|
94
|
+
@unlinked = true
|
|
95
|
+
end
|
|
96
|
+
# :nocov:
|
|
97
|
+
|
|
98
|
+
buffer = "".dup
|
|
99
|
+
while @io.read(1024 * 4, buffer)
|
|
100
|
+
entire_buffer_written_out = false
|
|
101
|
+
while !entire_buffer_written_out
|
|
102
|
+
written = @rewindable_io.write(buffer)
|
|
103
|
+
entire_buffer_written_out = written == buffer.bytesize
|
|
104
|
+
if !entire_buffer_written_out
|
|
105
|
+
buffer.slice!(0 .. written - 1)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
@rewindable_io.rewind
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def filesystem_has_posix_semantics?
|
|
113
|
+
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'utils'
|
|
4
|
+
|
|
5
|
+
module Rack
|
|
6
|
+
# Sets an "x-runtime" response header, indicating the response
|
|
7
|
+
# time of the request, in seconds
|
|
8
|
+
#
|
|
9
|
+
# You can put it right before the application to see the processing
|
|
10
|
+
# time, or before all the other middlewares to include time for them,
|
|
11
|
+
# too.
|
|
12
|
+
class Runtime
|
|
13
|
+
FORMAT_STRING = "%0.6f" # :nodoc:
|
|
14
|
+
HEADER_NAME = "x-runtime" # :nodoc:
|
|
15
|
+
|
|
16
|
+
def initialize(app, name = nil)
|
|
17
|
+
@app = app
|
|
18
|
+
@header_name = HEADER_NAME
|
|
19
|
+
@header_name += "-#{name.to_s.downcase}" if name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(env)
|
|
23
|
+
start_time = Utils.clock_time
|
|
24
|
+
_, headers, _ = response = @app.call(env)
|
|
25
|
+
|
|
26
|
+
request_time = Utils.clock_time - start_time
|
|
27
|
+
|
|
28
|
+
unless headers.key?(@header_name)
|
|
29
|
+
headers[@header_name] = FORMAT_STRING % request_time
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
response
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|