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 |