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; | |
Søren Gjesse
2014/08/07 12:16:22
Shouldn't this be a copy of the list?
kustermann
2014/08/07 12:50:37
Yes.
| |
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 implementation of [Request]. | |
Søren Gjesse
2014/08/07 12:16:22
Please add comment to say that this is immutable,
kustermann
2014/08/07 12:50:37
Done. But the documentation on the stream ownershi
| |
196 class RequestImpl extends _Message implements Request { | |
197 final String method; | |
198 final Uri url; | |
199 | |
200 RequestImpl(this.method, this.url, {Headers headers, Stream<List<int>> body}) | |
201 : super(headers, body); | |
202 | |
203 RequestImpl replace( | |
204 {String method, Uri url, Headers headers, Stream<List<int>> body}) { | |
205 if (method == null) method = this.method; | |
206 if (url == null) url = this.url; | |
207 if (headers == null) headers = this.headers; | |
208 if (body == null) body = read(); | |
209 | |
210 return new RequestImpl(method, url, headers: headers, body: body); | |
211 } | |
212 } | |
213 | |
214 | |
215 /// An implementation of [Response]. | |
Søren Gjesse
2014/08/07 12:16:22
ditto.
kustermann
2014/08/07 12:50:37
Done.
| |
216 class ResponseImpl extends _Message implements Response { | |
217 final int statusCode; | |
218 | |
219 ResponseImpl(this.statusCode, {Headers headers, Stream<List<int>> body}) | |
220 : super(headers, body); | |
221 | |
222 /// Returns a new [ResponseImpl] by overriding `statusCode`, `headers` and | |
223 /// `body` if they are not null. | |
224 /// | |
225 /// In case no [body] was supplied, the current `body` will be used and is | |
226 /// therefore no longer available to users. This is a transfer of the owner | |
227 /// of the body stream to the returned object. | |
Søren Gjesse
2014/08/07 12:16:22
Add this comment above as well.
kustermann
2014/08/07 12:50:37
Done.
| |
228 ResponseImpl replace( | |
229 {int statusCode, Headers headers, Stream<List<int>> body}) { | |
230 if (statusCode == null) statusCode = this.statusCode; | |
231 if (headers == null) headers = this.headers; | |
232 if (body == null) body = read(); | |
233 | |
234 return new ResponseImpl(statusCode, headers: headers, body: body); | |
235 } | |
236 } | |
OLD | NEW |