OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library shelf.response; | 5 library shelf.response; |
6 | 6 |
7 import 'dart:async'; | |
8 import 'dart:convert'; | 7 import 'dart:convert'; |
9 | 8 |
10 import 'package:http_parser/http_parser.dart'; | 9 import 'package:http_parser/http_parser.dart'; |
11 | 10 |
12 import 'message.dart'; | 11 import 'message.dart'; |
13 import 'util.dart'; | 12 import 'util.dart'; |
14 | 13 |
15 /// The response returned by a [Handler]. | 14 /// The response returned by a [Handler]. |
16 class Response extends Message { | 15 class Response extends Message { |
17 /// The HTTP status code of the response. | 16 /// The HTTP status code of the response. |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 : this._redirect(303, location, body, headers, encoding, | 116 : this._redirect(303, location, body, headers, encoding, |
118 context: context); | 117 context: context); |
119 | 118 |
120 /// Constructs a helper constructor for redirect responses. | 119 /// Constructs a helper constructor for redirect responses. |
121 Response._redirect(int statusCode, location, body, | 120 Response._redirect(int statusCode, location, body, |
122 Map<String, String> headers, Encoding encoding, | 121 Map<String, String> headers, Encoding encoding, |
123 { Map<String, Object> context }) | 122 { Map<String, Object> context }) |
124 : this(statusCode, | 123 : this(statusCode, |
125 body: body, | 124 body: body, |
126 encoding: encoding, | 125 encoding: encoding, |
127 headers: _addHeader( | 126 headers: addHeader( |
128 headers, 'location', _locationToString(location)), | 127 headers, 'location', _locationToString(location)), |
129 context: context); | 128 context: context); |
130 | 129 |
131 /// Constructs a 304 Not Modified response. | 130 /// Constructs a 304 Not Modified response. |
132 /// | 131 /// |
133 /// This is used to respond to a conditional GET request that provided | 132 /// This is used to respond to a conditional GET request that provided |
134 /// information used to determine whether the requested resource has changed | 133 /// information used to determine whether the requested resource has changed |
135 /// since the last request. It indicates that the resource has not changed and | 134 /// since the last request. It indicates that the resource has not changed and |
136 /// the old value should be used. | 135 /// the old value should be used. |
137 Response.notModified({Map<String, String> headers, | 136 Response.notModified({Map<String, String> headers, |
138 Map<String, Object> context}) | 137 Map<String, Object> context}) |
139 : this(304, headers: _addHeader( | 138 : this(304, headers: addHeader( |
140 headers, 'date', formatHttpDate(new DateTime.now())), | 139 headers, 'date', formatHttpDate(new DateTime.now())), |
141 context: context); | 140 context: context); |
142 | 141 |
143 /// Constructs a 403 Forbidden response. | 142 /// Constructs a 403 Forbidden response. |
144 /// | 143 /// |
145 /// This indicates that the server is refusing to fulfill the request. | 144 /// This indicates that the server is refusing to fulfill the request. |
146 /// | 145 /// |
147 /// [body] is the response body. It may be a [String], a [Stream<List<int>>], | 146 /// [body] is the response body. It may be a [String], a [Stream<List<int>>], |
148 /// or `null`. If it's a [String], [encoding] is used to encode it to a | 147 /// or `null`. If it's a [String], [encoding] is used to encode it to a |
149 /// [Stream<List<int>>]. The default encoding is UTF-8. If it's `null` or not | 148 /// [Stream<List<int>>]. The default encoding is UTF-8. If it's `null` or not |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
206 /// [body] is the response body. It may be either a [String], a | 205 /// [body] is the response body. It may be either a [String], a |
207 /// [Stream<List<int>>], or `null` to indicate no body. | 206 /// [Stream<List<int>>], or `null` to indicate no body. |
208 /// If it's a [String], [encoding] is used to encode it to a | 207 /// If it's a [String], [encoding] is used to encode it to a |
209 /// [Stream<List<int>>]. The default encoding is UTF-8. | 208 /// [Stream<List<int>>]. The default encoding is UTF-8. |
210 /// | 209 /// |
211 /// If [encoding] is passed, the "encoding" field of the Content-Type header | 210 /// If [encoding] is passed, the "encoding" field of the Content-Type header |
212 /// in [headers] will be set appropriately. If there is no existing | 211 /// in [headers] will be set appropriately. If there is no existing |
213 /// Content-Type header, it will be set to "application/octet-stream". | 212 /// Content-Type header, it will be set to "application/octet-stream". |
214 Response(this.statusCode, {body, Map<String, String> headers, | 213 Response(this.statusCode, {body, Map<String, String> headers, |
215 Encoding encoding, Map<String, Object> context}) | 214 Encoding encoding, Map<String, Object> context}) |
216 : super(_bodyToStream(body, encoding), | 215 : super(body, encoding: encoding, headers: headers, context: context) { |
217 headers: _adjustHeaders(headers, encoding), | |
218 context: context) { | |
219 if (statusCode < 100) { | 216 if (statusCode < 100) { |
220 throw new ArgumentError("Invalid status code: $statusCode."); | 217 throw new ArgumentError("Invalid status code: $statusCode."); |
221 } | 218 } |
222 } | 219 } |
223 | 220 |
224 /// Creates a new [Response] by copying existing values and applying specified | 221 /// Creates a new [Response] by copying existing values and applying specified |
225 /// changes. | 222 /// changes. |
226 /// | 223 /// |
227 /// New key-value pairs in [context] and [headers] will be added to the copied | 224 /// New key-value pairs in [context] and [headers] will be added to the copied |
228 /// [Response]. | 225 /// [Response]. |
229 /// | 226 /// |
230 /// If [context] or [headers] includes a key that already exists, the | 227 /// If [context] or [headers] includes a key that already exists, the |
231 /// key-value pair will replace the corresponding entry in the copied | 228 /// key-value pair will replace the corresponding entry in the copied |
232 /// [Response]. | 229 /// [Response]. |
233 /// | 230 /// |
234 /// All other context and header values from the [Response] will be included | 231 /// All other context and header values from the [Response] will be included |
235 /// in the copied [Response] unchanged. | 232 /// in the copied [Response] unchanged. |
236 Response change({Map<String, String> headers, Map<String, Object> context}) { | 233 Response change({Map<String, String> headers, Map<String, Object> context}) { |
237 headers = updateMap(this.headers, headers); | 234 headers = updateMap(this.headers, headers); |
238 context = updateMap(this.context, context); | 235 context = updateMap(this.context, context); |
239 | 236 |
240 return new Response(this.statusCode, body: this.read(), headers: headers, | 237 return new Response(this.statusCode, body: this.read(), headers: headers, |
241 context: context); | 238 context: context); |
242 } | 239 } |
243 } | 240 } |
244 | 241 |
245 /// Converts [body] to a byte stream. | |
246 /// | |
247 /// [body] may be either a [String], a [Stream<List<int>>], or `null`. If it's a | |
248 /// [String], [encoding] will be used to convert it to a [Stream<List<int>>]. | |
249 Stream<List<int>> _bodyToStream(body, Encoding encoding) { | |
250 if (encoding == null) encoding = UTF8; | |
251 if (body == null) return new Stream.fromIterable([]); | |
252 if (body is String) return new Stream.fromIterable([encoding.encode(body)]); | |
253 if (body is Stream) return body; | |
254 | |
255 throw new ArgumentError('Response body "$body" must be a String or a ' | |
256 'Stream.'); | |
257 } | |
258 | |
259 /// Adds information about [encoding] to [headers]. | |
260 /// | |
261 /// Returns a new map without modifying [headers]. | |
262 Map<String, String> _adjustHeaders( | |
263 Map<String, String> headers, Encoding encoding) { | |
264 if (headers == null) headers = const {}; | |
265 if (encoding == null) return headers; | |
266 if (headers['content-type'] == null) { | |
267 return _addHeader(headers, 'content-type', | |
268 'application/octet-stream; charset=${encoding.name}'); | |
269 } | |
270 | |
271 var contentType = new MediaType.parse(headers['content-type']) | |
272 .change(parameters: {'charset': encoding.name}); | |
273 return _addHeader(headers, 'content-type', contentType.toString()); | |
274 } | |
275 | |
276 /// Adds a header with [name] and [value] to [headers], which may be null. | |
277 /// | |
278 /// Returns a new map without modifying [headers]. | |
279 Map<String, String> _addHeader( | |
280 Map<String, String> headers, String name, String value) { | |
281 headers = headers == null ? {} : new Map.from(headers); | |
282 headers[name] = value; | |
283 return headers; | |
284 } | |
285 | |
286 /// Adds content-type information to [headers]. | 242 /// Adds content-type information to [headers]. |
287 /// | 243 /// |
288 /// Returns a new map without modifying [headers]. This is used to add | 244 /// Returns a new map without modifying [headers]. This is used to add |
289 /// content-type information when creating a 500 response with a default body. | 245 /// content-type information when creating a 500 response with a default body. |
290 Map<String, String> _adjustErrorHeaders(Map<String, String> headers) { | 246 Map<String, String> _adjustErrorHeaders(Map<String, String> headers) { |
291 if (headers == null || headers['content-type'] == null) { | 247 if (headers == null || headers['content-type'] == null) { |
292 return _addHeader(headers, 'content-type', 'text/plain'); | 248 return addHeader(headers, 'content-type', 'text/plain'); |
293 } | 249 } |
294 | 250 |
295 var contentType = new MediaType.parse(headers['content-type']) | 251 var contentType = new MediaType.parse(headers['content-type']) |
296 .change(mimeType: 'text/plain'); | 252 .change(mimeType: 'text/plain'); |
297 return _addHeader(headers, 'content-type', contentType.toString()); | 253 return addHeader(headers, 'content-type', contentType.toString()); |
298 } | 254 } |
299 | 255 |
300 /// Converts [location], which may be a [String] or a [Uri], to a [String]. | 256 /// Converts [location], which may be a [String] or a [Uri], to a [String]. |
301 /// | 257 /// |
302 /// Throws an [ArgumentError] if [location] isn't a [String] or a [Uri]. | 258 /// Throws an [ArgumentError] if [location] isn't a [String] or a [Uri]. |
303 String _locationToString(location) { | 259 String _locationToString(location) { |
304 if (location is String) return location; | 260 if (location is String) return location; |
305 if (location is Uri) return location.toString(); | 261 if (location is Uri) return location.toString(); |
306 | 262 |
307 throw new ArgumentError('Response location must be a String or Uri, was ' | 263 throw new ArgumentError('Response location must be a String or Uri, was ' |
308 '"$location".'); | 264 '"$location".'); |
309 } | 265 } |
OLD | NEW |