Chromium Code Reviews| Index: dart/pkg/http_base/lib/http_base.dart |
| diff --git a/dart/pkg/http_base/lib/http_base.dart b/dart/pkg/http_base/lib/http_base.dart |
| index 55f6014df0d1e02f59fc20bd91174ff43110152d..413c749a088280f17e7a5bbd193008c08a272d65 100644 |
| --- a/dart/pkg/http_base/lib/http_base.dart |
| +++ b/dart/pkg/http_base/lib/http_base.dart |
| @@ -6,6 +6,14 @@ library http_base; |
| import 'dart:async'; |
| +/// These headers should be ignored by [Client]s when making requests and when |
| +/// receiving headers from a HTTP server. |
| +const List<String> _TRANSPORT_HEADERS = |
| + const ['connection', 'upgrade', 'keep-alive', 'transfer-encoding']; |
| + |
| +/// These headers cannot be folded into one header value via ',' joining. |
| +const List<String> _COOKIE_HEADERS = const ['set-cookie', 'cookie']; |
| + |
| /// Representation of a set of HTTP headers. |
| abstract class Headers { |
| /// Returns the names of all header fields. |
| @@ -22,7 +30,7 @@ abstract class Headers { |
| /// is the comma-separated list of all these values. |
| /// |
| /// For header field-names which do not allow combining multiple values with |
| - /// comma, this index operator will throw `IllegalArgument`. |
| + /// comma, this index operator will throw `ArgumentError`. |
| /// This is currently the case for the 'Cookie' and 'Set-Cookie' headers. Use |
| /// `getMultiple` method to iterate over the header values for these. |
| String operator [](String name); |
| @@ -31,9 +39,12 @@ abstract class Headers { |
| /// |
| /// The order in which the values for the field name appear is the same |
| /// as the order in which they are to be send or was received. |
| + /// |
| + /// If there are no header values named [name] `null` will be returned. |
| Iterable<String> getMultiple(String name); |
| } |
| + |
| /// Representation of a HTTP request. |
| abstract class Request { |
| /// Request method. |
| @@ -49,6 +60,7 @@ abstract class Request { |
| Stream<List<int>> read(); |
| } |
| + |
| /// Representation of a HTTP response. |
| abstract class Response { |
| /// Response status code. |
| @@ -61,6 +73,7 @@ abstract class Response { |
| Stream<List<int>> read(); |
| } |
| + |
| /// Function for performing an HTTP request. |
| /// |
| /// The [RequestHandler] may use any transport mechanism it wants |
| @@ -75,3 +88,149 @@ abstract class Response { |
| /// Connection specific headers: |
| /// 'Connection', 'Upgrade', 'Keep-Alive', 'Transfer-Encoding' |
| typedef Future<Response> RequestHandler(Request request); |
| + |
| + |
| +/// An implementation of [Headers]. |
| +class HeadersImpl implements Headers { |
| + static const HeadersImpl Empty = const HeadersImpl.empty(); |
| + |
| + final Map<String, List<String>> _m; |
| + |
| + /// Constructs a [HeadersImpl] with no headers. |
| + const HeadersImpl.empty() : _m = const {}; |
| + |
| + /// Constructs a new [HeaderImpl] initialized with [map]. |
| + /// |
| + /// [map] must contain only String keys and either String or |
| + /// Iterable<String> values. |
| + HeadersImpl(Map map) : _m = {} { |
| + _addDiff(map); |
| + } |
| + |
| + /// Makes a copy of this [HeadersImpl] and replaces all headers in present in |
| + /// [differenceMap]. |
| + /// |
| + /// [differenceMap] must contain only String keys and either String or |
| + /// Iterable<String> values. |
| + HeadersImpl replace(Map differenceMap) { |
| + var headers = new HeadersImpl({}); |
| + _m.forEach((String key, List<String> value) { |
| + headers._m[key] = value; |
| + }); |
| + headers._addDiff(differenceMap); |
| + return headers; |
| + } |
| + |
| + void _addDiff(Map diff) { |
| + diff.forEach((String key, value) { |
| + key = key.toLowerCase(); |
| + |
| + if (value == null) { |
| + _m.remove(key); |
| + } else if (value is String) { |
| + var values = new List(1); |
| + values[0] = value; |
| + _m[key] = values; |
| + } else { |
| + _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.
|
| + } |
| + }); |
| + } |
| + |
| + Iterable<String> get names => _m.keys; |
| + |
| + bool contains(String name) => _m.containsKey(name.toLowerCase()); |
| + |
| + String operator [](String name) { |
| + name = name.toLowerCase(); |
| + |
| + if (_COOKIE_HEADERS.contains(name)) { |
| + throw new ArgumentError('Cannot use Headers[] with $name header.'); |
| + } |
| + |
| + var values = _m[name]; |
| + if (values == null) return null; |
| + if (values.length == 1) return values.first; |
| + return values.join(','); |
| + } |
| + |
| + Iterable<String> getMultiple(String name) { |
| + name = name.toLowerCase(); |
| + var values = _m[name]; |
| + if (values == null) return values; |
| + |
| + if (_COOKIE_HEADERS.contains(name)) { |
| + return values; |
| + } else { |
| + return values.expand((e) => e.split(',')).map((e) => e.trim()); |
| + } |
| + } |
| +} |
| + |
| + |
| +/// Internal helper class to reduce code duplication between [RequestImpl] |
| +/// and [ResponseImpl]. |
| +class _Message { |
| + final Headers headers; |
| + final Stream<List<int>> _body; |
| + bool _bodyRead = false; |
| + |
| + _Message(Headers headers_, body) |
| + : headers = headers_ != null ? headers_ : HeadersImpl.Empty, |
| + _body = body != null ? body : (new StreamController()..close()).stream; |
| + |
| + /// Returns the [Stream] of bytes of this message. |
| + /// |
| + /// The body of a message can only be read once. |
| + Stream<List<int>> read() { |
| + if (_bodyRead) { |
| + throw new StateError('The response stream has already been listened to.'); |
| + } |
| + _bodyRead = true; |
| + return _body; |
| + } |
| +} |
| + |
| + |
| +/// 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
|
| +class RequestImpl extends _Message implements Request { |
| + final String method; |
| + final Uri url; |
| + |
| + RequestImpl(this.method, this.url, {Headers headers, Stream<List<int>> body}) |
| + : super(headers, body); |
| + |
| + RequestImpl replace( |
| + {String method, Uri url, Headers headers, Stream<List<int>> body}) { |
| + if (method == null) method = this.method; |
| + if (url == null) url = this.url; |
| + if (headers == null) headers = this.headers; |
| + if (body == null) body = read(); |
| + |
| + return new RequestImpl(method, url, headers: headers, body: body); |
| + } |
| +} |
| + |
| + |
| +/// An implementation of [Response]. |
|
Søren Gjesse
2014/08/07 12:16:22
ditto.
kustermann
2014/08/07 12:50:37
Done.
|
| +class ResponseImpl extends _Message implements Response { |
| + final int statusCode; |
| + |
| + ResponseImpl(this.statusCode, {Headers headers, Stream<List<int>> body}) |
| + : super(headers, body); |
| + |
| + /// Returns a new [ResponseImpl] by overriding `statusCode`, `headers` and |
| + /// `body` if they are not null. |
| + /// |
| + /// In case no [body] was supplied, the current `body` will be used and is |
| + /// therefore no longer available to users. This is a transfer of the owner |
| + /// 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.
|
| + ResponseImpl replace( |
| + {int statusCode, Headers headers, Stream<List<int>> body}) { |
| + if (statusCode == null) statusCode = this.statusCode; |
| + if (headers == null) headers = this.headers; |
| + if (body == null) body = read(); |
| + |
| + return new ResponseImpl(statusCode, headers: headers, body: body); |
| + } |
| +} |