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 http_base; | 5 library http_base; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 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 |
9 /// Representation of a set of HTTP headers. | 17 /// Representation of a set of HTTP headers. |
10 abstract class Headers { | 18 abstract class Headers { |
11 /// Returns the names of all header fields. | 19 /// Returns the names of all header fields. |
12 Iterable<String> get names; | 20 Iterable<String> get names; |
13 | 21 |
14 /// Returns `true` if a header field of the specified [name] exist. | 22 /// Returns `true` if a header field of the specified [name] exist. |
15 bool contains(String name); | 23 bool contains(String name); |
16 | 24 |
17 /// Returns the value for the header field named [name]. | 25 /// Returns the value for the header field named [name]. |
18 /// | 26 /// |
19 /// The HTTP standard supports multiple values for each header field name. | 27 /// The HTTP standard supports multiple values for each header field name. |
20 /// Header fields with multiple values can be represented as a | 28 /// Header fields with multiple values can be represented as a |
21 /// comma-separated list. If a header has multiple values the returned string | 29 /// comma-separated list. If a header has multiple values the returned string |
22 /// is the comma-separated list of all these values. | 30 /// is the comma-separated list of all these values. |
23 /// | 31 /// |
24 /// For header field-names which do not allow combining multiple values with | 32 /// For header field-names which do not allow combining multiple values with |
25 /// comma, this index operator will throw `IllegalArgument`. | 33 /// comma, this index operator will throw `ArgumentError`. |
26 /// This is currently the case for the 'Cookie' and 'Set-Cookie' headers. Use | 34 /// This is currently the case for the 'Cookie' and 'Set-Cookie' headers. Use |
27 /// `getMultiple` method to iterate over the header values for these. | 35 /// `getMultiple` method to iterate over the header values for these. |
28 String operator [](String name); | 36 String operator [](String name); |
29 | 37 |
30 /// Returns the values for the header field named [name]. | 38 /// Returns the values for the header field named [name]. |
31 /// | 39 /// |
32 /// The order in which the values for the field name appear is the same | 40 /// The order in which the values for the field name appear is the same |
33 /// as the order in which they are to be send or was received. | 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. |
34 Iterable<String> getMultiple(String name); | 44 Iterable<String> getMultiple(String name); |
35 } | 45 } |
36 | 46 |
| 47 |
37 /// Representation of a HTTP request. | 48 /// Representation of a HTTP request. |
38 abstract class Request { | 49 abstract class Request { |
39 /// Request method. | 50 /// Request method. |
40 String get method; | 51 String get method; |
41 | 52 |
42 /// Request url. | 53 /// Request url. |
43 Uri get url; | 54 Uri get url; |
44 | 55 |
45 /// Request headers. | 56 /// Request headers. |
46 Headers get headers; | 57 Headers get headers; |
47 | 58 |
48 /// Request body. | 59 /// Request body. |
49 Stream<List<int>> read(); | 60 Stream<List<int>> read(); |
50 } | 61 } |
51 | 62 |
| 63 |
52 /// Representation of a HTTP response. | 64 /// Representation of a HTTP response. |
53 abstract class Response { | 65 abstract class Response { |
54 /// Response status code. | 66 /// Response status code. |
55 int get statusCode; | 67 int get statusCode; |
56 | 68 |
57 /// Response headers. | 69 /// Response headers. |
58 Headers get headers; | 70 Headers get headers; |
59 | 71 |
60 /// Response body. | 72 /// Response body. |
61 Stream<List<int>> read(); | 73 Stream<List<int>> read(); |
62 } | 74 } |
63 | 75 |
| 76 |
64 /// Function for performing an HTTP request. | 77 /// Function for performing an HTTP request. |
65 /// | 78 /// |
66 /// The [RequestHandler] may use any transport mechanism it wants | 79 /// The [RequestHandler] may use any transport mechanism it wants |
67 /// (e.g. HTTP/1.1, HTTP/2.0, SPDY) to perform the HTTP request. | 80 /// (e.g. HTTP/1.1, HTTP/2.0, SPDY) to perform the HTTP request. |
68 /// | 81 /// |
69 /// [RequestHandler]s are composable. E.g. A [RequestHandler] may add an | 82 /// [RequestHandler]s are composable. E.g. A [RequestHandler] may add an |
70 /// 'Authorization' header to [request] and forward to another [RequestHandler]. | 83 /// 'Authorization' header to [request] and forward to another [RequestHandler]. |
71 /// | 84 /// |
72 /// A [RequestHandler] may ignore connection specific headers in [request] and | 85 /// A [RequestHandler] may ignore connection specific headers in [request] and |
73 /// may not present them in the [Response] object. | 86 /// may not present them in the [Response] object. |
74 /// | 87 /// |
75 /// Connection specific headers: | 88 /// Connection specific headers: |
76 /// 'Connection', 'Upgrade', 'Keep-Alive', 'Transfer-Encoding' | 89 /// 'Connection', 'Upgrade', 'Keep-Alive', 'Transfer-Encoding' |
77 typedef Future<Response> RequestHandler(Request request); | 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 |