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..1cf469660d2004efdc1f8c1d754554e7649d07ee 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,159 @@ 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.toList(); |
+ } |
+ }); |
+ } |
+ |
+ 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 immutable implementation of [Request]. |
+/// |
+/// The request can be modified with the copy-on-write `replace` method. |
+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); |
+ |
+ /// Makes a copy of this [RequestImpl] by overriding `method`, `url`, |
+ /// `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. |
+ 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 immutable implementation of [Response]. |
+/// |
+/// The response can be modified with the copy-on-write `replace` method. |
+class ResponseImpl extends _Message implements Response { |
+ final int statusCode; |
+ |
+ ResponseImpl(this.statusCode, {Headers headers, Stream<List<int>> body}) |
+ : super(headers, body); |
+ |
+ /// Returns a copy of this [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. |
+ 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); |
+ } |
+} |