| 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.message; | 5 library shelf.message; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 | 9 |
| 10 import 'package:http_parser/http_parser.dart'; | 10 import 'package:http_parser/http_parser.dart'; |
| 11 import 'package:stack_trace/stack_trace.dart'; | 11 import 'package:stack_trace/stack_trace.dart'; |
| 12 | 12 |
| 13 import 'shelf_unmodifiable_map.dart'; | 13 import 'shelf_unmodifiable_map.dart'; |
| 14 import 'util.dart'; |
| 14 | 15 |
| 15 /// Represents logic shared between [Request] and [Response]. | 16 /// Represents logic shared between [Request] and [Response]. |
| 16 abstract class Message { | 17 abstract class Message { |
| 17 /// The HTTP headers. | 18 /// The HTTP headers. |
| 18 /// | 19 /// |
| 19 /// The value is immutable. | 20 /// The value is immutable. |
| 20 final Map<String, String> headers; | 21 final Map<String, String> headers; |
| 21 | 22 |
| 22 /// Extra context that can be used by for middleware and handlers. | 23 /// Extra context that can be used by for middleware and handlers. |
| 23 /// | 24 /// |
| (...skipping 14 matching lines...) Expand all Loading... |
| 38 final Stream<List<int>> _body; | 39 final Stream<List<int>> _body; |
| 39 | 40 |
| 40 /// This boolean indicates whether [_body] has been read. | 41 /// This boolean indicates whether [_body] has been read. |
| 41 /// | 42 /// |
| 42 /// After calling [read], or [readAsString] (which internally calls [read]), | 43 /// After calling [read], or [readAsString] (which internally calls [read]), |
| 43 /// this will be `true`. | 44 /// this will be `true`. |
| 44 bool _bodyWasRead = false; | 45 bool _bodyWasRead = false; |
| 45 | 46 |
| 46 /// Creates a new [Message]. | 47 /// Creates a new [Message]. |
| 47 /// | 48 /// |
| 49 /// [body] is the response body. It may be either a [String], a |
| 50 /// [Stream<List<int>>], or `null` to indicate no body. If it's a [String], |
| 51 /// [encoding] is used to encode it to a [Stream<List<int>>]. It defaults to |
| 52 /// UTF-8. |
| 53 /// |
| 48 /// If [headers] is `null`, it is treated as empty. | 54 /// If [headers] is `null`, it is treated as empty. |
| 49 Message(this._body, | 55 /// |
| 50 {Map<String, String> headers, Map<String, Object> context}) | 56 /// If [encoding] is passed, the "encoding" field of the Content-Type header |
| 51 : this.headers = new ShelfUnmodifiableMap<String>(headers, | 57 /// in [headers] will be set appropriately. If there is no existing |
| 52 ignoreKeyCase: true), | 58 /// Content-Type header, it will be set to "application/octet-stream". |
| 59 Message(body, {Encoding encoding, Map<String, String> headers, |
| 60 Map<String, Object> context}) |
| 61 : this._body = _bodyToStream(body, encoding), |
| 62 this.headers = new ShelfUnmodifiableMap<String>( |
| 63 _adjustHeaders(headers, encoding), ignoreKeyCase: true), |
| 53 this.context = new ShelfUnmodifiableMap<Object>(context, | 64 this.context = new ShelfUnmodifiableMap<Object>(context, |
| 54 ignoreKeyCase: false); | 65 ignoreKeyCase: false); |
| 55 | 66 |
| 56 /// The contents of the content-length field in [headers]. | 67 /// The contents of the content-length field in [headers]. |
| 57 /// | 68 /// |
| 58 /// If not set, `null`. | 69 /// If not set, `null`. |
| 59 int get contentLength { | 70 int get contentLength { |
| 60 if (_contentLengthCache != null) return _contentLengthCache; | 71 if (_contentLengthCache != null) return _contentLengthCache; |
| 61 if (!headers.containsKey('content-length')) return null; | 72 if (!headers.containsKey('content-length')) return null; |
| 62 _contentLengthCache = int.parse(headers['content-length']); | 73 _contentLengthCache = int.parse(headers['content-length']); |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 123 Future<String> readAsString([Encoding encoding]) { | 134 Future<String> readAsString([Encoding encoding]) { |
| 124 if (encoding == null) encoding = this.encoding; | 135 if (encoding == null) encoding = this.encoding; |
| 125 if (encoding == null) encoding = UTF8; | 136 if (encoding == null) encoding = UTF8; |
| 126 return Chain.track(encoding.decodeStream(read())); | 137 return Chain.track(encoding.decodeStream(read())); |
| 127 } | 138 } |
| 128 | 139 |
| 129 /// Creates a new [Message] by copying existing values and applying specified | 140 /// Creates a new [Message] by copying existing values and applying specified |
| 130 /// changes. | 141 /// changes. |
| 131 Message change({Map<String, String> headers, Map<String, Object> context}); | 142 Message change({Map<String, String> headers, Map<String, Object> context}); |
| 132 } | 143 } |
| 144 |
| 145 /// Converts [body] to a byte stream. |
| 146 /// |
| 147 /// [body] may be either a [String], a [Stream<List<int>>], or `null`. If it's a |
| 148 /// [String], [encoding] will be used to convert it to a [Stream<List<int>>]. |
| 149 Stream<List<int>> _bodyToStream(body, Encoding encoding) { |
| 150 if (encoding == null) encoding = UTF8; |
| 151 if (body == null) return new Stream.fromIterable([]); |
| 152 if (body is String) return new Stream.fromIterable([encoding.encode(body)]); |
| 153 if (body is Stream) return body; |
| 154 |
| 155 throw new ArgumentError('Response body "$body" must be a String or a ' |
| 156 'Stream.'); |
| 157 } |
| 158 |
| 159 /// Adds information about [encoding] to [headers]. |
| 160 /// |
| 161 /// Returns a new map without modifying [headers]. |
| 162 Map<String, String> _adjustHeaders( |
| 163 Map<String, String> headers, Encoding encoding) { |
| 164 if (headers == null) headers = const {}; |
| 165 if (encoding == null) return headers; |
| 166 if (headers['content-type'] == null) { |
| 167 return addHeader(headers, 'content-type', |
| 168 'application/octet-stream; charset=${encoding.name}'); |
| 169 } |
| 170 |
| 171 var contentType = new MediaType.parse(headers['content-type']).change( |
| 172 parameters: {'charset': encoding.name}); |
| 173 return addHeader(headers, 'content-type', contentType.toString()); |
| 174 } |
| OLD | NEW |