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 | 11 |
| 12 import 'body.dart'; |
12 import 'shelf_unmodifiable_map.dart'; | 13 import 'shelf_unmodifiable_map.dart'; |
13 import 'util.dart'; | 14 import 'util.dart'; |
14 | 15 |
| 16 Body getBody(Message message) => message._body; |
| 17 |
15 /// Represents logic shared between [Request] and [Response]. | 18 /// Represents logic shared between [Request] and [Response]. |
16 abstract class Message { | 19 abstract class Message { |
17 /// The HTTP headers. | 20 /// The HTTP headers. |
18 /// | 21 /// |
19 /// The value is immutable. | 22 /// The value is immutable. |
20 final Map<String, String> headers; | 23 final Map<String, String> headers; |
21 | 24 |
22 /// Extra context that can be used by for middleware and handlers. | 25 /// Extra context that can be used by for middleware and handlers. |
23 /// | 26 /// |
24 /// For requests, this is used to pass data to inner middleware and handlers; | 27 /// For requests, this is used to pass data to inner middleware and handlers; |
25 /// for responses, it's used to pass data to outer middleware and handlers. | 28 /// for responses, it's used to pass data to outer middleware and handlers. |
26 /// | 29 /// |
27 /// Context properties that are used by a particular package should begin with | 30 /// Context properties that are used by a particular package should begin with |
28 /// that package's name followed by a period. For example, if [logRequests] | 31 /// that package's name followed by a period. For example, if [logRequests] |
29 /// wanted to take a prefix, its property name would be `"shelf.prefix"`, | 32 /// wanted to take a prefix, its property name would be `"shelf.prefix"`, |
30 /// since it's in the `shelf` package. | 33 /// since it's in the `shelf` package. |
31 /// | 34 /// |
32 /// The value is immutable. | 35 /// The value is immutable. |
33 final Map<String, Object> context; | 36 final Map<String, Object> context; |
34 | 37 |
35 /// The streaming body of the message. | 38 /// The streaming body of the message. |
36 /// | 39 /// |
37 /// This can be read via [read] or [readAsString]. | 40 /// This can be read via [read] or [readAsString]. |
38 final Stream<List<int>> _body; | 41 final Body _body; |
39 | |
40 /// This boolean indicates whether [_body] has been read. | |
41 /// | |
42 /// After calling [read], or [readAsString] (which internally calls [read]), | |
43 /// this will be `true`. | |
44 bool _bodyWasRead = false; | |
45 | 42 |
46 /// Creates a new [Message]. | 43 /// Creates a new [Message]. |
47 /// | 44 /// |
48 /// [body] is the response body. It may be either a [String], a | 45 /// [body] is the response body. It may be either a [String], a |
49 /// [Stream<List<int>>], or `null` to indicate no body. If it's a [String], | 46 /// [Stream<List<int>>], or `null` to indicate no body. If it's a [String], |
50 /// [encoding] is used to encode it to a [Stream<List<int>>]. It defaults to | 47 /// [encoding] is used to encode it to a [Stream<List<int>>]. It defaults to |
51 /// UTF-8. | 48 /// UTF-8. |
52 /// | 49 /// |
53 /// If [headers] is `null`, it is treated as empty. | 50 /// If [headers] is `null`, it is treated as empty. |
54 /// | 51 /// |
55 /// If [encoding] is passed, the "encoding" field of the Content-Type header | 52 /// If [encoding] is passed, the "encoding" field of the Content-Type header |
56 /// in [headers] will be set appropriately. If there is no existing | 53 /// in [headers] will be set appropriately. If there is no existing |
57 /// Content-Type header, it will be set to "application/octet-stream". | 54 /// Content-Type header, it will be set to "application/octet-stream". |
58 Message(body, {Encoding encoding, Map<String, String> headers, | 55 Message(body, {Encoding encoding, Map<String, String> headers, |
59 Map<String, Object> context}) | 56 Map<String, Object> context}) |
60 : this._body = _bodyToStream(body, encoding), | 57 : this._body = new Body(body, encoding), |
61 this.headers = new ShelfUnmodifiableMap<String>( | 58 this.headers = new ShelfUnmodifiableMap<String>( |
62 _adjustHeaders(headers, encoding), ignoreKeyCase: true), | 59 _adjustHeaders(headers, encoding), ignoreKeyCase: true), |
63 this.context = new ShelfUnmodifiableMap<Object>(context, | 60 this.context = new ShelfUnmodifiableMap<Object>(context, |
64 ignoreKeyCase: false); | 61 ignoreKeyCase: false); |
65 | 62 |
66 /// The contents of the content-length field in [headers]. | 63 /// The contents of the content-length field in [headers]. |
67 /// | 64 /// |
68 /// If not set, `null`. | 65 /// If not set, `null`. |
69 int get contentLength { | 66 int get contentLength { |
70 if (_contentLengthCache != null) return _contentLengthCache; | 67 if (_contentLengthCache != null) return _contentLengthCache; |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 if (_contentTypeCache != null) return _contentTypeCache; | 104 if (_contentTypeCache != null) return _contentTypeCache; |
108 if (!headers.containsKey('content-type')) return null; | 105 if (!headers.containsKey('content-type')) return null; |
109 _contentTypeCache = new MediaType.parse(headers['content-type']); | 106 _contentTypeCache = new MediaType.parse(headers['content-type']); |
110 return _contentTypeCache; | 107 return _contentTypeCache; |
111 } | 108 } |
112 MediaType _contentTypeCache; | 109 MediaType _contentTypeCache; |
113 | 110 |
114 /// Returns a [Stream] representing the body. | 111 /// Returns a [Stream] representing the body. |
115 /// | 112 /// |
116 /// Can only be called once. | 113 /// Can only be called once. |
117 Stream<List<int>> read() { | 114 Stream<List<int>> read() => _body.read(); |
118 if (_bodyWasRead) { | |
119 throw new StateError("The 'read' method can only be called once on a " | |
120 "shelf.Request/shelf.Response object."); | |
121 } | |
122 _bodyWasRead = true; | |
123 return _body; | |
124 } | |
125 | 115 |
126 /// Returns a [Future] containing the body as a String. | 116 /// Returns a [Future] containing the body as a String. |
127 /// | 117 /// |
128 /// If [encoding] is passed, that's used to decode the body. | 118 /// If [encoding] is passed, that's used to decode the body. |
129 /// Otherwise the encoding is taken from the Content-Type header. If that | 119 /// Otherwise the encoding is taken from the Content-Type header. If that |
130 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. | 120 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. |
131 /// | 121 /// |
132 /// This calls [read] internally, which can only be called once. | 122 /// This calls [read] internally, which can only be called once. |
133 Future<String> readAsString([Encoding encoding]) { | 123 Future<String> readAsString([Encoding encoding]) { |
134 if (encoding == null) encoding = this.encoding; | 124 if (encoding == null) encoding = this.encoding; |
135 if (encoding == null) encoding = UTF8; | 125 if (encoding == null) encoding = UTF8; |
136 return encoding.decodeStream(read()); | 126 return encoding.decodeStream(read()); |
137 } | 127 } |
138 | 128 |
139 /// Creates a new [Message] by copying existing values and applying specified | 129 /// Creates a new [Message] by copying existing values and applying specified |
140 /// changes. | 130 /// changes. |
141 Message change({Map<String, String> headers, Map<String, Object> context, | 131 Message change({Map<String, String> headers, Map<String, Object> context, |
142 body}); | 132 body}); |
143 } | 133 } |
144 | 134 |
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]. | 135 /// Adds information about [encoding] to [headers]. |
160 /// | 136 /// |
161 /// Returns a new map without modifying [headers]. | 137 /// Returns a new map without modifying [headers]. |
162 Map<String, String> _adjustHeaders( | 138 Map<String, String> _adjustHeaders( |
163 Map<String, String> headers, Encoding encoding) { | 139 Map<String, String> headers, Encoding encoding) { |
164 if (headers == null) headers = const {}; | 140 if (headers == null) headers = const {}; |
165 if (encoding == null) return headers; | 141 if (encoding == null) return headers; |
166 if (headers['content-type'] == null) { | 142 if (headers['content-type'] == null) { |
167 return addHeader(headers, 'content-type', | 143 return addHeader(headers, 'content-type', |
168 'application/octet-stream; charset=${encoding.name}'); | 144 'application/octet-stream; charset=${encoding.name}'); |
169 } | 145 } |
170 | 146 |
171 var contentType = new MediaType.parse(headers['content-type']).change( | 147 var contentType = new MediaType.parse(headers['content-type']).change( |
172 parameters: {'charset': encoding.name}); | 148 parameters: {'charset': encoding.name}); |
173 return addHeader(headers, 'content-type', contentType.toString()); | 149 return addHeader(headers, 'content-type', contentType.toString()); |
174 } | 150 } |
OLD | NEW |