| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library http_base; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 /// These headers should be ignored by [Client]s when making requests and when | |
| 10 /// receiving headers from a HTTP server. | |
| 11 const List<String> _TRANSPORT_HEADERS = | |
| 12 const ['connection', 'upgrade', 'keep-alive', 'transfer-encoding']; | |
| 13 | |
| 14 /// These headers cannot be folded into one header value via ',' joining. | |
| 15 const List<String> _COOKIE_HEADERS = const ['set-cookie', 'cookie']; | |
| 16 | |
| 17 /// Representation of a set of HTTP headers. | |
| 18 abstract class Headers { | |
| 19 /// Returns the names of all header fields. | |
| 20 Iterable<String> get names; | |
| 21 | |
| 22 /// Returns `true` if a header field of the specified [name] exist. | |
| 23 bool contains(String name); | |
| 24 | |
| 25 /// Returns the value for the header field named [name]. | |
| 26 /// | |
| 27 /// The HTTP standard supports multiple values for each header field name. | |
| 28 /// Header fields with multiple values can be represented as a | |
| 29 /// comma-separated list. If a header has multiple values the returned string | |
| 30 /// is the comma-separated list of all these values. | |
| 31 /// | |
| 32 /// For header field-names which do not allow combining multiple values with | |
| 33 /// comma, this index operator will throw `ArgumentError`. | |
| 34 /// This is currently the case for the 'Cookie' and 'Set-Cookie' headers. Use | |
| 35 /// `getMultiple` method to iterate over the header values for these. | |
| 36 String operator [](String name); | |
| 37 | |
| 38 /// Returns the values for the header field named [name]. | |
| 39 /// | |
| 40 /// The order in which the values for the field name appear is the same | |
| 41 /// as the order in which they are to be send or was received. | |
| 42 /// | |
| 43 /// If there are no header values named [name] `null` will be returned. | |
| 44 Iterable<String> getMultiple(String name); | |
| 45 } | |
| 46 | |
| 47 | |
| 48 /// Representation of a HTTP request. | |
| 49 abstract class Request { | |
| 50 /// Request method. | |
| 51 String get method; | |
| 52 | |
| 53 /// Request url. | |
| 54 Uri get url; | |
| 55 | |
| 56 /// Request headers. | |
| 57 Headers get headers; | |
| 58 | |
| 59 /// Request body. | |
| 60 Stream<List<int>> read(); | |
| 61 } | |
| 62 | |
| 63 | |
| 64 /// Representation of a HTTP response. | |
| 65 abstract class Response { | |
| 66 /// Response status code. | |
| 67 int get statusCode; | |
| 68 | |
| 69 /// Response headers. | |
| 70 Headers get headers; | |
| 71 | |
| 72 /// Response body. | |
| 73 Stream<List<int>> read(); | |
| 74 } | |
| 75 | |
| 76 | |
| 77 /// Function for performing an HTTP request. | |
| 78 /// | |
| 79 /// The [RequestHandler] may use any transport mechanism it wants | |
| 80 /// (e.g. HTTP/1.1, HTTP/2.0, SPDY) to perform the HTTP request. | |
| 81 /// | |
| 82 /// [RequestHandler]s are composable. E.g. A [RequestHandler] may add an | |
| 83 /// 'Authorization' header to [request] and forward to another [RequestHandler]. | |
| 84 /// | |
| 85 /// A [RequestHandler] may ignore connection specific headers in [request] and | |
| 86 /// may not present them in the [Response] object. | |
| 87 /// | |
| 88 /// Connection specific headers: | |
| 89 /// 'Connection', 'Upgrade', 'Keep-Alive', 'Transfer-Encoding' | |
| 90 typedef Future<Response> RequestHandler(Request request); | |
| 91 | |
| 92 | |
| 93 /// An implementation of [Headers]. | |
| 94 class HeadersImpl implements Headers { | |
| 95 static const HeadersImpl Empty = const HeadersImpl.empty(); | |
| 96 | |
| 97 final Map<String, List<String>> _m; | |
| 98 | |
| 99 /// Constructs a [HeadersImpl] with no headers. | |
| 100 const HeadersImpl.empty() : _m = const {}; | |
| 101 | |
| 102 /// Constructs a new [HeaderImpl] initialized with [map]. | |
| 103 /// | |
| 104 /// [map] must contain only String keys and either String or | |
| 105 /// Iterable<String> values. | |
| 106 HeadersImpl(Map map) : _m = {} { | |
| 107 _addDiff(map); | |
| 108 } | |
| 109 | |
| 110 /// Makes a copy of this [HeadersImpl] and replaces all headers in present in | |
| 111 /// [differenceMap]. | |
| 112 /// | |
| 113 /// [differenceMap] must contain only String keys and either String or | |
| 114 /// Iterable<String> values. | |
| 115 HeadersImpl replace(Map differenceMap) { | |
| 116 var headers = new HeadersImpl({}); | |
| 117 _m.forEach((String key, List<String> value) { | |
| 118 headers._m[key] = value; | |
| 119 }); | |
| 120 headers._addDiff(differenceMap); | |
| 121 return headers; | |
| 122 } | |
| 123 | |
| 124 void _addDiff(Map diff) { | |
| 125 diff.forEach((String key, value) { | |
| 126 key = key.toLowerCase(); | |
| 127 | |
| 128 if (value == null) { | |
| 129 _m.remove(key); | |
| 130 } else if (value is String) { | |
| 131 var values = new List(1); | |
| 132 values[0] = value; | |
| 133 _m[key] = values; | |
| 134 } else { | |
| 135 _m[key] = value.toList(); | |
| 136 } | |
| 137 }); | |
| 138 } | |
| 139 | |
| 140 Iterable<String> get names => _m.keys; | |
| 141 | |
| 142 bool contains(String name) => _m.containsKey(name.toLowerCase()); | |
| 143 | |
| 144 String operator [](String name) { | |
| 145 name = name.toLowerCase(); | |
| 146 | |
| 147 if (_COOKIE_HEADERS.contains(name)) { | |
| 148 throw new ArgumentError('Cannot use Headers[] with $name header.'); | |
| 149 } | |
| 150 | |
| 151 var values = _m[name]; | |
| 152 if (values == null) return null; | |
| 153 if (values.length == 1) return values.first; | |
| 154 return values.join(','); | |
| 155 } | |
| 156 | |
| 157 Iterable<String> getMultiple(String name) { | |
| 158 name = name.toLowerCase(); | |
| 159 var values = _m[name]; | |
| 160 if (values == null) return values; | |
| 161 | |
| 162 if (_COOKIE_HEADERS.contains(name)) { | |
| 163 return values; | |
| 164 } else { | |
| 165 return values.expand((e) => e.split(',')).map((e) => e.trim()); | |
| 166 } | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 | |
| 171 /// Internal helper class to reduce code duplication between [RequestImpl] | |
| 172 /// and [ResponseImpl]. | |
| 173 class _Message { | |
| 174 final Headers headers; | |
| 175 final Stream<List<int>> _body; | |
| 176 bool _bodyRead = false; | |
| 177 | |
| 178 _Message(Headers headers_, body) | |
| 179 : headers = headers_ != null ? headers_ : HeadersImpl.Empty, | |
| 180 _body = body != null ? body : (new StreamController()..close()).stream; | |
| 181 | |
| 182 /// Returns the [Stream] of bytes of this message. | |
| 183 /// | |
| 184 /// The body of a message can only be read once. | |
| 185 Stream<List<int>> read() { | |
| 186 if (_bodyRead) { | |
| 187 throw new StateError('The response stream has already been listened to.'); | |
| 188 } | |
| 189 _bodyRead = true; | |
| 190 return _body; | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 | |
| 195 /// An immutable implementation of [Request]. | |
| 196 /// | |
| 197 /// The request can be modified with the copy-on-write `replace` method. | |
| 198 class RequestImpl extends _Message implements Request { | |
| 199 final String method; | |
| 200 final Uri url; | |
| 201 | |
| 202 RequestImpl(this.method, this.url, {Headers headers, Stream<List<int>> body}) | |
| 203 : super(headers, body); | |
| 204 | |
| 205 /// Makes a copy of this [RequestImpl] by overriding `method`, `url`, | |
| 206 /// `headers` and `body` if they are not null. | |
| 207 /// | |
| 208 /// In case no [body] was supplied, the current `body` will be used and is | |
| 209 /// therefore no longer available to users. This is a transfer of the owner | |
| 210 /// of the body stream to the returned object. | |
| 211 RequestImpl replace( | |
| 212 {String method, Uri url, Headers headers, Stream<List<int>> body}) { | |
| 213 if (method == null) method = this.method; | |
| 214 if (url == null) url = this.url; | |
| 215 if (headers == null) headers = this.headers; | |
| 216 if (body == null) body = read(); | |
| 217 | |
| 218 return new RequestImpl(method, url, headers: headers, body: body); | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 | |
| 223 /// An immutable implementation of [Response]. | |
| 224 /// | |
| 225 /// The response can be modified with the copy-on-write `replace` method. | |
| 226 class ResponseImpl extends _Message implements Response { | |
| 227 final int statusCode; | |
| 228 | |
| 229 ResponseImpl(this.statusCode, {Headers headers, Stream<List<int>> body}) | |
| 230 : super(headers, body); | |
| 231 | |
| 232 /// Returns a copy of this [ResponseImpl] by overriding `statusCode`, | |
| 233 /// `headers` and `body` if they are not null. | |
| 234 /// | |
| 235 /// In case no [body] was supplied, the current `body` will be used and is | |
| 236 /// therefore no longer available to users. This is a transfer of the owner | |
| 237 /// of the body stream to the returned object. | |
| 238 ResponseImpl replace( | |
| 239 {int statusCode, Headers headers, Stream<List<int>> body}) { | |
| 240 if (statusCode == null) statusCode = this.statusCode; | |
| 241 if (headers == null) headers = this.headers; | |
| 242 if (body == null) body = read(); | |
| 243 | |
| 244 return new ResponseImpl(statusCode, headers: headers, body: body); | |
| 245 } | |
| 246 } | |
| OLD | NEW |