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 |