1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
|
# frozen_string_literal: true
require 'rack/request'
require 'rack/utils'
require 'rack/body_proxy'
require 'rack/media_type'
require 'time'
module Rack
# Rack::Response provides a convenient interface to create a Rack
# response.
#
# It allows setting of headers and cookies, and provides useful
# defaults (an OK response with empty headers and body).
#
# You can use Response#write to iteratively generate your response,
# but note that this is buffered by Rack::Response until you call
# +finish+. +finish+ however can take a block inside which calls to
# +write+ are synchronous with the Rack response.
#
# Your application's +call+ should end returning Response#finish.
class Response
def self.[](status, headers, body)
self.new(body, status, headers)
end
CHUNKED = 'chunked'
STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
# Initialize the response object with the specified body, status
# and headers.
#
# @param body [nil, #each, #to_str] the response body.
# @param status [Integer] the integer status as defined by the
# HTTP protocol RFCs.
# @param headers [#each] a list of key-value header pairs which
# conform to the HTTP protocol RFCs.
#
# Providing a body which responds to #to_str is legacy behaviour.
def initialize(body = nil, status = 200, headers = {})
@status = status.to_i
@headers = Utils::HeaderHash.new(headers)
@writer = self.method(:append)
@block = nil
@length = 0
# Keep track of whether we have expanded the user supplied body.
if body.nil?
@body = []
@buffered = true
elsif body.respond_to?(:to_str)
@body = [body]
@buffered = true
else
@body = body
@buffered = false
end
yield self if block_given?
end
attr_accessor :length, :status, :body
attr_reader :headers
# @deprecated Use {#headers} instead.
alias header headers
def redirect(target, status = 302)
self.status = status
self.location = target
end
def chunked?
CHUNKED == get_header(TRANSFER_ENCODING)
end
# Generate a response array consistent with the requirements of the SPEC.
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
# which is suitable to be returned from the middleware `#call(env)` method.
def finish(&block)
if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
delete_header CONTENT_TYPE
delete_header CONTENT_LENGTH
close
[status.to_i, header, []]
else
if block_given?
@block = block
[status.to_i, header, self]
else
[status.to_i, header, @body]
end
end
end
alias to_a finish # For *response
def each(&callback)
@body.each(&callback)
@buffered = true
if @block
@writer = callback
@block.call(self)
end
end
# Append to body and update Content-Length.
#
# NOTE: Do not mix #write and direct #body access!
#
def write(chunk)
buffered_body!
@writer.call(chunk.to_s)
end
def close
@body.close if @body.respond_to?(:close)
end
def empty?
@block == nil && @body.empty?
end
def has_header?(key); headers.key? key; end
def get_header(key); headers[key]; end
def set_header(key, v); headers[key] = v; end
def delete_header(key); headers.delete key; end
alias :[] :get_header
alias :[]= :set_header
module Helpers
def invalid?; status < 100 || status >= 600; end
def informational?; status >= 100 && status < 200; end
def successful?; status >= 200 && status < 300; end
def redirection?; status >= 300 && status < 400; end
def client_error?; status >= 400 && status < 500; end
def server_error?; status >= 500 && status < 600; end
def ok?; status == 200; end
def created?; status == 201; end
def accepted?; status == 202; end
def no_content?; status == 204; end
def moved_permanently?; status == 301; end
def bad_request?; status == 400; end
def unauthorized?; status == 401; end
def forbidden?; status == 403; end
def not_found?; status == 404; end
def method_not_allowed?; status == 405; end
def precondition_failed?; status == 412; end
def unprocessable?; status == 422; end
def redirect?; [301, 302, 303, 307, 308].include? status; end
def include?(header)
has_header? header
end
# Add a header that may have multiple values.
#
# Example:
# response.add_header 'Vary', 'Accept-Encoding'
# response.add_header 'Vary', 'Cookie'
#
# assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
#
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
def add_header key, v
if v.nil?
get_header key
elsif has_header? key
set_header key, "#{get_header key},#{v}"
else
set_header key, v
end
end
def content_type
get_header CONTENT_TYPE
end
def media_type
MediaType.type(content_type)
end
def media_type_params
MediaType.params(content_type)
end
def content_length
cl = get_header CONTENT_LENGTH
cl ? cl.to_i : cl
end
def location
get_header "Location"
end
def location=(location)
set_header "Location", location
end
def set_cookie(key, value)
cookie_header = get_header SET_COOKIE
set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
end
def delete_cookie(key, value = {})
set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
end
def set_cookie_header
get_header SET_COOKIE
end
def set_cookie_header= v
set_header SET_COOKIE, v
end
def cache_control
get_header CACHE_CONTROL
end
def cache_control= v
set_header CACHE_CONTROL, v
end
def etag
get_header ETAG
end
def etag= v
set_header ETAG, v
end
protected
def buffered_body!
return if @buffered
if @body.is_a?(Array)
# The user supplied body was an array:
@body = @body.compact
else
# Turn the user supplied body into a buffered array:
body = @body
@body = Array.new
body.each do |part|
@writer.call(part.to_s)
end
body.close if body.respond_to?(:close)
end
@buffered = true
end
def append(chunk)
@body << chunk
unless chunked?
@length += chunk.bytesize
set_header(CONTENT_LENGTH, @length.to_s)
end
return chunk
end
end
include Helpers
class Raw
include Helpers
attr_reader :headers
attr_accessor :status
def initialize status, headers
@status = status
@headers = headers
end
def has_header?(key); headers.key? key; end
def get_header(key); headers[key]; end
def set_header(key, v); headers[key] = v; end
def delete_header(key); headers.delete key; end
end
end
end
|