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:collection'; | 8 import 'dart:collection'; |
9 import 'dart:convert'; | 9 import 'dart:convert'; |
10 | 10 |
(...skipping 25 matching lines...) Expand all Loading... | |
36 /// The streaming body of the message. | 36 /// The streaming body of the message. |
37 /// | 37 /// |
38 /// This can be read via [read] or [readAsString]. | 38 /// This can be read via [read] or [readAsString]. |
39 final Stream<List<int>> _body; | 39 final Stream<List<int>> _body; |
40 | 40 |
41 /// Creates a new [Message]. | 41 /// Creates a new [Message]. |
42 /// | 42 /// |
43 /// If [headers] is `null`, it is treated as empty. | 43 /// If [headers] is `null`, it is treated as empty. |
44 Message(this._body, {Map<String, String> headers, | 44 Message(this._body, {Map<String, String> headers, |
45 Map<String, Object> context}) | 45 Map<String, Object> context}) |
46 : this.headers = _getIgnoreCaseMapView(headers), | 46 : this.headers = new _ShelfUnmodifiableMap<String>(headers, |
47 this.context = new pc.UnmodifiableMapView( | 47 ignoreKeyCase: true), |
48 context == null ? const {} : new Map.from(context)); | 48 this.context = new _ShelfUnmodifiableMap<Object>(context, |
49 ignoreKeyCase: false); | |
49 | 50 |
50 /// The contents of the content-length field in [headers]. | 51 /// The contents of the content-length field in [headers]. |
51 /// | 52 /// |
52 /// If not set, `null`. | 53 /// If not set, `null`. |
53 int get contentLength { | 54 int get contentLength { |
54 if (_contentLengthCache != null) return _contentLengthCache; | 55 if (_contentLengthCache != null) return _contentLengthCache; |
55 if (!headers.containsKey('content-length')) return null; | 56 if (!headers.containsKey('content-length')) return null; |
56 _contentLengthCache = int.parse(headers['content-length']); | 57 _contentLengthCache = int.parse(headers['content-length']); |
57 return _contentLengthCache; | 58 return _contentLengthCache; |
58 } | 59 } |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
105 /// If [encoding] is passed, that's used to decode the body. | 106 /// If [encoding] is passed, that's used to decode the body. |
106 /// Otherwise the encoding is taken from the Content-Type header. If that | 107 /// Otherwise the encoding is taken from the Content-Type header. If that |
107 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. | 108 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. |
108 /// | 109 /// |
109 /// This calls [read] internally, which can only be called once. | 110 /// This calls [read] internally, which can only be called once. |
110 Future<String> readAsString([Encoding encoding]) { | 111 Future<String> readAsString([Encoding encoding]) { |
111 if (encoding == null) encoding = this.encoding; | 112 if (encoding == null) encoding = this.encoding; |
112 if (encoding == null) encoding = UTF8; | 113 if (encoding == null) encoding = UTF8; |
113 return Chain.track(encoding.decodeStream(read())); | 114 return Chain.track(encoding.decodeStream(read())); |
114 } | 115 } |
116 | |
117 /// Creates a new [Message] by copying existing values and applying specified | |
118 /// changes. | |
119 Message change({Map<String, String> headers, Map<String, Object> context}); | |
115 } | 120 } |
116 | 121 |
117 /// Creates on an unmodifiable map of [headers] with case-insensitive access. | 122 /// Returns a [Map] with the values from [original] and the values from |
118 Map<String, String> _getIgnoreCaseMapView(Map<String, String> headers) { | 123 /// [updates]. |
119 if (headers == null) return const {}; | 124 /// |
120 // TODO(kevmoo) generalize this model with a 'canonical map' to align with | 125 /// For keys that are the same between [original] and [updates], the value in |
121 // similiar implementation in http pkg [BaseRequest]. | 126 /// [updates] is used. |
122 var map = new LinkedHashMap<String, String>( | 127 /// |
123 equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(), | 128 /// If [updates] is `null` or empty, [original] is returned unchanged. |
124 hashCode: (key) => key.toLowerCase().hashCode); | 129 Map updateMap(Map original, Map updates) { |
130 if (updates == null || updates.isEmpty) return original; | |
125 | 131 |
126 map.addAll(headers); | 132 return new Map.from(original) |
133 ..addAll(updates); | |
134 } | |
127 | 135 |
128 return new pc.UnmodifiableMapView<String, String>(map); | 136 /// A simple wrapper over [pc.UnmodifiableMapView] which avoids re-wrapping |
137 /// itself. | |
138 class _ShelfUnmodifiableMap<V> extends pc.UnmodifiableMapView<String, V> { | |
139 /// If `true`, the keys will have case-insensitive access. | |
140 final bool ignoreKeyCase; | |
nweiz
2014/04/30 20:21:39
This shouldn't be public.
kevmoo
2014/05/02 16:18:01
Done.
| |
141 | |
142 /// If [source] is a [_ShelfUnmodifiableMap] with matching [ignoreKeyCase], | |
143 /// then [source] is returned. | |
144 /// | |
145 /// If [source] is `null` it is treated like an empty map. | |
146 /// | |
147 /// If [ignoreKeyCase] is `true`, the keys will have case-insensitive access. | |
148 /// | |
149 /// [source] is copied to a new [Map] to ensure changes to the paramater value | |
150 /// after constructions are not reflected. | |
151 factory _ShelfUnmodifiableMap(Map<String, V> source, | |
152 {bool ignoreKeyCase: false}) { | |
153 if (source is _ShelfUnmodifiableMap<V> && | |
154 source.ignoreKeyCase == ignoreKeyCase) { | |
155 return source; | |
156 } | |
157 | |
158 if (source == null || source.isEmpty) { | |
159 return new _EmptyShelfUnmodifiableMap<V>(ignoreKeyCase); | |
160 } | |
161 | |
162 if (ignoreKeyCase) { | |
163 // TODO(kevmoo) generalize this model with a 'canonical map' to align with | |
164 // similiar implementation in http pkg [BaseRequest]. | |
165 var map = new LinkedHashMap<String, V>( | |
166 equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(), | |
167 hashCode: (key) => key.toLowerCase().hashCode); | |
168 | |
169 map.addAll(source); | |
170 | |
171 source = map; | |
172 } else { | |
173 source = new Map<String, V>.from(source); | |
174 } | |
175 | |
176 return new _ShelfUnmodifiableMap<V>._(source, ignoreKeyCase); | |
177 } | |
178 | |
179 _ShelfUnmodifiableMap._(Map<String, V> source, this.ignoreKeyCase) | |
180 : super(source); | |
129 } | 181 } |
182 | |
183 /// An const empty implementation of [_ShelfUnmodifiableMap]. | |
184 class _EmptyShelfUnmodifiableMap<V> extends pc.DelegatingMap<String, V> | |
185 implements _ShelfUnmodifiableMap<V> { | |
186 final bool ignoreKeyCase; | |
nweiz
2014/04/30 20:21:39
Once [ignoreKeyCase] is no longer public, there's
kevmoo
2014/05/02 16:18:01
Done.
| |
187 | |
188 /// Returns one of two const values for [_EmptyShelfUnmodifiableMap] depending | |
189 /// on the value for [ignoreKeyCase]. | |
190 factory _EmptyShelfUnmodifiableMap(bool ignoreKeyCase) { | |
191 if (ignoreKeyCase) { | |
192 return const _EmptyShelfUnmodifiableMap._(true); | |
193 } | |
194 return const _EmptyShelfUnmodifiableMap._(false); | |
195 } | |
196 | |
197 const _EmptyShelfUnmodifiableMap._(this.ignoreKeyCase) : super(const {}); | |
198 } | |
OLD | NEW |