Chromium Code Reviews| 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, true), |
| 47 this.context = new pc.UnmodifiableMapView( | 47 this.context = new _ShelfUnmodifiableMap<Object>(context, false); |
| 48 context == null ? const {} : new Map.from(context)); | |
| 49 | 48 |
| 50 /// The contents of the content-length field in [headers]. | 49 /// The contents of the content-length field in [headers]. |
| 51 /// | 50 /// |
| 52 /// If not set, `null`. | 51 /// If not set, `null`. |
| 53 int get contentLength { | 52 int get contentLength { |
| 54 if (_contentLengthCache != null) return _contentLengthCache; | 53 if (_contentLengthCache != null) return _contentLengthCache; |
| 55 if (!headers.containsKey('content-length')) return null; | 54 if (!headers.containsKey('content-length')) return null; |
| 56 _contentLengthCache = int.parse(headers['content-length']); | 55 _contentLengthCache = int.parse(headers['content-length']); |
| 57 return _contentLengthCache; | 56 return _contentLengthCache; |
| 58 } | 57 } |
| (...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. | 104 /// If [encoding] is passed, that's used to decode the body. |
| 106 /// Otherwise the encoding is taken from the Content-Type header. If that | 105 /// 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. | 106 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. |
| 108 /// | 107 /// |
| 109 /// This calls [read] internally, which can only be called once. | 108 /// This calls [read] internally, which can only be called once. |
| 110 Future<String> readAsString([Encoding encoding]) { | 109 Future<String> readAsString([Encoding encoding]) { |
| 111 if (encoding == null) encoding = this.encoding; | 110 if (encoding == null) encoding = this.encoding; |
| 112 if (encoding == null) encoding = UTF8; | 111 if (encoding == null) encoding = UTF8; |
| 113 return Chain.track(encoding.decodeStream(read())); | 112 return Chain.track(encoding.decodeStream(read())); |
| 114 } | 113 } |
| 114 | |
| 115 /// Creates a new [Message] by copying existing values and applying specified | |
| 116 /// changes. | |
| 117 Message change({Map<String, String> headers, Map<String, Object> context}); | |
| 115 } | 118 } |
| 116 | 119 |
| 117 /// Creates on an unmodifiable map of [headers] with case-insensitive access. | 120 /// Returns a [Map] with the values from [original] and the values from |
| 118 Map<String, String> _getIgnoreCaseMapView(Map<String, String> headers) { | 121 /// [updates]. |
| 119 if (headers == null) return const {}; | 122 /// |
| 120 // TODO(kevmoo) generalize this model with a 'canonical map' to align with | 123 /// For keys that are the same between [original] and [updates], the value in |
| 121 // similiar implementation in http pkg [BaseRequest]. | 124 /// [updates] is used. |
| 122 var map = new LinkedHashMap<String, String>( | 125 /// |
| 123 equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(), | 126 /// If [updates] is `null` or empty, [original] is returned unchanged. |
| 124 hashCode: (key) => key.toLowerCase().hashCode); | 127 Map updateMap(Map original, Map updates) { |
|
nweiz
2014/04/29 19:48:48
Put this in util.
kevmoo
2014/04/30 14:27:52
Neither Request nor Response import util at the mo
nweiz
2014/04/30 20:21:39
It's not onerous to add an import statement.
kevmoo
2014/05/02 16:18:01
*grumble* Done *eye roll*
| |
| 128 if (updates == null || updates.isEmpty) return original; | |
| 125 | 129 |
| 126 map.addAll(headers); | 130 return new Map.from(original) |
| 131 ..addAll(updates); | |
| 132 } | |
| 127 | 133 |
| 128 return new pc.UnmodifiableMapView<String, String>(map); | 134 /// A simple wrapper over [pc.UnmodifiableMapView] which avoids re-wrapping |
| 135 /// itself. | |
| 136 /// | |
| 137 /// If [source] is a [_ShelfUnmodifiableMap] with matcher [ignoreKeyCase], then | |
|
nweiz
2014/04/29 19:48:48
"matcher" -> "matching"
This and the following te
kevmoo
2014/04/30 14:27:52
Done.
| |
| 138 /// [source] is returned. | |
| 139 /// | |
| 140 /// If [source] is `null` it is treated like an empty map. | |
| 141 /// | |
| 142 /// If [ignoreKeyCase] is `true`, the keys will have case-insensitive access. | |
| 143 /// | |
| 144 /// [source] is copied to a new [Map] to ensure changes to the paramater value | |
| 145 /// after constructions are not reflected. | |
| 146 class _ShelfUnmodifiableMap<V> extends pc.UnmodifiableMapView<String, V> { | |
|
nweiz
2014/04/29 19:48:48
This should probably go in its own library.
kevmoo
2014/04/30 14:27:52
I like having it private here. It's purely an impl
nweiz
2014/04/30 20:21:39
Shoving multiple classes into a single library whe
kevmoo
2014/05/02 16:18:01
fine fine Done yeah yeah
| |
| 147 final bool ignoreKeyCase; | |
|
nweiz
2014/04/29 19:48:48
Document this field.
kevmoo
2014/04/30 14:27:52
Done.
| |
| 148 | |
| 149 factory _ShelfUnmodifiableMap(Map<String, V> source, bool ignoreKeyCase) { | |
|
nweiz
2014/04/29 19:48:48
Make [ignoreKeyCase] a named argument so it's clea
kevmoo
2014/04/30 14:27:52
Done.
| |
| 150 if (source is _ShelfUnmodifiableMap<V> && | |
| 151 source.ignoreKeyCase == ignoreKeyCase) { | |
| 152 return source; | |
| 153 } | |
| 154 | |
| 155 if (source == null || source.isEmpty) { | |
| 156 return new _ShelfUnmodifiableMap<V>._(const {}, ignoreKeyCase); | |
|
nweiz
2014/04/29 19:48:48
It would be really nice to be able to just return
kevmoo
2014/04/30 14:27:52
Take a look at my alternative. We keep the generic
| |
| 157 } | |
| 158 | |
| 159 if (ignoreKeyCase) { | |
| 160 // TODO(kevmoo) generalize this model with a 'canonical map' to align with | |
| 161 // similiar implementation in http pkg [BaseRequest]. | |
| 162 var map = new LinkedHashMap<String, V>( | |
| 163 equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(), | |
| 164 hashCode: (key) => key.toLowerCase().hashCode); | |
| 165 | |
| 166 map.addAll(source); | |
| 167 | |
| 168 source = map; | |
| 169 } else { | |
| 170 source = new Map<String, V>.from(source); | |
| 171 } | |
| 172 | |
| 173 return new _ShelfUnmodifiableMap<V>._(source, ignoreKeyCase); | |
| 174 } | |
| 175 | |
| 176 _ShelfUnmodifiableMap._(Map<String, V> source, this.ignoreKeyCase) | |
| 177 : super(source); | |
| 129 } | 178 } |
| OLD | NEW |