| 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 |