Copying sources from dart:io
diff --git a/.analysis_options b/.analysis_options
new file mode 100644
index 0000000..a10d4c5
--- /dev/null
+++ b/.analysis_options
@@ -0,0 +1,2 @@
+analyzer:
+  strong-mode: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7dbf035
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
\ No newline at end of file
diff --git a/.status b/.status
new file mode 100644
index 0000000..a5f8562
--- /dev/null
+++ b/.status
@@ -0,0 +1,25 @@
+# Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Skip non-test files ending with "_test".
+packages/*: Skip
+*/packages/*: Skip
+*/*/packages/*: Skip
+*/*/*/packages/*: Skip
+*/*/*/*packages/*: Skip
+*/*/*/*/*packages/*: Skip
+
+# Only run tests from the build directory, since we don't care about the
+# difference between transformed an untransformed code.
+test/*: Skip
+
+[ $browser ]
+build/test/io/*: Fail, OK # Uses dart:io.
+
+[ $runtime == vm ]
+build/test/html/*: Skip # Uses dart:html.
+
+[ $runtime == drt ]
+build/test/html/client_test: Skip # Issue 18566
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..356667a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.10.0
+
+Initial check-in.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..861945b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# http_io
+
+The HTTP APIs in `dart:io` are pure Dart code. To enable faster development and
+bug fixes, these APIS are moving out of `dart:io` into this package.
diff --git a/lib/http_io.dart b/lib/http_io.dart
new file mode 100644
index 0000000..cca0267
--- /dev/null
+++ b/lib/http_io.dart
@@ -0,0 +1,2034 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http_io;
+
+import 'dart:async';
+import 'dart:collection'
+    show
+        HashMap,
+        HashSet,
+        Queue,
+        ListQueue,
+        LinkedList,
+        LinkedListEntry,
+        UnmodifiableMapView;
+import 'dart:convert';
+import 'dart:developer' hide log;
+import 'dart:math';
+import 'dart:io';
+import 'dart:typed_data';
+
+part 'src/crypto.dart';
+part 'src/http_date.dart';
+part 'src/http_headers.dart';
+part 'src/http_impl.dart';
+part 'src/http_parser.dart';
+part 'src/http_session.dart';
+part 'src/overrides.dart';
+
+/**
+ * HTTP status codes.
+ */
+abstract class HttpStatus {
+  static const int CONTINUE = 100;
+  static const int SWITCHING_PROTOCOLS = 101;
+  static const int OK = 200;
+  static const int CREATED = 201;
+  static const int ACCEPTED = 202;
+  static const int NON_AUTHORITATIVE_INFORMATION = 203;
+  static const int NO_CONTENT = 204;
+  static const int RESET_CONTENT = 205;
+  static const int PARTIAL_CONTENT = 206;
+  static const int MULTIPLE_CHOICES = 300;
+  static const int MOVED_PERMANENTLY = 301;
+  static const int FOUND = 302;
+  static const int MOVED_TEMPORARILY = 302; // Common alias for FOUND.
+  static const int SEE_OTHER = 303;
+  static const int NOT_MODIFIED = 304;
+  static const int USE_PROXY = 305;
+  static const int TEMPORARY_REDIRECT = 307;
+  static const int BAD_REQUEST = 400;
+  static const int UNAUTHORIZED = 401;
+  static const int PAYMENT_REQUIRED = 402;
+  static const int FORBIDDEN = 403;
+  static const int NOT_FOUND = 404;
+  static const int METHOD_NOT_ALLOWED = 405;
+  static const int NOT_ACCEPTABLE = 406;
+  static const int PROXY_AUTHENTICATION_REQUIRED = 407;
+  static const int REQUEST_TIMEOUT = 408;
+  static const int CONFLICT = 409;
+  static const int GONE = 410;
+  static const int LENGTH_REQUIRED = 411;
+  static const int PRECONDITION_FAILED = 412;
+  static const int REQUEST_ENTITY_TOO_LARGE = 413;
+  static const int REQUEST_URI_TOO_LONG = 414;
+  static const int UNSUPPORTED_MEDIA_TYPE = 415;
+  static const int REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+  static const int EXPECTATION_FAILED = 417;
+  static const int UPGRADE_REQUIRED = 426;
+  static const int INTERNAL_SERVER_ERROR = 500;
+  static const int NOT_IMPLEMENTED = 501;
+  static const int BAD_GATEWAY = 502;
+  static const int SERVICE_UNAVAILABLE = 503;
+  static const int GATEWAY_TIMEOUT = 504;
+  static const int HTTP_VERSION_NOT_SUPPORTED = 505;
+  // Client generated status code.
+  static const int NETWORK_CONNECT_TIMEOUT_ERROR = 599;
+}
+
+/**
+ * A server that delivers content, such as web pages, using the HTTP protocol.
+ *
+ * The HttpServer is a [Stream] that provides [HttpRequest] objects. Each
+ * HttpRequest has an associated [HttpResponse] object.
+ * The server responds to a request by writing to that HttpResponse object.
+ * The following example shows how to bind an HttpServer to an IPv6
+ * [InternetAddress] on port 80 (the standard port for HTTP servers)
+ * and how to listen for requests.
+ * Port 80 is the default HTTP port. However, on most systems accessing
+ * this requires super-user privileges. For local testing consider
+ * using a non-reserved port (1024 and above).
+ *
+ *     import 'dart:io';
+ *
+ *     main() {
+ *       HttpServer
+ *           .bind(InternetAddress.ANY_IP_V6, 80)
+ *           .then((server) {
+ *             server.listen((HttpRequest request) {
+ *               request.response.write('Hello, world!');
+ *               request.response.close();
+ *             });
+ *           });
+ *     }
+ *
+ * Incomplete requests, in which all or part of the header is missing, are
+ * ignored, and no exceptions or HttpRequest objects are generated for them.
+ * Likewise, when writing to an HttpResponse, any [Socket] exceptions are
+ * ignored and any future writes are ignored.
+ *
+ * The HttpRequest exposes the request headers and provides the request body,
+ * if it exists, as a Stream of data. If the body is unread, it is drained
+ * when the server writes to the HttpResponse or closes it.
+ *
+ * ## Bind with a secure HTTPS connection
+ *
+ * Use [bindSecure] to create an HTTPS server.
+ *
+ * The server presents a certificate to the client. The certificate
+ * chain and the private key are set in the [SecurityContext]
+ * object that is passed to [bindSecure].
+ *
+ *     import 'dart:io';
+ *     import "dart:isolate";
+ *
+ *     main() {
+ *       SecurityContext context = new SecurityContext();
+ *       var chain =
+ *           Platform.script.resolve('certificates/server_chain.pem')
+ *           .toFilePath();
+ *       var key =
+ *           Platform.script.resolve('certificates/server_key.pem')
+ *           .toFilePath();
+ *       context.useCertificateChain(chain);
+ *       context.usePrivateKey(key, password: 'dartdart');
+ *
+ *       HttpServer
+ *           .bindSecure(InternetAddress.ANY_IP_V6,
+ *                       443,
+ *                       context)
+ *           .then((server) {
+ *             server.listen((HttpRequest request) {
+ *               request.response.write('Hello, world!');
+ *               request.response.close();
+ *             });
+ *           });
+ *     }
+ *
+ *  The certificates and keys are PEM files, which can be created and
+ *  managed with the tools in OpenSSL.
+ *
+ * ## Connect to a server socket
+ *
+ * You can use the [listenOn] constructor to attach an HTTP server to
+ * a [ServerSocket].
+ *
+ *     import 'dart:io';
+ *
+ *     main() {
+ *       ServerSocket.bind(InternetAddress.ANY_IP_V6, 80)
+ *         .then((serverSocket) {
+ *           HttpServer httpserver = new HttpServer.listenOn(serverSocket);
+ *           serverSocket.listen((Socket socket) {
+ *             socket.write('Hello, client.');
+ *           });
+ *         });
+ *     }
+ *
+ * ## Other resources
+ *
+ * * HttpServer is a Stream. Refer to the [Stream] class for information
+ * about the streaming qualities of an HttpServer.
+ * Pausing the subscription of the stream, pauses at the OS level.
+ *
+ * * The [shelf](https://pub.dartlang.org/packages/shelf)
+ * package on pub.dartlang.org contains a set of high-level classes that,
+ * together with this class, makes it easy to provide content through HTTP
+ * servers.
+ */
+abstract class HttpServer implements Stream<HttpRequest> {
+  /**
+   * Gets and sets the default value of the `Server` header for all responses
+   * generated by this [HttpServer].
+   *
+   * If [serverHeader] is `null`, no `Server` header will be added to each
+   * response.
+   *
+   * The default value is `null`.
+   */
+  String serverHeader;
+
+  /**
+   * Default set of headers added to all response objects.
+   *
+   * By default the following headers are in this set:
+   *
+   *     Content-Type: text/plain; charset=utf-8
+   *     X-Frame-Options: SAMEORIGIN
+   *     X-Content-Type-Options: nosniff
+   *     X-XSS-Protection: 1; mode=block
+   *
+   * If the `Server` header is added here and the `serverHeader` is set as
+   * well then the value of `serverHeader` takes precedence.
+   */
+  HttpHeaders get defaultResponseHeaders;
+
+  /**
+   * Whether the [HttpServer] should compress the content, if possible.
+   *
+   * The content can only be compressed when the response is using
+   * chunked Transfer-Encoding and the incoming request has `gzip`
+   * as an accepted encoding in the Accept-Encoding header.
+   *
+   * The default value is `false` (compression disabled).
+   * To enable, set `autoCompress` to `true`.
+   */
+  bool autoCompress;
+
+  /**
+   * Gets or sets the timeout used for idle keep-alive connections. If no
+   * further request is seen within [idleTimeout] after the previous request was
+   * completed, the connection is dropped.
+   *
+   * Default is 120 seconds.
+   *
+   * Note that it may take up to `2 * idleTimeout` before a idle connection is
+   * aborted.
+   *
+   * To disable, set [idleTimeout] to `null`.
+   */
+  Duration idleTimeout;
+
+  /**
+   * Starts listening for HTTP requests on the specified [address] and
+   * [port].
+   *
+   * The [address] can either be a [String] or an
+   * [InternetAddress]. If [address] is a [String], [bind] will
+   * perform a [InternetAddress.lookup] and use the first value in the
+   * list. To listen on the loopback adapter, which will allow only
+   * incoming connections from the local host, use the value
+   * [InternetAddress.LOOPBACK_IP_V4] or
+   * [InternetAddress.LOOPBACK_IP_V6]. To allow for incoming
+   * connection from the network use either one of the values
+   * [InternetAddress.ANY_IP_V4] or [InternetAddress.ANY_IP_V6] to
+   * bind to all interfaces or the IP address of a specific interface.
+   *
+   * If an IP version 6 (IPv6) address is used, both IP version 6
+   * (IPv6) and version 4 (IPv4) connections will be accepted. To
+   * restrict this to version 6 (IPv6) only, use [v6Only] to set
+   * version 6 only. However, if the address is
+   * [InternetAddress.LOOPBACK_IP_V6], only IP version 6 (IPv6) connections
+   * will be accepted.
+   *
+   * If [port] has the value [:0:] an ephemeral port will be chosen by
+   * the system. The actual port used can be retrieved using the
+   * [port] getter.
+   *
+   * The optional argument [backlog] can be used to specify the listen
+   * backlog for the underlying OS listen setup. If [backlog] has the
+   * value of [:0:] (the default) a reasonable value will be chosen by
+   * the system.
+   *
+   * The optional argument [shared] specifies whether additional HttpServer
+   * objects can bind to the same combination of `address`, `port` and `v6Only`.
+   * If `shared` is `true` and more `HttpServer`s from this isolate or other
+   * isolates are bound to the port, then the incoming connections will be
+   * distributed among all the bound `HttpServer`s. Connections can be
+   * distributed over multiple isolates this way.
+   */
+  static Future<HttpServer> bind(address, int port,
+          {int backlog: 0, bool v6Only: false, bool shared: false}) =>
+      _HttpServer.bind(address, port, backlog, v6Only, shared);
+
+  /**
+   * The [address] can either be a [String] or an
+   * [InternetAddress]. If [address] is a [String], [bind] will
+   * perform a [InternetAddress.lookup] and use the first value in the
+   * list. To listen on the loopback adapter, which will allow only
+   * incoming connections from the local host, use the value
+   * [InternetAddress.LOOPBACK_IP_V4] or
+   * [InternetAddress.LOOPBACK_IP_V6]. To allow for incoming
+   * connection from the network use either one of the values
+   * [InternetAddress.ANY_IP_V4] or [InternetAddress.ANY_IP_V6] to
+   * bind to all interfaces or the IP address of a specific interface.
+   *
+   * If an IP version 6 (IPv6) address is used, both IP version 6
+   * (IPv6) and version 4 (IPv4) connections will be accepted. To
+   * restrict this to version 6 (IPv6) only, use [v6Only] to set
+   * version 6 only.
+   *
+   * If [port] has the value [:0:] an ephemeral port will be chosen by
+   * the system. The actual port used can be retrieved using the
+   * [port] getter.
+   *
+   * The optional argument [backlog] can be used to specify the listen
+   * backlog for the underlying OS listen setup. If [backlog] has the
+   * value of [:0:] (the default) a reasonable value will be chosen by
+   * the system.
+   *
+   * If [requestClientCertificate] is true, the server will
+   * request clients to authenticate with a client certificate.
+   * The server will advertise the names of trusted issuers of client
+   * certificates, getting them from a [SecurityContext], where they have been
+   * set using [SecurityContext.setClientAuthorities].
+   *
+   * The optional argument [shared] specifies whether additional HttpServer
+   * objects can bind to the same combination of `address`, `port` and `v6Only`.
+   * If `shared` is `true` and more `HttpServer`s from this isolate or other
+   * isolates are bound to the port, then the incoming connections will be
+   * distributed among all the bound `HttpServer`s. Connections can be
+   * distributed over multiple isolates this way.
+   */
+
+  static Future<HttpServer> bindSecure(
+          address, int port, SecurityContext context,
+          {int backlog: 0,
+          bool v6Only: false,
+          bool requestClientCertificate: false,
+          bool shared: false}) =>
+      _HttpServer.bindSecure(address, port, context, backlog, v6Only,
+          requestClientCertificate, shared);
+
+  /**
+   * Attaches the HTTP server to an existing [ServerSocket]. When the
+   * [HttpServer] is closed, the [HttpServer] will just detach itself,
+   * closing current connections but not closing [serverSocket].
+   */
+  factory HttpServer.listenOn(ServerSocket serverSocket) =>
+      new _HttpServer.listenOn(serverSocket);
+
+  /**
+   * Permanently stops this [HttpServer] from listening for new
+   * connections.  This closes the [Stream] of [HttpRequest]s with a
+   * done event. The returned future completes when the server is
+   * stopped. For a server started using [bind] or [bindSecure] this
+   * means that the port listened on no longer in use.
+   *
+   * If [force] is `true`, active connections will be closed immediately.
+   */
+  Future close({bool force: false});
+
+  /**
+   * Returns the port that the server is listening on. This can be
+   * used to get the actual port used when a value of 0 for [:port:] is
+   * specified in the [bind] or [bindSecure] call.
+   */
+  int get port;
+
+  /**
+   * Returns the address that the server is listening on. This can be
+   * used to get the actual address used, when the address is fetched by
+   * a lookup from a hostname.
+   */
+  InternetAddress get address;
+
+  /**
+   * Sets the timeout, in seconds, for sessions of this [HttpServer].
+   * The default timeout is 20 minutes.
+   */
+  set sessionTimeout(int timeout);
+
+  /**
+   * Returns an [HttpConnectionsInfo] object summarizing the number of
+   * current connections handled by the server.
+   */
+  HttpConnectionsInfo connectionsInfo();
+}
+
+/**
+ * Summary statistics about an [HttpServer]s current socket connections.
+ */
+class HttpConnectionsInfo {
+  /**
+   * Total number of socket connections.
+   */
+  int total = 0;
+
+  /**
+   * Number of active connections where actual request/response
+   * processing is active.
+   */
+  int active = 0;
+
+  /**
+   * Number of idle connections held by clients as persistent connections.
+   */
+  int idle = 0;
+
+  /**
+   * Number of connections which are preparing to close. Note: These
+   * connections are also part of the [:active:] count as they might
+   * still be sending data to the client before finally closing.
+   */
+  int closing = 0;
+}
+
+/**
+ * Headers for HTTP requests and responses.
+ *
+ * In some situations, headers are immutable:
+ *
+ * * HttpRequest and HttpClientResponse always have immutable headers.
+ *
+ * * HttpResponse and HttpClientRequest have immutable headers
+ *   from the moment the body is written to.
+ *
+ * In these situations, the mutating methods throw exceptions.
+ *
+ * For all operations on HTTP headers the header name is
+ * case-insensitive.
+ *
+ * To set the value of a header use the `set()` method:
+ *
+ *     request.headers.set(HttpHeaders.CACHE_CONTROL,
+ *                         'max-age=3600, must-revalidate');
+ *
+ * To retrieve the value of a header use the `value()` method:
+ *
+ *     print(request.headers.value(HttpHeaders.USER_AGENT));
+ *
+ * An HttpHeaders object holds a list of values for each name
+ * as the standard allows. In most cases a name holds only a single value,
+ * The most common mode of operation is to use `set()` for setting a value,
+ * and `value()` for retrieving a value.
+ */
+abstract class HttpHeaders {
+  static const ACCEPT = "accept";
+  static const ACCEPT_CHARSET = "accept-charset";
+  static const ACCEPT_ENCODING = "accept-encoding";
+  static const ACCEPT_LANGUAGE = "accept-language";
+  static const ACCEPT_RANGES = "accept-ranges";
+  static const AGE = "age";
+  static const ALLOW = "allow";
+  static const AUTHORIZATION = "authorization";
+  static const CACHE_CONTROL = "cache-control";
+  static const CONNECTION = "connection";
+  static const CONTENT_ENCODING = "content-encoding";
+  static const CONTENT_LANGUAGE = "content-language";
+  static const CONTENT_LENGTH = "content-length";
+  static const CONTENT_LOCATION = "content-location";
+  static const CONTENT_MD5 = "content-md5";
+  static const CONTENT_RANGE = "content-range";
+  static const CONTENT_TYPE = "content-type";
+  static const DATE = "date";
+  static const ETAG = "etag";
+  static const EXPECT = "expect";
+  static const EXPIRES = "expires";
+  static const FROM = "from";
+  static const HOST = "host";
+  static const IF_MATCH = "if-match";
+  static const IF_MODIFIED_SINCE = "if-modified-since";
+  static const IF_NONE_MATCH = "if-none-match";
+  static const IF_RANGE = "if-range";
+  static const IF_UNMODIFIED_SINCE = "if-unmodified-since";
+  static const LAST_MODIFIED = "last-modified";
+  static const LOCATION = "location";
+  static const MAX_FORWARDS = "max-forwards";
+  static const PRAGMA = "pragma";
+  static const PROXY_AUTHENTICATE = "proxy-authenticate";
+  static const PROXY_AUTHORIZATION = "proxy-authorization";
+  static const RANGE = "range";
+  static const REFERER = "referer";
+  static const RETRY_AFTER = "retry-after";
+  static const SERVER = "server";
+  static const TE = "te";
+  static const TRAILER = "trailer";
+  static const TRANSFER_ENCODING = "transfer-encoding";
+  static const UPGRADE = "upgrade";
+  static const USER_AGENT = "user-agent";
+  static const VARY = "vary";
+  static const VIA = "via";
+  static const WARNING = "warning";
+  static const WWW_AUTHENTICATE = "www-authenticate";
+
+  // Cookie headers from RFC 6265.
+  static const COOKIE = "cookie";
+  static const SET_COOKIE = "set-cookie";
+
+  static const GENERAL_HEADERS = const [
+    CACHE_CONTROL,
+    CONNECTION,
+    DATE,
+    PRAGMA,
+    TRAILER,
+    TRANSFER_ENCODING,
+    UPGRADE,
+    VIA,
+    WARNING
+  ];
+
+  static const ENTITY_HEADERS = const [
+    ALLOW,
+    CONTENT_ENCODING,
+    CONTENT_LANGUAGE,
+    CONTENT_LENGTH,
+    CONTENT_LOCATION,
+    CONTENT_MD5,
+    CONTENT_RANGE,
+    CONTENT_TYPE,
+    EXPIRES,
+    LAST_MODIFIED
+  ];
+
+  static const RESPONSE_HEADERS = const [
+    ACCEPT_RANGES,
+    AGE,
+    ETAG,
+    LOCATION,
+    PROXY_AUTHENTICATE,
+    RETRY_AFTER,
+    SERVER,
+    VARY,
+    WWW_AUTHENTICATE
+  ];
+
+  static const REQUEST_HEADERS = const [
+    ACCEPT,
+    ACCEPT_CHARSET,
+    ACCEPT_ENCODING,
+    ACCEPT_LANGUAGE,
+    AUTHORIZATION,
+    EXPECT,
+    FROM,
+    HOST,
+    IF_MATCH,
+    IF_MODIFIED_SINCE,
+    IF_NONE_MATCH,
+    IF_RANGE,
+    IF_UNMODIFIED_SINCE,
+    MAX_FORWARDS,
+    PROXY_AUTHORIZATION,
+    RANGE,
+    REFERER,
+    TE,
+    USER_AGENT
+  ];
+
+  /**
+   * Gets and sets the date. The value of this property will
+   * reflect the 'date' header.
+   */
+  DateTime date;
+
+  /**
+   * Gets and sets the expiry date. The value of this property will
+   * reflect the 'expires' header.
+   */
+  DateTime expires;
+
+  /**
+   * Gets and sets the "if-modified-since" date. The value of this property will
+   * reflect the "if-modified-since" header.
+   */
+  DateTime ifModifiedSince;
+
+  /**
+   * Gets and sets the host part of the 'host' header for the
+   * connection.
+   */
+  String host;
+
+  /**
+   * Gets and sets the port part of the 'host' header for the
+   * connection.
+   */
+  int port;
+
+  /**
+   * Gets and sets the content type. Note that the content type in the
+   * header will only be updated if this field is set
+   * directly. Mutating the returned current value will have no
+   * effect.
+   */
+  ContentType contentType;
+
+  /**
+   * Gets and sets the content length header value.
+   */
+  int contentLength;
+
+  /**
+   * Gets and sets the persistent connection header value.
+   */
+  bool persistentConnection;
+
+  /**
+   * Gets and sets the chunked transfer encoding header value.
+   */
+  bool chunkedTransferEncoding;
+
+  /**
+   * Returns the list of values for the header named [name]. If there
+   * is no header with the provided name, [:null:] will be returned.
+   */
+  List<String> operator [](String name);
+
+  /**
+   * Convenience method for the value for a single valued header. If
+   * there is no header with the provided name, [:null:] will be
+   * returned. If the header has more than one value an exception is
+   * thrown.
+   */
+  String value(String name);
+
+  /**
+   * Adds a header value. The header named [name] will have the value
+   * [value] added to its list of values. Some headers are single
+   * valued, and for these adding a value will replace the previous
+   * value. If the value is of type DateTime a HTTP date format will be
+   * applied. If the value is a [:List:] each element of the list will
+   * be added separately. For all other types the default [:toString:]
+   * method will be used.
+   */
+  void add(String name, Object value);
+
+  /**
+   * Sets a header. The header named [name] will have all its values
+   * cleared before the value [value] is added as its value.
+   */
+  void set(String name, Object value);
+
+  /**
+   * Removes a specific value for a header name. Some headers have
+   * system supplied values and for these the system supplied values
+   * will still be added to the collection of values for the header.
+   */
+  void remove(String name, Object value);
+
+  /**
+   * Removes all values for the specified header name. Some headers
+   * have system supplied values and for these the system supplied
+   * values will still be added to the collection of values for the
+   * header.
+   */
+  void removeAll(String name);
+
+  /**
+   * Enumerates the headers, applying the function [f] to each
+   * header. The header name passed in [:name:] will be all lower
+   * case.
+   */
+  void forEach(void f(String name, List<String> values));
+
+  /**
+   * Disables folding for the header named [name] when sending the HTTP
+   * header. By default, multiple header values are folded into a
+   * single header line by separating the values with commas. The
+   * 'set-cookie' header has folding disabled by default.
+   */
+  void noFolding(String name);
+
+  /**
+   * Remove all headers. Some headers have system supplied values and
+   * for these the system supplied values will still be added to the
+   * collection of values for the header.
+   */
+  void clear();
+}
+
+/**
+ * Representation of a header value in the form:
+ *
+ *   [:value; parameter1=value1; parameter2=value2:]
+ *
+ * [HeaderValue] can be used to conveniently build and parse header
+ * values on this form.
+ *
+ * To build an [:accepts:] header with the value
+ *
+ *     text/plain; q=0.3, text/html
+ *
+ * use code like this:
+ *
+ *     HttpClientRequest request = ...;
+ *     var v = new HeaderValue("text/plain", {"q": "0.3"});
+ *     request.headers.add(HttpHeaders.ACCEPT, v);
+ *     request.headers.add(HttpHeaders.ACCEPT, "text/html");
+ *
+ * To parse the header values use the [:parse:] static method.
+ *
+ *     HttpRequest request = ...;
+ *     List<String> values = request.headers[HttpHeaders.ACCEPT];
+ *     values.forEach((value) {
+ *       HeaderValue v = HeaderValue.parse(value);
+ *       // Use v.value and v.parameters
+ *     });
+ *
+ * An instance of [HeaderValue] is immutable.
+ */
+abstract class HeaderValue {
+  /**
+   * Creates a new header value object setting the value and parameters.
+   */
+  factory HeaderValue([String value = "", Map<String, String> parameters]) {
+    return new _HeaderValue(value, parameters);
+  }
+
+  /**
+   * Creates a new header value object from parsing a header value
+   * string with both value and optional parameters.
+   */
+  static HeaderValue parse(String value,
+      {String parameterSeparator: ";",
+      String valueSeparator: null,
+      bool preserveBackslash: false}) {
+    return _HeaderValue.parse(value,
+        parameterSeparator: parameterSeparator,
+        valueSeparator: valueSeparator,
+        preserveBackslash: preserveBackslash);
+  }
+
+  /**
+   * Gets the header value.
+   */
+  String get value;
+
+  /**
+   * Gets the map of parameters.
+   *
+   * This map cannot be modified. Invoking any operation which would
+   * modify the map will throw [UnsupportedError].
+   */
+  Map<String, String> get parameters;
+
+  /**
+   * Returns the formatted string representation in the form:
+   *
+   *     value; parameter1=value1; parameter2=value2
+   */
+  String toString();
+}
+
+abstract class HttpSession implements Map {
+  /**
+   * Gets the id for the current session.
+   */
+  String get id;
+
+  /**
+   * Destroys the session. This will terminate the session and any further
+   * connections with this id will be given a new id and session.
+   */
+  void destroy();
+
+  /**
+   * Sets a callback that will be called when the session is timed out.
+   */
+  void set onTimeout(void callback());
+
+  /**
+   * Is true if the session has not been sent to the client yet.
+   */
+  bool get isNew;
+}
+
+/**
+ * Representation of a content type. An instance of [ContentType] is
+ * immutable.
+ */
+abstract class ContentType implements HeaderValue {
+  /**
+   * Content type for plain text using UTF-8 encoding.
+   *
+   *     text/plain; charset=utf-8
+   */
+  static final TEXT = new ContentType("text", "plain", charset: "utf-8");
+
+  /**
+   *  Content type for HTML using UTF-8 encoding.
+   *
+   *     text/html; charset=utf-8
+   */
+  static final HTML = new ContentType("text", "html", charset: "utf-8");
+
+  /**
+   *  Content type for JSON using UTF-8 encoding.
+   *
+   *     application/json; charset=utf-8
+   */
+  static final JSON = new ContentType("application", "json", charset: "utf-8");
+
+  /**
+   *  Content type for binary data.
+   *
+   *     application/octet-stream
+   */
+  static final BINARY = new ContentType("application", "octet-stream");
+
+  /**
+   * Creates a new content type object setting the primary type and
+   * sub type. The charset and additional parameters can also be set
+   * using [charset] and [parameters]. If charset is passed and
+   * [parameters] contains charset as well the passed [charset] will
+   * override the value in parameters. Keys passed in parameters will be
+   * converted to lower case. The `charset` entry, whether passed as `charset`
+   * or in `parameters`, will have its value converted to lower-case.
+   */
+  factory ContentType(String primaryType, String subType,
+      {String charset, Map<String, String> parameters}) {
+    return new _ContentType(primaryType, subType, charset, parameters);
+  }
+
+  /**
+   * Creates a new content type object from parsing a Content-Type
+   * header value. As primary type, sub type and parameter names and
+   * values are not case sensitive all these values will be converted
+   * to lower case. Parsing this string
+   *
+   *     text/html; charset=utf-8
+   *
+   * will create a content type object with primary type [:text:], sub
+   * type [:html:] and parameter [:charset:] with value [:utf-8:].
+   */
+  static ContentType parse(String value) {
+    return _ContentType.parse(value);
+  }
+
+  /**
+   * Gets the mime-type, without any parameters.
+   */
+  String get mimeType;
+
+  /**
+   * Gets the primary type.
+   */
+  String get primaryType;
+
+  /**
+   * Gets the sub type.
+   */
+  String get subType;
+
+  /**
+   * Gets the character set.
+   */
+  String get charset;
+}
+
+/**
+ * Representation of a cookie. For cookies received by the server as
+ * Cookie header values only [:name:] and [:value:] fields will be
+ * set. When building a cookie for the 'set-cookie' header in the server
+ * and when receiving cookies in the client as 'set-cookie' headers all
+ * fields can be used.
+ */
+abstract class Cookie {
+  /**
+   * Gets and sets the name.
+   */
+  String name;
+
+  /**
+   * Gets and sets the value.
+   */
+  String value;
+
+  /**
+   * Gets and sets the expiry date.
+   */
+  DateTime expires;
+
+  /**
+   * Gets and sets the max age. A value of [:0:] means delete cookie
+   * now.
+   */
+  int maxAge;
+
+  /**
+   * Gets and sets the domain.
+   */
+  String domain;
+
+  /**
+   * Gets and sets the path.
+   */
+  String path;
+
+  /**
+   * Gets and sets whether this cookie is secure.
+   */
+  bool secure;
+
+  /**
+   * Gets and sets whether this cookie is HTTP only.
+   */
+  bool httpOnly;
+
+  /**
+   * Creates a new cookie optionally setting the name and value.
+   *
+   * By default the value of `httpOnly` will be set to `true`.
+   */
+  factory Cookie([String name, String value]) => new _Cookie(name, value);
+
+  /**
+   * Creates a new cookie by parsing a header value from a 'set-cookie'
+   * header.
+   */
+  factory Cookie.fromSetCookieValue(String value) {
+    return new _Cookie.fromSetCookieValue(value);
+  }
+
+  /**
+   * Returns the formatted string representation of the cookie. The
+   * string representation can be used for for setting the Cookie or
+   * 'set-cookie' headers
+   */
+  String toString();
+}
+
+/**
+ * A server-side object
+ * that contains the content of and information about an HTTP request.
+ *
+ * __Note__: Check out the
+ * [http_server](http://pub.dartlang.org/packages/http_server)
+ * package, which makes working with the low-level
+ * dart:io HTTP server subsystem easier.
+ *
+ * `HttpRequest` objects are generated by an [HttpServer],
+ * which listens for HTTP requests on a specific host and port.
+ * For each request received, the HttpServer, which is a [Stream],
+ * generates an `HttpRequest` object and adds it to the stream.
+ *
+ * An `HttpRequest` object delivers the body content of the request
+ * as a stream of byte lists.
+ * The object also contains information about the request,
+ * such as the method, URI, and headers.
+ *
+ * In the following code, an HttpServer listens
+ * for HTTP requests. When the server receives a request,
+ * it uses the HttpRequest object's `method` property to dispatch requests.
+ *
+ *     final HOST = InternetAddress.LOOPBACK_IP_V4;
+ *     final PORT = 80;
+ *
+ *     HttpServer.bind(HOST, PORT).then((_server) {
+ *       _server.listen((HttpRequest request) {
+ *         switch (request.method) {
+ *           case 'GET':
+ *             handleGetRequest(request);
+ *             break;
+ *           case 'POST':
+ *             ...
+ *         }
+ *       },
+ *       onError: handleError);    // listen() failed.
+ *     }).catchError(handleError);
+ *
+ * An HttpRequest object provides access to the associated [HttpResponse]
+ * object through the response property.
+ * The server writes its response to the body of the HttpResponse object.
+ * For example, here's a function that responds to a request:
+ *
+ *     void handleGetRequest(HttpRequest req) {
+ *       HttpResponse res = req.response;
+ *       res.write('Received request ${req.method}: ${req.uri.path}');
+ *       res.close();
+ *     }
+ */
+abstract class HttpRequest implements Stream<List<int>> {
+  /**
+   * The content length of the request body.
+   *
+   * If the size of the request body is not known in advance,
+   * this value is -1.
+   */
+  int get contentLength;
+
+  /**
+   * The method, such as 'GET' or 'POST', for the request.
+   */
+  String get method;
+
+  /**
+   * The URI for the request.
+   *
+   * This provides access to the
+   * path and query string for the request.
+   */
+  Uri get uri;
+
+  /**
+   * The requested URI for the request.
+   *
+   * The returned URI is reconstructed by using http-header fields, to access
+   * otherwise lost information, e.g. host and scheme.
+   *
+   * To reconstruct the scheme, first 'X-Forwarded-Proto' is checked, and then
+   * falling back to server type.
+   *
+   * To reconstruct the host, first 'X-Forwarded-Host' is checked, then 'Host'
+   * and finally calling back to server.
+   */
+  Uri get requestedUri;
+
+  /**
+   * The request headers.
+   *
+   * The returned [HttpHeaders] are immutable.
+   */
+  HttpHeaders get headers;
+
+  /**
+   * The cookies in the request, from the Cookie headers.
+   */
+  List<Cookie> get cookies;
+
+  /**
+   * The persistent connection state signaled by the client.
+   */
+  bool get persistentConnection;
+
+  /**
+   * The client certificate of the client making the request.
+   *
+   * This value is null if the connection is not a secure TLS or SSL connection,
+   * or if the server does not request a client certificate, or if the client
+   * does not provide one.
+   */
+  X509Certificate get certificate;
+
+  /**
+   * The session for the given request.
+   *
+   * If the session is
+   * being initialized by this call, [:isNew:] is true for the returned
+   * session.
+   * See [HttpServer.sessionTimeout] on how to change default timeout.
+   */
+  HttpSession get session;
+
+  /**
+   * The HTTP protocol version used in the request,
+   * either "1.0" or "1.1".
+   */
+  String get protocolVersion;
+
+  /**
+   * Information about the client connection.
+   *
+   * Returns [:null:] if the socket is not available.
+   */
+  HttpConnectionInfo get connectionInfo;
+
+  /**
+   * The [HttpResponse] object, used for sending back the response to the
+   * client.
+   *
+   * If the [contentLength] of the body isn't 0, and the body isn't being read,
+   * any write calls on the [HttpResponse] automatically drain the request
+   * body.
+   */
+  HttpResponse get response;
+}
+
+/**
+ * An HTTP response, which returns the headers and data
+ * from the server to the client in response to an HTTP request.
+ *
+ * Every HttpRequest object provides access to the associated [HttpResponse]
+ * object through the `response` property.
+ * The server sends its response to the client by writing to the
+ * HttpResponse object.
+ *
+ * ## Writing the response
+ *
+ * This class implements [IOSink].
+ * After the header has been set up, the methods
+ * from IOSink, such as `writeln()`, can be used to write
+ * the body of the HTTP response.
+ * Use the `close()` method to close the response and send it to the client.
+ *
+ *     server.listen((HttpRequest request) {
+ *       request.response.write('Hello, world!');
+ *       request.response.close();
+ *     });
+ *
+ * When one of the IOSink methods is used for the
+ * first time, the request header is sent. Calling any methods that
+ * change the header after it is sent throws an exception.
+ *
+ * ## Setting the headers
+ *
+ * The HttpResponse object has a number of properties for setting up
+ * the HTTP headers of the response.
+ * When writing string data through the IOSink, the encoding used
+ * is determined from the "charset" parameter of the
+ * "Content-Type" header.
+ *
+ *     HttpResponse response = ...
+ *     response.headers.contentType
+ *         = new ContentType("application", "json", charset: "utf-8");
+ *     response.write(...);  // Strings written will be UTF-8 encoded.
+ *
+ * If no charset is provided the default of ISO-8859-1 (Latin 1) will
+ * be used.
+ *
+ *     HttpResponse response = ...
+ *     response.headers.add(HttpHeaders.CONTENT_TYPE, "text/plain");
+ *     response.write(...);  // Strings written will be ISO-8859-1 encoded.
+ *
+ * An exception is thrown if you use the `write()` method
+ * while an unsupported content-type is set.
+ */
+abstract class HttpResponse implements IOSink {
+  // TODO(ajohnsen): Add documentation of how to pipe a file to the response.
+  /**
+   * Gets and sets the content length of the response. If the size of
+   * the response is not known in advance set the content length to
+   * -1 - which is also the default if not set.
+   */
+  int contentLength;
+
+  /**
+   * Gets and sets the status code. Any integer value is accepted. For
+   * the official HTTP status codes use the fields from
+   * [HttpStatus]. If no status code is explicitly set the default
+   * value [HttpStatus.OK] is used.
+   *
+   * The status code must be set before the body is written
+   * to. Setting the status code after writing to the response body or
+   * closing the response will throw a `StateError`.
+   */
+  int statusCode;
+
+  /**
+   * Gets and sets the reason phrase. If no reason phrase is explicitly
+   * set a default reason phrase is provided.
+   *
+   * The reason phrase must be set before the body is written
+   * to. Setting the reason phrase after writing to the response body
+   * or closing the response will throw a `StateError`.
+   */
+  String reasonPhrase;
+
+  /**
+   * Gets and sets the persistent connection state. The initial value
+   * of this property is the persistent connection state from the
+   * request.
+   */
+  bool persistentConnection;
+
+  /**
+   * Set and get the [deadline] for the response. The deadline is timed from the
+   * time it's set. Setting a new deadline will override any previous deadline.
+   * When a deadline is exceeded, the response will be closed and any further
+   * data ignored.
+   *
+   * To disable a deadline, set the [deadline] to `null`.
+   *
+   * The [deadline] is `null` by default.
+   */
+  Duration deadline;
+
+  /**
+   * Gets or sets if the [HttpResponse] should buffer output.
+   *
+   * Default value is `true`.
+   *
+   * __Note__: Disabling buffering of the output can result in very poor
+   * performance, when writing many small chunks.
+   */
+  bool bufferOutput;
+
+  /**
+   * Returns the response headers.
+   *
+   * The response headers can be modified until the response body is
+   * written to or closed. After that they become immutable.
+   */
+  HttpHeaders get headers;
+
+  /**
+   * Cookies to set in the client (in the 'set-cookie' header).
+   */
+  List<Cookie> get cookies;
+
+  /**
+   * Respond with a redirect to [location].
+   *
+   * The URI in [location] should be absolute, but there are no checks
+   * to enforce that.
+   *
+   * By default the HTTP status code `HttpStatus.MOVED_TEMPORARILY`
+   * (`302`) is used for the redirect, but an alternative one can be
+   * specified using the [status] argument.
+   *
+   * This method will also call `close`, and the returned future is
+   * the future returned by `close`.
+   */
+  Future redirect(Uri location, {int status: HttpStatus.MOVED_TEMPORARILY});
+
+  /**
+   * Detaches the underlying socket from the HTTP server. When the
+   * socket is detached the HTTP server will no longer perform any
+   * operations on it.
+   *
+   * This is normally used when a HTTP upgrade request is received
+   * and the communication should continue with a different protocol.
+   *
+   * If [writeHeaders] is `true`, the status line and [headers] will be written
+   * to the socket before it's detached. If `false`, the socket is detached
+   * immediately, without any data written to the socket. Default is `true`.
+   */
+  Future<Socket> detachSocket({bool writeHeaders: true});
+
+  /**
+   * Gets information about the client connection. Returns [:null:] if the
+   * socket is not available.
+   */
+  HttpConnectionInfo get connectionInfo;
+}
+
+/**
+ * A client that receives content, such as web pages, from
+ * a server using the HTTP protocol.
+ *
+ * HttpClient contains a number of methods to send an [HttpClientRequest]
+ * to an Http server and receive an [HttpClientResponse] back.
+ * For example, you can use the [get], [getUrl], [post], and [postUrl] methods
+ * for GET and POST requests, respectively.
+ *
+ * ## Making a simple GET request: an example
+ *
+ * A `getUrl` request is a two-step process, triggered by two [Future]s.
+ * When the first future completes with a [HttpClientRequest], the underlying
+ * network connection has been established, but no data has been sent.
+ * In the callback function for the first future, the HTTP headers and body
+ * can be set on the request. Either the first write to the request object
+ * or a call to [close] sends the request to the server.
+ *
+ * When the HTTP response is received from the server,
+ * the second future, which is returned by close,
+ * completes with an [HttpClientResponse] object.
+ * This object provides access to the headers and body of the response.
+ * The body is available as a stream implemented by HttpClientResponse.
+ * If a body is present, it must be read. Otherwise, it leads to resource
+ * leaks. Consider using [HttpClientResponse.drain] if the body is unused.
+ *
+ *     HttpClient client = new HttpClient();
+ *     client.getUrl(Uri.parse("http://www.example.com/"))
+ *         .then((HttpClientRequest request) {
+ *           // Optionally set up headers...
+ *           // Optionally write to the request object...
+ *           // Then call close.
+ *           ...
+ *           return request.close();
+ *         })
+ *         .then((HttpClientResponse response) {
+ *           // Process the response.
+ *           ...
+ *         });
+ *
+ * The future for [HttpClientRequest] is created by methods such as
+ * [getUrl] and [open].
+ *
+ * ## HTTPS connections
+ *
+ * An HttpClient can make HTTPS requests, connecting to a server using
+ * the TLS (SSL) secure networking protocol. Calling [getUrl] with an
+ * https: scheme will work automatically, if the server's certificate is
+ * signed by a root CA (certificate authority) on the default list of
+ * well-known trusted CAs, compiled by Mozilla.
+ *
+ * To add a custom trusted certificate authority, or to send a client
+ * certificate to servers that request one, pass a [SecurityContext] object
+ * as the optional `context` argument to the `HttpClient` constructor.
+ * The desired security options can be set on the [SecurityContext] object.
+ *
+ * ## Headers
+ *
+ * All HttpClient requests set the following header by default:
+ *
+ *     Accept-Encoding: gzip
+ *
+ * This allows the HTTP server to use gzip compression for the body if
+ * possible. If this behavior is not desired set the
+ * `Accept-Encoding` header to something else.
+ * To turn off gzip compression of the response, clear this header:
+ *
+ *      request.headers.removeAll(HttpHeaders.ACCEPT_ENCODING)
+ *
+ * ## Closing the HttpClient
+ *
+ * The HttpClient supports persistent connections and caches network
+ * connections to reuse them for multiple requests whenever
+ * possible. This means that network connections can be kept open for
+ * some time after a request has completed. Use HttpClient.close
+ * to force the HttpClient object to shut down and to close the idle
+ * network connections.
+ *
+ * ## Turning proxies on and off
+ *
+ * By default the HttpClient uses the proxy configuration available
+ * from the environment, see [findProxyFromEnvironment]. To turn off
+ * the use of proxies set the [findProxy] property to
+ * [:null:].
+ *
+ *     HttpClient client = new HttpClient();
+ *     client.findProxy = null;
+ */
+abstract class HttpClient {
+  static const int DEFAULT_HTTP_PORT = 80;
+  static const int DEFAULT_HTTPS_PORT = 443;
+
+  /// Gets and sets the idle timeout of non-active persistent (keep-alive)
+  /// connections.
+  ///
+  /// The default value is 15 seconds.
+  Duration idleTimeout;
+
+  /**
+   * Gets and sets the maximum number of live connections, to a single host.
+   *
+   * Increasing this number may lower performance and take up unwanted
+   * system resources.
+   *
+   * To disable, set to `null`.
+   *
+   * Default is `null`.
+   */
+  int maxConnectionsPerHost;
+
+  /**
+   * Gets and sets whether the body of a response will be automatically
+   * uncompressed.
+   *
+   * The body of an HTTP response can be compressed. In most
+   * situations providing the un-compressed body is most
+   * convenient. Therefore the default behavior is to un-compress the
+   * body. However in some situations (e.g. implementing a transparent
+   * proxy) keeping the uncompressed stream is required.
+   *
+   * NOTE: Headers in the response are never modified. This means
+   * that when automatic un-compression is turned on the value of the
+   * header `Content-Length` will reflect the length of the original
+   * compressed body. Likewise the header `Content-Encoding` will also
+   * have the original value indicating compression.
+   *
+   * NOTE: Automatic un-compression is only performed if the
+   * `Content-Encoding` header value is `gzip`.
+   *
+   * This value affects all responses produced by this client after the
+   * value is changed.
+   *
+   * To disable, set to `false`.
+   *
+   * Default is `true`.
+   */
+  bool autoUncompress;
+
+  /// Gets and sets the default value of the `User-Agent` header for all requests
+  /// generated by this [HttpClient].
+  ///
+  /// The default value is `Dart/<version> (dart:io)`.
+  ///
+  /// If the userAgent is set to `null`, no default `User-Agent` header will be
+  /// added to each request.
+  String userAgent;
+
+  factory HttpClient({SecurityContext context}) {
+    HttpOverrides overrides = HttpOverrides.current;
+    if (overrides == null) {
+      return new _HttpClient(context);
+    }
+    return overrides.createHttpClient(context);
+  }
+
+  /**
+   * Opens a HTTP connection.
+   *
+   * The HTTP method to use is specified in [method], the server is
+   * specified using [host] and [port], and the path (including
+   * a possible query) is specified using [path].
+   * The path may also contain a URI fragment, which will be ignored.
+   *
+   * The `Host` header for the request will be set to the value
+   * [host]:[port]. This can be overridden through the
+   * [HttpClientRequest] interface before the request is sent.  NOTE
+   * if [host] is an IP address this will still be set in the `Host`
+   * header.
+   *
+   * For additional information on the sequence of events during an
+   * HTTP transaction, and the objects returned by the futures, see
+   * the overall documentation for the class [HttpClient].
+   */
+  Future<HttpClientRequest> open(
+      String method, String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection.
+   *
+   * The HTTP method is specified in [method] and the URL to use in
+   * [url].
+   *
+   * The `Host` header for the request will be set to the value
+   * [Uri.host]:[Uri.port] from [url]. This can be overridden through the
+   * [HttpClientRequest] interface before the request is sent.  NOTE
+   * if [Uri.host] is an IP address this will still be set in the `Host`
+   * header.
+   *
+   * For additional information on the sequence of events during an
+   * HTTP transaction, and the objects returned by the futures, see
+   * the overall documentation for the class [HttpClient].
+   */
+  Future<HttpClientRequest> openUrl(String method, Uri url);
+
+  /**
+   * Opens a HTTP connection using the GET method.
+   *
+   * The server is specified using [host] and [port], and the path
+   * (including a possible query) is specified using
+   * [path].
+   *
+   * See [open] for details.
+   */
+  Future<HttpClientRequest> get(String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection using the GET method.
+   *
+   * The URL to use is specified in [url].
+   *
+   * See [openUrl] for details.
+   */
+  Future<HttpClientRequest> getUrl(Uri url);
+
+  /**
+   * Opens a HTTP connection using the POST method.
+   *
+   * The server is specified using [host] and [port], and the path
+   * (including a possible query) is specified using
+   * [path].
+   *
+   * See [open] for details.
+   */
+  Future<HttpClientRequest> post(String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection using the POST method.
+   *
+   * The URL to use is specified in [url].
+   *
+   * See [openUrl] for details.
+   */
+  Future<HttpClientRequest> postUrl(Uri url);
+
+  /**
+   * Opens a HTTP connection using the PUT method.
+   *
+   * The server is specified using [host] and [port], and the path
+   * (including a possible query) is specified using [path].
+   *
+   * See [open] for details.
+   */
+  Future<HttpClientRequest> put(String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection using the PUT method.
+   *
+   * The URL to use is specified in [url].
+   *
+   * See [openUrl] for details.
+   */
+  Future<HttpClientRequest> putUrl(Uri url);
+
+  /**
+   * Opens a HTTP connection using the DELETE method.
+   *
+   * The server is specified using [host] and [port], and the path
+   * (including a possible query) is specified using [path].
+   *
+   * See [open] for details.
+   */
+  Future<HttpClientRequest> delete(String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection using the DELETE method.
+   *
+   * The URL to use is specified in [url].
+   *
+   * See [openUrl] for details.
+   */
+  Future<HttpClientRequest> deleteUrl(Uri url);
+
+  /**
+   * Opens a HTTP connection using the PATCH method.
+   *
+   * The server is specified using [host] and [port], and the path
+   * (including a possible query) is specified using [path].
+   *
+   * See [open] for details.
+   */
+  Future<HttpClientRequest> patch(String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection using the PATCH method.
+   *
+   * The URL to use is specified in [url].
+   *
+   * See [openUrl] for details.
+   */
+  Future<HttpClientRequest> patchUrl(Uri url);
+
+  /**
+   * Opens a HTTP connection using the HEAD method.
+   *
+   * The server is specified using [host] and [port], and the path
+   * (including a possible query) is specified using [path].
+   *
+   * See [open] for details.
+   */
+  Future<HttpClientRequest> head(String host, int port, String path);
+
+  /**
+   * Opens a HTTP connection using the HEAD method.
+   *
+   * The URL to use is specified in [url].
+   *
+   * See [openUrl] for details.
+   */
+  Future<HttpClientRequest> headUrl(Uri url);
+
+  /**
+   * Sets the function to be called when a site is requesting
+   * authentication. The URL requested and the security realm from the
+   * server are passed in the arguments [url] and [realm].
+   *
+   * The function returns a [Future] which should complete when the
+   * authentication has been resolved. If credentials cannot be
+   * provided the [Future] should complete with [:false:]. If
+   * credentials are available the function should add these using
+   * [addCredentials] before completing the [Future] with the value
+   * [:true:].
+   *
+   * If the [Future] completes with true the request will be retried
+   * using the updated credentials. Otherwise response processing will
+   * continue normally.
+   */
+  set authenticate(Future<bool> f(Uri url, String scheme, String realm));
+
+  /**
+   * Add credentials to be used for authorizing HTTP requests.
+   */
+  void addCredentials(Uri url, String realm, HttpClientCredentials credentials);
+
+  /**
+   * Sets the function used to resolve the proxy server to be used for
+   * opening a HTTP connection to the specified [url]. If this
+   * function is not set, direct connections will always be used.
+   *
+   * The string returned by [f] must be in the format used by browser
+   * PAC (proxy auto-config) scripts. That is either
+   *
+   *     "DIRECT"
+   *
+   * for using a direct connection or
+   *
+   *     "PROXY host:port"
+   *
+   * for using the proxy server [:host:] on port [:port:].
+   *
+   * A configuration can contain several configuration elements
+   * separated by semicolons, e.g.
+   *
+   *     "PROXY host:port; PROXY host2:port2; DIRECT"
+   *
+   * The static function [findProxyFromEnvironment] on this class can
+   * be used to implement proxy server resolving based on environment
+   * variables.
+   */
+  set findProxy(String f(Uri url));
+
+  /**
+   * Function for resolving the proxy server to be used for a HTTP
+   * connection from the proxy configuration specified through
+   * environment variables.
+   *
+   * The following environment variables are taken into account:
+   *
+   *     http_proxy
+   *     https_proxy
+   *     no_proxy
+   *     HTTP_PROXY
+   *     HTTPS_PROXY
+   *     NO_PROXY
+   *
+   * [:http_proxy:] and [:HTTP_PROXY:] specify the proxy server to use for
+   * http:// urls. Use the format [:hostname:port:]. If no port is used a
+   * default of 1080 will be used. If both are set the lower case one takes
+   * precedence.
+   *
+   * [:https_proxy:] and [:HTTPS_PROXY:] specify the proxy server to use for
+   * https:// urls. Use the format [:hostname:port:]. If no port is used a
+   * default of 1080 will be used. If both are set the lower case one takes
+   * precedence.
+   *
+   * [:no_proxy:] and [:NO_PROXY:] specify a comma separated list of
+   * postfixes of hostnames for which not to use the proxy
+   * server. E.g. the value "localhost,127.0.0.1" will make requests
+   * to both "localhost" and "127.0.0.1" not use a proxy. If both are set
+   * the lower case one takes precedence.
+   *
+   * To activate this way of resolving proxies assign this function to
+   * the [findProxy] property on the [HttpClient].
+   *
+   *     HttpClient client = new HttpClient();
+   *     client.findProxy = HttpClient.findProxyFromEnvironment;
+   *
+   * If you don't want to use the system environment you can use a
+   * different one by wrapping the function.
+   *
+   *     HttpClient client = new HttpClient();
+   *     client.findProxy = (url) {
+   *       return HttpClient.findProxyFromEnvironment(
+   *           url, environment: {"http_proxy": ..., "no_proxy": ...});
+   *     }
+   *
+   * If a proxy requires authentication it is possible to configure
+   * the username and password as well. Use the format
+   * [:username:password@hostname:port:] to include the username and
+   * password. Alternatively the API [addProxyCredentials] can be used
+   * to set credentials for proxies which require authentication.
+   */
+  static String findProxyFromEnvironment(Uri url,
+      {Map<String, String> environment}) {
+    HttpOverrides overrides = HttpOverrides.current;
+    if (overrides == null) {
+      return _HttpClient._findProxyFromEnvironment(url, environment);
+    }
+    return overrides.findProxyFromEnvironment(url, environment);
+  }
+
+  /**
+   * Sets the function to be called when a proxy is requesting
+   * authentication. Information on the proxy in use and the security
+   * realm for the authentication are passed in the arguments [host],
+   * [port] and [realm].
+   *
+   * The function returns a [Future] which should complete when the
+   * authentication has been resolved. If credentials cannot be
+   * provided the [Future] should complete with [:false:]. If
+   * credentials are available the function should add these using
+   * [addProxyCredentials] before completing the [Future] with the value
+   * [:true:].
+   *
+   * If the [Future] completes with [:true:] the request will be retried
+   * using the updated credentials. Otherwise response processing will
+   * continue normally.
+   */
+  set authenticateProxy(
+      Future<bool> f(String host, int port, String scheme, String realm));
+
+  /**
+   * Add credentials to be used for authorizing HTTP proxies.
+   */
+  void addProxyCredentials(
+      String host, int port, String realm, HttpClientCredentials credentials);
+
+  /**
+   * Sets a callback that will decide whether to accept a secure connection
+   * with a server certificate that cannot be authenticated by any of our
+   * trusted root certificates.
+   *
+   * When an secure HTTP request if made, using this HttpClient, and the
+   * server returns a server certificate that cannot be authenticated, the
+   * callback is called asynchronously with the [X509Certificate] object and
+   * the server's hostname and port.  If the value of [badCertificateCallback]
+   * is [:null:], the bad certificate is rejected, as if the callback
+   * returned [:false:]
+   *
+   * If the callback returns true, the secure connection is accepted and the
+   * [:Future<HttpClientRequest>:] that was returned from the call making the
+   * request completes with a valid HttpRequest object. If the callback returns
+   * false, the [:Future<HttpClientRequest>:] completes with an exception.
+   *
+   * If a bad certificate is received on a connection attempt, the library calls
+   * the function that was the value of badCertificateCallback at the time
+   * the request is made, even if the value of badCertificateCallback
+   * has changed since then.
+   */
+  set badCertificateCallback(
+      bool callback(X509Certificate cert, String host, int port));
+
+  /// Shuts down the HTTP client.
+  ///
+  /// If [force] is `false` (the default) the [HttpClient] will be kept alive
+  /// until all active connections are done. If [force] is `true` any active
+  /// connections will be closed to immediately release all resources. These
+  /// closed connections will receive an error event to indicate that the client
+  /// was shut down. In both cases trying to establish a new connection after
+  /// calling [close] will throw an exception.
+  void close({bool force: false});
+}
+
+/**
+ * HTTP request for a client connection.
+ *
+ * To set up a request, set the headers using the headers property
+ * provided in this class and write the data to the body of the request.
+ * HttpClientRequest is an [IOSink]. Use the methods from IOSink,
+ * such as writeCharCode(), to write the body of the HTTP
+ * request. When one of the IOSink methods is used for the first
+ * time, the request header is sent. Calling any methods that
+ * change the header after it is sent throws an exception.
+ *
+ * When writing string data through the [IOSink] the
+ * encoding used is determined from the "charset" parameter of
+ * the "Content-Type" header.
+ *
+ *     HttpClientRequest request = ...
+ *     request.headers.contentType
+ *         = new ContentType("application", "json", charset: "utf-8");
+ *     request.write(...);  // Strings written will be UTF-8 encoded.
+ *
+ * If no charset is provided the default of ISO-8859-1 (Latin 1) is
+ * be used.
+ *
+ *     HttpClientRequest request = ...
+ *     request.headers.add(HttpHeaders.CONTENT_TYPE, "text/plain");
+ *     request.write(...);  // Strings written will be ISO-8859-1 encoded.
+ *
+ * An exception is thrown if you use an unsupported encoding and the
+ * `write()` method being used takes a string parameter.
+ */
+abstract class HttpClientRequest implements IOSink {
+  /**
+   * Gets and sets the requested persistent connection state.
+   *
+   * The default value is [:true:].
+   */
+  bool persistentConnection;
+
+  /**
+   * Set this property to [:true:] if this request should
+   * automatically follow redirects. The default is [:true:].
+   *
+   * Automatic redirect will only happen for "GET" and "HEAD" requests
+   * and only for the status codes [:HttpStatus.MOVED_PERMANENTLY:]
+   * (301), [:HttpStatus.FOUND:] (302),
+   * [:HttpStatus.MOVED_TEMPORARILY:] (302, alias for
+   * [:HttpStatus.FOUND:]), [:HttpStatus.SEE_OTHER:] (303) and
+   * [:HttpStatus.TEMPORARY_REDIRECT:] (307). For
+   * [:HttpStatus.SEE_OTHER:] (303) automatic redirect will also happen
+   * for "POST" requests with the method changed to "GET" when
+   * following the redirect.
+   *
+   * All headers added to the request will be added to the redirection
+   * request(s). However, any body send with the request will not be
+   * part of the redirection request(s).
+   */
+  bool followRedirects;
+
+  /**
+   * Set this property to the maximum number of redirects to follow
+   * when [followRedirects] is `true`. If this number is exceeded
+   * an error event will be added with a [RedirectException].
+   *
+   * The default value is 5.
+   */
+  int maxRedirects;
+
+  /**
+   * The method of the request.
+   */
+  String get method;
+
+  /**
+   * The uri of the request.
+   */
+  Uri get uri;
+
+  /// Gets and sets the content length of the request.
+  ///
+  /// If the size of the request is not known in advance set content length to
+  /// -1, which is also the default.
+  int contentLength;
+
+  /**
+   * Gets or sets if the [HttpClientRequest] should buffer output.
+   *
+   * Default value is `true`.
+   *
+   * __Note__: Disabling buffering of the output can result in very poor
+   * performance, when writing many small chunks.
+   */
+  bool bufferOutput;
+
+  /**
+   * Returns the client request headers.
+   *
+   * The client request headers can be modified until the client
+   * request body is written to or closed. After that they become
+   * immutable.
+   */
+  HttpHeaders get headers;
+
+  /**
+   * Cookies to present to the server (in the 'cookie' header).
+   */
+  List<Cookie> get cookies;
+
+  /// A [HttpClientResponse] future that will complete once the response is
+  /// available.
+  ///
+  /// If an error occurs before the response is available, this future will
+  /// complete with an error.
+  Future<HttpClientResponse> get done;
+
+  /**
+   * Close the request for input. Returns the value of [done].
+   */
+  Future<HttpClientResponse> close();
+
+  /// Gets information about the client connection.
+  ///
+  /// Returns [:null:] if the socket is not available.
+  HttpConnectionInfo get connectionInfo;
+}
+
+/**
+ * HTTP response for a client connection.
+ *
+ * The body of a [HttpClientResponse] object is a
+ * [Stream] of data from the server. Listen to the body to handle
+ * the data and be notified when the entire body is received.
+ *
+ *     new HttpClient().get('localhost', 80, '/file.txt')
+ *          .then((HttpClientRequest request) => request.close())
+ *          .then((HttpClientResponse response) {
+ *            response.transform(utf8.decoder).listen((contents) {
+ *              // handle data
+ *            });
+ *          });
+ */
+abstract class HttpClientResponse implements Stream<List<int>> {
+  /**
+   * Returns the status code.
+   *
+   * The status code must be set before the body is written
+   * to. Setting the status code after writing to the body will throw
+   * a `StateError`.
+   */
+  int get statusCode;
+
+  /**
+   * Returns the reason phrase associated with the status code.
+   *
+   * The reason phrase must be set before the body is written
+   * to. Setting the reason phrase after writing to the body will throw
+   * a `StateError`.
+   */
+  String get reasonPhrase;
+
+  /**
+   * Returns the content length of the response body. Returns -1 if the size of
+   * the response body is not known in advance.
+   *
+   * If the content length needs to be set, it must be set before the
+   * body is written to. Setting the reason phrase after writing to
+   * the body will throw a `StateError`.
+   */
+  int get contentLength;
+
+  /**
+   * Gets the persistent connection state returned by the server.
+   *
+   * if the persistent connection state needs to be set, it must be
+   * set before the body is written to. Setting the reason phrase
+   * after writing to the body will throw a `StateError`.
+   */
+  bool get persistentConnection;
+
+  /**
+   * Returns whether the status code is one of the normal redirect
+   * codes [HttpStatus.MOVED_PERMANENTLY], [HttpStatus.FOUND],
+   * [HttpStatus.MOVED_TEMPORARILY], [HttpStatus.SEE_OTHER] and
+   * [HttpStatus.TEMPORARY_REDIRECT].
+   */
+  bool get isRedirect;
+
+  /**
+   * Returns the series of redirects this connection has been through. The
+   * list will be empty if no redirects were followed. [redirects] will be
+   * updated both in the case of an automatic and a manual redirect.
+   */
+  List<RedirectInfo> get redirects;
+
+  /**
+   * Redirects this connection to a new URL. The default value for
+   * [method] is the method for the current request. The default value
+   * for [url] is the value of the [HttpHeaders.LOCATION] header of
+   * the current response. All body data must have been read from the
+   * current response before calling [redirect].
+   *
+   * All headers added to the request will be added to the redirection
+   * request. However, any body sent with the request will not be
+   * part of the redirection request.
+   *
+   * If [followLoops] is set to [:true:], redirect will follow the redirect,
+   * even if the URL was already visited. The default value is [:false:].
+   *
+   * The method will ignore [HttpClientRequest.maxRedirects]
+   * and will always perform the redirect.
+   */
+  Future<HttpClientResponse> redirect(
+      [String method, Uri url, bool followLoops]);
+
+  /**
+   * Returns the client response headers.
+   *
+   * The client response headers are immutable.
+   */
+  HttpHeaders get headers;
+
+  /**
+   * Detach the underlying socket from the HTTP client. When the
+   * socket is detached the HTTP client will no longer perform any
+   * operations on it.
+   *
+   * This is normally used when a HTTP upgrade is negotiated and the
+   * communication should continue with a different protocol.
+   */
+  Future<Socket> detachSocket();
+
+  /**
+   * Cookies set by the server (from the 'set-cookie' header).
+   */
+  List<Cookie> get cookies;
+
+  /**
+   * Returns the certificate of the HTTPS server providing the response.
+   * Returns null if the connection is not a secure TLS or SSL connection.
+   */
+  X509Certificate get certificate;
+
+  /**
+   * Gets information about the client connection. Returns [:null:] if the socket
+   * is not available.
+   */
+  HttpConnectionInfo get connectionInfo;
+}
+
+abstract class HttpClientCredentials {}
+
+/**
+ * Represents credentials for basic authentication.
+ */
+abstract class HttpClientBasicCredentials extends HttpClientCredentials {
+  factory HttpClientBasicCredentials(String username, String password) =>
+      new _HttpClientBasicCredentials(username, password);
+}
+
+/**
+ * Represents credentials for digest authentication. Digest
+ * authentication is only supported for servers using the MD5
+ * algorithm and quality of protection (qop) of either "none" or
+ * "auth".
+ */
+abstract class HttpClientDigestCredentials extends HttpClientCredentials {
+  factory HttpClientDigestCredentials(String username, String password) =>
+      new _HttpClientDigestCredentials(username, password);
+}
+
+/**
+ * Information about an [HttpRequest], [HttpResponse], [HttpClientRequest], or
+ * [HttpClientResponse] connection.
+ */
+abstract class HttpConnectionInfo {
+  InternetAddress get remoteAddress;
+  int get remotePort;
+  int get localPort;
+}
+
+/**
+ * Redirect information.
+ */
+abstract class RedirectInfo {
+  /**
+   * Returns the status code used for the redirect.
+   */
+  int get statusCode;
+
+  /**
+   * Returns the method used for the redirect.
+   */
+  String get method;
+
+  /**
+   * Returns the location for the redirect.
+   */
+  Uri get location;
+}
+
+/**
+ * When detaching a socket from either the [:HttpServer:] or the
+ * [:HttpClient:] due to a HTTP connection upgrade there might be
+ * unparsed data already read from the socket. This unparsed data
+ * together with the detached socket is returned in an instance of
+ * this class.
+ */
+abstract class DetachedSocket {
+  Socket get socket;
+  List<int> get unparsedData;
+}
+
+class HttpException implements IOException {
+  final String message;
+  final Uri uri;
+
+  const HttpException(this.message, {this.uri});
+
+  String toString() {
+    var b = new StringBuffer()..write('HttpException: ')..write(message);
+    if (uri != null) {
+      b.write(', uri = $uri');
+    }
+    return b.toString();
+  }
+}
+
+class RedirectException implements HttpException {
+  final String message;
+  final List<RedirectInfo> redirects;
+
+  const RedirectException(this.message, this.redirects);
+
+  String toString() => "RedirectException: $message";
+
+  Uri get uri => redirects.last.location;
+}
diff --git a/lib/http_overrides.dart b/lib/http_overrides.dart
new file mode 100644
index 0000000..7f9e689
--- /dev/null
+++ b/lib/http_overrides.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of dart._http;
+
+final _httpOverridesToken = new Object();
+
+const _asyncRunZoned = runZoned;
+
+/// This class facilitates overriding [HttpClient] with a mock implementation.
+/// It should be extended by another class in client code with overrides
+/// that construct a mock implementation. The implementation in this base class
+/// defaults to the actual [HttpClient] implementation. For example:
+///
+/// ```
+/// class MyHttpClient implements HttpClient {
+///   ...
+///   // An implementation of the HttpClient interface
+///   ...
+/// }
+///
+/// main() {
+///   HttpOverrides.runZoned(() {
+///     ...
+///     // Operations will use MyHttpClient instead of the real HttpClient
+///     // implementation whenever HttpClient is used.
+///     ...
+///   }, createHttpClient: (SecurityContext c) => new MyHttpClient(c));
+/// }
+/// ```
+abstract class HttpOverrides {
+  static HttpOverrides _global;
+
+  static HttpOverrides get current {
+    return Zone.current[_httpOverridesToken] ?? _global;
+  }
+
+  /// The [HttpOverrides] to use in the root [Zone].
+  ///
+  /// These are the [HttpOverrides] that will be used in the root Zone, and in
+  /// Zone's that do not set [HttpOverrides] and whose ancestors up to the root
+  /// Zone do not set [HttpOverrides].
+  static set global(HttpOverrides overrides) {
+    _global = overrides;
+  }
+
+  /// Runs [body] in a fresh [Zone] using the provided overrides.
+  static R runZoned<R>(R body(),
+      {HttpClient Function(SecurityContext) createHttpClient,
+      String Function(Uri uri, Map<String, String> environment)
+          findProxyFromEnvironment,
+      ZoneSpecification zoneSpecification,
+      Function onError}) {
+    HttpOverrides overrides =
+        new _HttpOverridesScope(createHttpClient, findProxyFromEnvironment);
+    return _asyncRunZoned<R>(body,
+        zoneValues: {_httpOverridesToken: overrides},
+        zoneSpecification: zoneSpecification,
+        onError: onError);
+  }
+
+  /// Runs [body] in a fresh [Zone] using the overrides found in [overrides].
+  ///
+  /// Note that [overrides] should be an instance of a class that extends
+  /// [HttpOverrides].
+  static R runWithHttpOverrides<R>(R body(), HttpOverrides overrides,
+      {ZoneSpecification zoneSpecification, Function onError}) {
+    return _asyncRunZoned<R>(body,
+        zoneValues: {_httpOverridesToken: overrides},
+        zoneSpecification: zoneSpecification,
+        onError: onError);
+  }
+
+  /// Returns a new [HttpClient] using the given [context].
+  ///
+  /// When this override is installed, this function overrides the behavior of
+  /// `new HttpClient`.
+  HttpClient createHttpClient(SecurityContext context) {
+    return new _HttpClient(context);
+  }
+
+  /// Resolves the proxy server to be used for HTTP connections.
+  ///
+  /// When this override is installed, this function overrides the behavior of
+  /// `HttpClient.findProxyFromEnvironment`.
+  String findProxyFromEnvironment(Uri url, Map<String, String> environment) {
+    return _HttpClient._findProxyFromEnvironment(url, environment);
+  }
+}
+
+class _HttpOverridesScope extends HttpOverrides {
+  final HttpOverrides _previous = HttpOverrides.current;
+
+  final HttpClient Function(SecurityContext) _createHttpClient;
+  final String Function(Uri uri, Map<String, String> environment)
+      _findProxyFromEnvironment;
+
+  _HttpOverridesScope(this._createHttpClient, this._findProxyFromEnvironment);
+
+  @override
+  HttpClient createHttpClient(SecurityContext context) {
+    if (_createHttpClient != null) return _createHttpClient(context);
+    if (_previous != null) return _previous.createHttpClient(context);
+    return super.createHttpClient(context);
+  }
+
+  @override
+  String findProxyFromEnvironment(Uri url, Map<String, String> environment) {
+    if (_findProxyFromEnvironment != null) {
+      return _findProxyFromEnvironment(url, environment);
+    }
+    if (_previous != null) {
+      return _previous.findProxyFromEnvironment(url, environment);
+    }
+    return super.findProxyFromEnvironment(url, environment);
+  }
+}
diff --git a/lib/src/crypto.dart b/lib/src/crypto.dart
new file mode 100644
index 0000000..56c16dd
--- /dev/null
+++ b/lib/src/crypto.dart
@@ -0,0 +1,458 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http_io;
+
+class _CryptoUtils {
+  static const int PAD = 61; // '='
+  static const int CR = 13; // '\r'
+  static const int LF = 10; // '\n'
+  static const int LINE_LENGTH = 76;
+
+  static const String _encodeTable =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+  static const String _encodeTableUrlSafe =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+  // Lookup table used for finding Base 64 alphabet index of a given byte.
+  // -2 : Outside Base 64 alphabet.
+  // -1 : '\r' or '\n'
+  //  0 : = (Padding character).
+  // >0 : Base 64 alphabet index of given byte.
+  static const List<int> _decodeTable = const [
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -1, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, 62, -2, 63, //
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, 00, -2, -2, //
+    -2, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, //
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, 63, //
+    -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, //
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, //
+    -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
+  ];
+
+  static Random _rng = new Random.secure();
+
+  static Uint8List getRandomBytes(int count) {
+    final Uint8List result = new Uint8List(count);
+    for (int i = 0; i < count; i++) {
+      result[i] = _rng.nextInt(0xff);
+    }
+    return result;
+  }
+
+  static String bytesToHex(List<int> bytes) {
+    var result = new StringBuffer();
+    for (var part in bytes) {
+      result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
+    }
+    return result.toString();
+  }
+
+  static String bytesToBase64(List<int> bytes,
+      [bool urlSafe = false, bool addLineSeparator = false]) {
+    int len = bytes.length;
+    if (len == 0) {
+      return "";
+    }
+    final String lookup = urlSafe ? _encodeTableUrlSafe : _encodeTable;
+    // Size of 24 bit chunks.
+    final int remainderLength = len.remainder(3);
+    final int chunkLength = len - remainderLength;
+    // Size of base output.
+    int outputLen = ((len ~/ 3) * 4) + ((remainderLength > 0) ? 4 : 0);
+    // Add extra for line separators.
+    if (addLineSeparator) {
+      outputLen += ((outputLen - 1) ~/ LINE_LENGTH) << 1;
+    }
+    List<int> out = new List<int>(outputLen);
+
+    // Encode 24 bit chunks.
+    int j = 0, i = 0, c = 0;
+    while (i < chunkLength) {
+      int x = ((bytes[i++] << 16) & 0xFFFFFF) |
+          ((bytes[i++] << 8) & 0xFFFFFF) |
+          bytes[i++];
+      out[j++] = lookup.codeUnitAt(x >> 18);
+      out[j++] = lookup.codeUnitAt((x >> 12) & 0x3F);
+      out[j++] = lookup.codeUnitAt((x >> 6) & 0x3F);
+      out[j++] = lookup.codeUnitAt(x & 0x3f);
+      // Add optional line separator for each 76 char output.
+      if (addLineSeparator && ++c == 19 && j < outputLen - 2) {
+        out[j++] = CR;
+        out[j++] = LF;
+        c = 0;
+      }
+    }
+
+    // If input length if not a multiple of 3, encode remaining bytes and
+    // add padding.
+    if (remainderLength == 1) {
+      int x = bytes[i];
+      out[j++] = lookup.codeUnitAt(x >> 2);
+      out[j++] = lookup.codeUnitAt((x << 4) & 0x3F);
+      out[j++] = PAD;
+      out[j++] = PAD;
+    } else if (remainderLength == 2) {
+      int x = bytes[i];
+      int y = bytes[i + 1];
+      out[j++] = lookup.codeUnitAt(x >> 2);
+      out[j++] = lookup.codeUnitAt(((x << 4) | (y >> 4)) & 0x3F);
+      out[j++] = lookup.codeUnitAt((y << 2) & 0x3F);
+      out[j++] = PAD;
+    }
+
+    return new String.fromCharCodes(out);
+  }
+
+  static List<int> base64StringToBytes(String input,
+      [bool ignoreInvalidCharacters = true]) {
+    int len = input.length;
+    if (len == 0) {
+      return new List<int>(0);
+    }
+
+    // Count '\r', '\n' and illegal characters, For illegal characters,
+    // if [ignoreInvalidCharacters] is false, throw an exception.
+    int extrasLen = 0;
+    for (int i = 0; i < len; i++) {
+      int c = _decodeTable[input.codeUnitAt(i)];
+      if (c < 0) {
+        extrasLen++;
+        if (c == -2 && !ignoreInvalidCharacters) {
+          throw new FormatException('Invalid character: ${input[i]}');
+        }
+      }
+    }
+
+    if ((len - extrasLen) % 4 != 0) {
+      throw new FormatException('''Size of Base 64 characters in Input
+          must be a multiple of 4. Input: $input''');
+    }
+
+    // Count pad characters, ignore illegal characters at the end.
+    int padLength = 0;
+    for (int i = len - 1; i >= 0; i--) {
+      int currentCodeUnit = input.codeUnitAt(i);
+      if (_decodeTable[currentCodeUnit] > 0) break;
+      if (currentCodeUnit == PAD) padLength++;
+    }
+    int outputLen = (((len - extrasLen) * 6) >> 3) - padLength;
+    List<int> out = new List<int>(outputLen);
+
+    for (int i = 0, o = 0; o < outputLen;) {
+      // Accumulate 4 valid 6 bit Base 64 characters into an int.
+      int x = 0;
+      for (int j = 4; j > 0;) {
+        int c = _decodeTable[input.codeUnitAt(i++)];
+        if (c >= 0) {
+          x = ((x << 6) & 0xFFFFFF) | c;
+          j--;
+        }
+      }
+      out[o++] = x >> 16;
+      if (o < outputLen) {
+        out[o++] = (x >> 8) & 0xFF;
+        if (o < outputLen) out[o++] = x & 0xFF;
+      }
+    }
+    return out;
+  }
+}
+
+// Constants.
+const _MASK_8 = 0xff;
+const _MASK_32 = 0xffffffff;
+const _BITS_PER_BYTE = 8;
+const _BYTES_PER_WORD = 4;
+
+// Base class encapsulating common behavior for cryptographic hash
+// functions.
+abstract class _HashBase {
+  // Hasher state.
+  final int _chunkSizeInWords;
+  final int _digestSizeInWords;
+  final bool _bigEndianWords;
+  int _lengthInBytes = 0;
+  List<int> _pendingData;
+  List<int> _currentChunk;
+  List<int> _h;
+  bool _digestCalled = false;
+
+  _HashBase(
+      this._chunkSizeInWords, this._digestSizeInWords, this._bigEndianWords)
+      : _pendingData = [] {
+    _currentChunk = new List(_chunkSizeInWords);
+    _h = new List(_digestSizeInWords);
+  }
+
+  // Update the hasher with more data.
+  add(List<int> data) {
+    if (_digestCalled) {
+      throw new StateError(
+          'Hash update method called after digest was retrieved');
+    }
+    _lengthInBytes += data.length;
+    _pendingData.addAll(data);
+    _iterate();
+  }
+
+  // Finish the hash computation and return the digest string.
+  List<int> close() {
+    if (_digestCalled) {
+      return _resultAsBytes();
+    }
+    _digestCalled = true;
+    _finalizeData();
+    _iterate();
+    assert(_pendingData.length == 0);
+    return _resultAsBytes();
+  }
+
+  // Returns the block size of the hash in bytes.
+  int get blockSize {
+    return _chunkSizeInWords * _BYTES_PER_WORD;
+  }
+
+  // Create a fresh instance of this Hash.
+  newInstance();
+
+  // One round of the hash computation.
+  _updateHash(List<int> m);
+
+  // Helper methods.
+  _add32(x, y) => (x + y) & _MASK_32;
+  _roundUp(val, n) => (val + n - 1) & -n;
+
+  // Rotate left limiting to unsigned 32-bit values.
+  int _rotl32(int val, int shift) {
+    var mod_shift = shift & 31;
+    return ((val << mod_shift) & _MASK_32) |
+        ((val & _MASK_32) >> (32 - mod_shift));
+  }
+
+  // Compute the final result as a list of bytes from the hash words.
+  List<int> _resultAsBytes() {
+    var result = <int>[];
+    for (var i = 0; i < _h.length; i++) {
+      result.addAll(_wordToBytes(_h[i]));
+    }
+    return result;
+  }
+
+  // Converts a list of bytes to a chunk of 32-bit words.
+  _bytesToChunk(List<int> data, int dataIndex) {
+    assert((data.length - dataIndex) >= (_chunkSizeInWords * _BYTES_PER_WORD));
+
+    for (var wordIndex = 0; wordIndex < _chunkSizeInWords; wordIndex++) {
+      var w3 = _bigEndianWords ? data[dataIndex] : data[dataIndex + 3];
+      var w2 = _bigEndianWords ? data[dataIndex + 1] : data[dataIndex + 2];
+      var w1 = _bigEndianWords ? data[dataIndex + 2] : data[dataIndex + 1];
+      var w0 = _bigEndianWords ? data[dataIndex + 3] : data[dataIndex];
+      dataIndex += 4;
+      var word = (w3 & 0xff) << 24;
+      word |= (w2 & _MASK_8) << 16;
+      word |= (w1 & _MASK_8) << 8;
+      word |= (w0 & _MASK_8);
+      _currentChunk[wordIndex] = word;
+    }
+  }
+
+  // Convert a 32-bit word to four bytes.
+  List<int> _wordToBytes(int word) {
+    List<int> bytes = new List(_BYTES_PER_WORD);
+    bytes[0] = (word >> (_bigEndianWords ? 24 : 0)) & _MASK_8;
+    bytes[1] = (word >> (_bigEndianWords ? 16 : 8)) & _MASK_8;
+    bytes[2] = (word >> (_bigEndianWords ? 8 : 16)) & _MASK_8;
+    bytes[3] = (word >> (_bigEndianWords ? 0 : 24)) & _MASK_8;
+    return bytes;
+  }
+
+  // Iterate through data updating the hash computation for each
+  // chunk.
+  _iterate() {
+    var len = _pendingData.length;
+    var chunkSizeInBytes = _chunkSizeInWords * _BYTES_PER_WORD;
+    if (len >= chunkSizeInBytes) {
+      var index = 0;
+      for (; (len - index) >= chunkSizeInBytes; index += chunkSizeInBytes) {
+        _bytesToChunk(_pendingData, index);
+        _updateHash(_currentChunk);
+      }
+      _pendingData = _pendingData.sublist(index, len);
+    }
+  }
+
+  // Finalize the data. Add a 1 bit to the end of the message. Expand with
+  // 0 bits and add the length of the message.
+  _finalizeData() {
+    _pendingData.add(0x80);
+    var contentsLength = _lengthInBytes + 9;
+    var chunkSizeInBytes = _chunkSizeInWords * _BYTES_PER_WORD;
+    var finalizedLength = _roundUp(contentsLength, chunkSizeInBytes);
+    var zeroPadding = finalizedLength - contentsLength;
+    for (var i = 0; i < zeroPadding; i++) {
+      _pendingData.add(0);
+    }
+    var lengthInBits = _lengthInBytes * _BITS_PER_BYTE;
+    assert(lengthInBits < pow(2, 32));
+    if (_bigEndianWords) {
+      _pendingData.addAll(_wordToBytes(0));
+      _pendingData.addAll(_wordToBytes(lengthInBits & _MASK_32));
+    } else {
+      _pendingData.addAll(_wordToBytes(lengthInBits & _MASK_32));
+      _pendingData.addAll(_wordToBytes(0));
+    }
+  }
+}
+
+// The MD5 hasher is used to compute an MD5 message digest.
+class _MD5 extends _HashBase {
+  _MD5() : super(16, 4, false) {
+    _h[0] = 0x67452301;
+    _h[1] = 0xefcdab89;
+    _h[2] = 0x98badcfe;
+    _h[3] = 0x10325476;
+  }
+
+  // Returns a new instance of this Hash.
+  _MD5 newInstance() {
+    return new _MD5();
+  }
+
+  static const _k = const [
+    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, //
+    0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //
+    0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, //
+    0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, //
+    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, //
+    0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //
+    0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, //
+    0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //
+    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, //
+    0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //
+    0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+  ];
+
+  static const _r = const [
+    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, //
+    20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, //
+    16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, //
+    10, 15, 21, 6, 10, 15, 21
+  ];
+
+  // Compute one iteration of the MD5 algorithm with a chunk of
+  // 16 32-bit pieces.
+  void _updateHash(List<int> m) {
+    assert(m.length == 16);
+
+    var a = _h[0];
+    var b = _h[1];
+    var c = _h[2];
+    var d = _h[3];
+
+    var t0;
+    var t1;
+
+    for (var i = 0; i < 64; i++) {
+      if (i < 16) {
+        t0 = (b & c) | ((~b & _MASK_32) & d);
+        t1 = i;
+      } else if (i < 32) {
+        t0 = (d & b) | ((~d & _MASK_32) & c);
+        t1 = ((5 * i) + 1) % 16;
+      } else if (i < 48) {
+        t0 = b ^ c ^ d;
+        t1 = ((3 * i) + 5) % 16;
+      } else {
+        t0 = c ^ (b | (~d & _MASK_32));
+        t1 = (7 * i) % 16;
+      }
+
+      var temp = d;
+      d = c;
+      c = b;
+      b = _add32(
+          b, _rotl32(_add32(_add32(a, t0), _add32(_k[i], m[t1])), _r[i]));
+      a = temp;
+    }
+
+    _h[0] = _add32(a, _h[0]);
+    _h[1] = _add32(b, _h[1]);
+    _h[2] = _add32(c, _h[2]);
+    _h[3] = _add32(d, _h[3]);
+  }
+}
+
+// The SHA1 hasher is used to compute an SHA1 message digest.
+class _SHA1 extends _HashBase {
+  // Construct a SHA1 hasher object.
+  _SHA1()
+      : _w = new List(80),
+        super(16, 5, true) {
+    _h[0] = 0x67452301;
+    _h[1] = 0xEFCDAB89;
+    _h[2] = 0x98BADCFE;
+    _h[3] = 0x10325476;
+    _h[4] = 0xC3D2E1F0;
+  }
+
+  // Returns a new instance of this Hash.
+  _SHA1 newInstance() {
+    return new _SHA1();
+  }
+
+  // Compute one iteration of the SHA1 algorithm with a chunk of
+  // 16 32-bit pieces.
+  void _updateHash(List<int> m) {
+    assert(m.length == 16);
+
+    var a = _h[0];
+    var b = _h[1];
+    var c = _h[2];
+    var d = _h[3];
+    var e = _h[4];
+
+    for (var i = 0; i < 80; i++) {
+      if (i < 16) {
+        _w[i] = m[i];
+      } else {
+        var n = _w[i - 3] ^ _w[i - 8] ^ _w[i - 14] ^ _w[i - 16];
+        _w[i] = _rotl32(n, 1);
+      }
+      var t = _add32(_add32(_rotl32(a, 5), e), _w[i]);
+      if (i < 20) {
+        t = _add32(_add32(t, (b & c) | (~b & d)), 0x5A827999);
+      } else if (i < 40) {
+        t = _add32(_add32(t, (b ^ c ^ d)), 0x6ED9EBA1);
+      } else if (i < 60) {
+        t = _add32(_add32(t, (b & c) | (b & d) | (c & d)), 0x8F1BBCDC);
+      } else {
+        t = _add32(_add32(t, b ^ c ^ d), 0xCA62C1D6);
+      }
+
+      e = d;
+      d = c;
+      c = _rotl32(b, 30);
+      b = a;
+      a = t & _MASK_32;
+    }
+
+    _h[0] = _add32(a, _h[0]);
+    _h[1] = _add32(b, _h[1]);
+    _h[2] = _add32(c, _h[2]);
+    _h[3] = _add32(d, _h[3]);
+    _h[4] = _add32(e, _h[4]);
+  }
+
+  List<int> _w;
+}
diff --git a/lib/src/http_date.dart b/lib/src/http_date.dart
new file mode 100644
index 0000000..aba09cd
--- /dev/null
+++ b/lib/src/http_date.dart
@@ -0,0 +1,388 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http_io;
+
+/**
+ * Utility functions for working with dates with HTTP specific date
+ * formats.
+ */
+class HttpDate {
+  // From RFC-2616 section "3.3.1 Full Date",
+  // http://tools.ietf.org/html/rfc2616#section-3.3.1
+  //
+  // HTTP-date    = rfc1123-date | rfc850-date | asctime-date
+  // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
+  // rfc850-date  = weekday "," SP date2 SP time SP "GMT"
+  // asctime-date = wkday SP date3 SP time SP 4DIGIT
+  // date1        = 2DIGIT SP month SP 4DIGIT
+  //                ; day month year (e.g., 02 Jun 1982)
+  // date2        = 2DIGIT "-" month "-" 2DIGIT
+  //                ; day-month-year (e.g., 02-Jun-82)
+  // date3        = month SP ( 2DIGIT | ( SP 1DIGIT ))
+  //                ; month day (e.g., Jun  2)
+  // time         = 2DIGIT ":" 2DIGIT ":" 2DIGIT
+  //                ; 00:00:00 - 23:59:59
+  // wkday        = "Mon" | "Tue" | "Wed"
+  //              | "Thu" | "Fri" | "Sat" | "Sun"
+  // weekday      = "Monday" | "Tuesday" | "Wednesday"
+  //              | "Thursday" | "Friday" | "Saturday" | "Sunday"
+  // month        = "Jan" | "Feb" | "Mar" | "Apr"
+  //              | "May" | "Jun" | "Jul" | "Aug"
+  //              | "Sep" | "Oct" | "Nov" | "Dec"
+
+  /**
+   * Format a date according to
+   * [RFC-1123](http://tools.ietf.org/html/rfc1123 "RFC-1123"),
+   * e.g. `Thu, 1 Jan 1970 00:00:00 GMT`.
+   */
+  static String format(DateTime date) {
+    const List wkday = const ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+    const List month = const [
+      "Jan",
+      "Feb",
+      "Mar",
+      "Apr",
+      "May",
+      "Jun",
+      "Jul",
+      "Aug",
+      "Sep",
+      "Oct",
+      "Nov",
+      "Dec"
+    ];
+
+    DateTime d = date.toUtc();
+    StringBuffer sb = new StringBuffer()
+      ..write(wkday[d.weekday - 1])
+      ..write(", ")
+      ..write(d.day <= 9 ? "0" : "")
+      ..write(d.day.toString())
+      ..write(" ")
+      ..write(month[d.month - 1])
+      ..write(" ")
+      ..write(d.year.toString())
+      ..write(d.hour <= 9 ? " 0" : " ")
+      ..write(d.hour.toString())
+      ..write(d.minute <= 9 ? ":0" : ":")
+      ..write(d.minute.toString())
+      ..write(d.second <= 9 ? ":0" : ":")
+      ..write(d.second.toString())
+      ..write(" GMT");
+    return sb.toString();
+  }
+
+  /**
+   * Parse a date string in either of the formats
+   * [RFC-1123](http://tools.ietf.org/html/rfc1123 "RFC-1123"),
+   * [RFC-850](http://tools.ietf.org/html/rfc850 "RFC-850") or
+   * ANSI C's asctime() format. These formats are listed here.
+   *
+   *     Thu, 1 Jan 1970 00:00:00 GMT
+   *     Thursday, 1-Jan-1970 00:00:00 GMT
+   *     Thu Jan  1 00:00:00 1970
+   *
+   * For more information see [RFC-2616 section
+   * 3.1.1](http://tools.ietf.org/html/rfc2616#section-3.3.1
+   * "RFC-2616 section 3.1.1").
+   */
+  static DateTime parse(String date) {
+    final int SP = 32;
+    const List wkdays = const ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
+    const List weekdays = const [
+      "Monday",
+      "Tuesday",
+      "Wednesday",
+      "Thursday",
+      "Friday",
+      "Saturday",
+      "Sunday"
+    ];
+    const List months = const [
+      "Jan",
+      "Feb",
+      "Mar",
+      "Apr",
+      "May",
+      "Jun",
+      "Jul",
+      "Aug",
+      "Sep",
+      "Oct",
+      "Nov",
+      "Dec"
+    ];
+    const List wkdaysLowerCase = const [
+      "mon",
+      "tue",
+      "wed",
+      "thu",
+      "fri",
+      "sat",
+      "sun"
+    ];
+    const List weekdaysLowerCase = const [
+      "monday",
+      "tuesday",
+      "wednesday",
+      "thursday",
+      "friday",
+      "saturday",
+      "sunday"
+    ];
+    const List monthsLowerCase = const [
+      "jan",
+      "feb",
+      "mar",
+      "apr",
+      "may",
+      "jun",
+      "jul",
+      "aug",
+      "sep",
+      "oct",
+      "nov",
+      "dec"
+    ];
+
+    final int formatRfc1123 = 0;
+    final int formatRfc850 = 1;
+    final int formatAsctime = 2;
+
+    int index = 0;
+    String tmp;
+    int format;
+
+    void expect(String s) {
+      if (date.length - index < s.length) {
+        throw new HttpException("Invalid HTTP date $date");
+      }
+      String tmp = date.substring(index, index + s.length);
+      if (tmp != s) {
+        throw new HttpException("Invalid HTTP date $date");
+      }
+      index += s.length;
+    }
+
+    int expectWeekday() {
+      int weekday;
+      // The formatting of the weekday signals the format of the date string.
+      int pos = date.indexOf(",", index);
+      if (pos == -1) {
+        int pos = date.indexOf(" ", index);
+        if (pos == -1) throw new HttpException("Invalid HTTP date $date");
+        tmp = date.substring(index, pos);
+        index = pos + 1;
+        weekday = wkdays.indexOf(tmp);
+        if (weekday != -1) {
+          format = formatAsctime;
+          return weekday;
+        }
+      } else {
+        tmp = date.substring(index, pos);
+        index = pos + 1;
+        weekday = wkdays.indexOf(tmp);
+        if (weekday != -1) {
+          format = formatRfc1123;
+          return weekday;
+        }
+        weekday = weekdays.indexOf(tmp);
+        if (weekday != -1) {
+          format = formatRfc850;
+          return weekday;
+        }
+      }
+      throw new HttpException("Invalid HTTP date $date");
+    }
+
+    int expectMonth(String separator) {
+      int pos = date.indexOf(separator, index);
+      if (pos - index != 3) throw new HttpException("Invalid HTTP date $date");
+      tmp = date.substring(index, pos);
+      index = pos + 1;
+      int month = months.indexOf(tmp);
+      if (month != -1) return month;
+      throw new HttpException("Invalid HTTP date $date");
+    }
+
+    int expectNum(String separator) {
+      int pos;
+      if (separator.length > 0) {
+        pos = date.indexOf(separator, index);
+      } else {
+        pos = date.length;
+      }
+      String tmp = date.substring(index, pos);
+      index = pos + separator.length;
+      try {
+        int value = int.parse(tmp);
+        return value;
+      } on FormatException catch (e) {
+        throw new HttpException("Invalid HTTP date $date");
+      }
+    }
+
+    void expectEnd() {
+      if (index != date.length) {
+        throw new HttpException("Invalid HTTP date $date");
+      }
+    }
+
+    int weekday = expectWeekday();
+    int day;
+    int month;
+    int year;
+    int hours;
+    int minutes;
+    int seconds;
+    if (format == formatAsctime) {
+      month = expectMonth(" ");
+      if (date.codeUnitAt(index) == SP) index++;
+      day = expectNum(" ");
+      hours = expectNum(":");
+      minutes = expectNum(":");
+      seconds = expectNum(" ");
+      year = expectNum("");
+    } else {
+      expect(" ");
+      day = expectNum(format == formatRfc1123 ? " " : "-");
+      month = expectMonth(format == formatRfc1123 ? " " : "-");
+      year = expectNum(" ");
+      hours = expectNum(":");
+      minutes = expectNum(":");
+      seconds = expectNum(" ");
+      expect("GMT");
+    }
+    expectEnd();
+    return new DateTime.utc(year, month + 1, day, hours, minutes, seconds, 0);
+  }
+
+  // Parse a cookie date string.
+  static DateTime _parseCookieDate(String date) {
+    const List monthsLowerCase = const [
+      "jan",
+      "feb",
+      "mar",
+      "apr",
+      "may",
+      "jun",
+      "jul",
+      "aug",
+      "sep",
+      "oct",
+      "nov",
+      "dec"
+    ];
+
+    int position = 0;
+
+    void error() {
+      throw new HttpException("Invalid cookie date $date");
+    }
+
+    bool isEnd() => position == date.length;
+
+    bool isDelimiter(String s) {
+      int char = s.codeUnitAt(0);
+      if (char == 0x09) return true;
+      if (char >= 0x20 && char <= 0x2F) return true;
+      if (char >= 0x3B && char <= 0x40) return true;
+      if (char >= 0x5B && char <= 0x60) return true;
+      if (char >= 0x7B && char <= 0x7E) return true;
+      return false;
+    }
+
+    bool isNonDelimiter(String s) {
+      int char = s.codeUnitAt(0);
+      if (char >= 0x00 && char <= 0x08) return true;
+      if (char >= 0x0A && char <= 0x1F) return true;
+      if (char >= 0x30 && char <= 0x39) return true; // Digit
+      if (char == 0x3A) return true; // ':'
+      if (char >= 0x41 && char <= 0x5A) return true; // Alpha
+      if (char >= 0x61 && char <= 0x7A) return true; // Alpha
+      if (char >= 0x7F && char <= 0xFF) return true; // Alpha
+      return false;
+    }
+
+    bool isDigit(String s) {
+      int char = s.codeUnitAt(0);
+      if (char > 0x2F && char < 0x3A) return true;
+      return false;
+    }
+
+    int getMonth(String month) {
+      if (month.length < 3) return -1;
+      return monthsLowerCase.indexOf(month.substring(0, 3));
+    }
+
+    int toInt(String s) {
+      int index = 0;
+      for (; index < s.length && isDigit(s[index]); index++);
+      return int.parse(s.substring(0, index));
+    }
+
+    var tokens = [];
+    while (!isEnd()) {
+      while (!isEnd() && isDelimiter(date[position])) position++;
+      int start = position;
+      while (!isEnd() && isNonDelimiter(date[position])) position++;
+      tokens.add(date.substring(start, position).toLowerCase());
+      while (!isEnd() && isDelimiter(date[position])) position++;
+    }
+
+    String timeStr;
+    String dayOfMonthStr;
+    String monthStr;
+    String yearStr;
+
+    for (var token in tokens) {
+      if (token.length < 1) continue;
+      if (timeStr == null &&
+          token.length >= 5 &&
+          isDigit(token[0]) &&
+          (token[1] == ":" || (isDigit(token[1]) && token[2] == ":"))) {
+        timeStr = token;
+      } else if (dayOfMonthStr == null && isDigit(token[0])) {
+        dayOfMonthStr = token;
+      } else if (monthStr == null && getMonth(token) >= 0) {
+        monthStr = token;
+      } else if (yearStr == null &&
+          token.length >= 2 &&
+          isDigit(token[0]) &&
+          isDigit(token[1])) {
+        yearStr = token;
+      }
+    }
+
+    if (timeStr == null ||
+        dayOfMonthStr == null ||
+        monthStr == null ||
+        yearStr == null) {
+      error();
+    }
+
+    int year = toInt(yearStr);
+    if (year >= 70 && year <= 99)
+      year += 1900;
+    else if (year >= 0 && year <= 69) year += 2000;
+    if (year < 1601) error();
+
+    int dayOfMonth = toInt(dayOfMonthStr);
+    if (dayOfMonth < 1 || dayOfMonth > 31) error();
+
+    int month = getMonth(monthStr) + 1;
+
+    var timeList = timeStr.split(":");
+    if (timeList.length != 3) error();
+    int hour = toInt(timeList[0]);
+    int minute = toInt(timeList[1]);
+    int second = toInt(timeList[2]);
+    if (hour > 23) error();
+    if (minute > 59) error();
+    if (second > 59) error();
+
+    return new DateTime.utc(year, month, dayOfMonth, hour, minute, second, 0);
+  }
+}
diff --git a/lib/src/http_headers.dart b/lib/src/http_headers.dart
new file mode 100644
index 0000000..6f28da4
--- /dev/null
+++ b/lib/src/http_headers.dart
@@ -0,0 +1,1000 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http_io;
+
+class _HttpHeaders implements HttpHeaders {
+  final Map<String, List<String>> _headers;
+  final String protocolVersion;
+
+  bool _mutable = true; // Are the headers currently mutable?
+  List<String> _noFoldingHeaders;
+
+  int _contentLength = -1;
+  bool _persistentConnection = true;
+  bool _chunkedTransferEncoding = false;
+  String _host;
+  int _port;
+
+  final int _defaultPortForScheme;
+
+  _HttpHeaders(this.protocolVersion,
+      {int defaultPortForScheme: HttpClient.DEFAULT_HTTP_PORT,
+      _HttpHeaders initialHeaders})
+      : _headers = new HashMap<String, List<String>>(),
+        _defaultPortForScheme = defaultPortForScheme {
+    if (initialHeaders != null) {
+      initialHeaders._headers.forEach((name, value) => _headers[name] = value);
+      _contentLength = initialHeaders._contentLength;
+      _persistentConnection = initialHeaders._persistentConnection;
+      _chunkedTransferEncoding = initialHeaders._chunkedTransferEncoding;
+      _host = initialHeaders._host;
+      _port = initialHeaders._port;
+    }
+    if (protocolVersion == "1.0") {
+      _persistentConnection = false;
+      _chunkedTransferEncoding = false;
+    }
+  }
+
+  List<String> operator [](String name) => _headers[name.toLowerCase()];
+
+  String value(String name) {
+    name = name.toLowerCase();
+    List<String> values = _headers[name];
+    if (values == null) return null;
+    if (values.length > 1) {
+      throw new HttpException("More than one value for header $name");
+    }
+    return values[0];
+  }
+
+  void add(String name, value) {
+    _checkMutable();
+    _addAll(_validateField(name), value);
+  }
+
+  void _addAll(String name, value) {
+    assert(name == _validateField(name));
+    if (value is Iterable) {
+      for (var v in value) {
+        _add(name, _validateValue(v));
+      }
+    } else {
+      _add(name, _validateValue(value));
+    }
+  }
+
+  void set(String name, Object value) {
+    _checkMutable();
+    name = _validateField(name);
+    _headers.remove(name);
+    if (name == HttpHeaders.TRANSFER_ENCODING) {
+      _chunkedTransferEncoding = false;
+    }
+    _addAll(name, value);
+  }
+
+  void remove(String name, Object value) {
+    _checkMutable();
+    name = _validateField(name);
+    value = _validateValue(value);
+    List<String> values = _headers[name];
+    if (values != null) {
+      int index = values.indexOf(value);
+      if (index != -1) {
+        values.removeRange(index, index + 1);
+      }
+      if (values.length == 0) _headers.remove(name);
+    }
+    if (name == HttpHeaders.TRANSFER_ENCODING && value == "chunked") {
+      _chunkedTransferEncoding = false;
+    }
+  }
+
+  void removeAll(String name) {
+    _checkMutable();
+    name = _validateField(name);
+    _headers.remove(name);
+  }
+
+  void forEach(void f(String name, List<String> values)) {
+    _headers.forEach(f);
+  }
+
+  void noFolding(String name) {
+    if (_noFoldingHeaders == null) _noFoldingHeaders = new List<String>();
+    _noFoldingHeaders.add(name);
+  }
+
+  bool get persistentConnection => _persistentConnection;
+
+  void set persistentConnection(bool persistentConnection) {
+    _checkMutable();
+    if (persistentConnection == _persistentConnection) return;
+    if (persistentConnection) {
+      if (protocolVersion == "1.1") {
+        remove(HttpHeaders.CONNECTION, "close");
+      } else {
+        if (_contentLength == -1) {
+          throw new HttpException(
+              "Trying to set 'Connection: Keep-Alive' on HTTP 1.0 headers with "
+              "no ContentLength");
+        }
+        add(HttpHeaders.CONNECTION, "keep-alive");
+      }
+    } else {
+      if (protocolVersion == "1.1") {
+        add(HttpHeaders.CONNECTION, "close");
+      } else {
+        remove(HttpHeaders.CONNECTION, "keep-alive");
+      }
+    }
+    _persistentConnection = persistentConnection;
+  }
+
+  int get contentLength => _contentLength;
+
+  void set contentLength(int contentLength) {
+    _checkMutable();
+    if (protocolVersion == "1.0" &&
+        persistentConnection &&
+        contentLength == -1) {
+      throw new HttpException(
+          "Trying to clear ContentLength on HTTP 1.0 headers with "
+          "'Connection: Keep-Alive' set");
+    }
+    if (_contentLength == contentLength) return;
+    _contentLength = contentLength;
+    if (_contentLength >= 0) {
+      if (chunkedTransferEncoding) chunkedTransferEncoding = false;
+      _set(HttpHeaders.CONTENT_LENGTH, contentLength.toString());
+    } else {
+      removeAll(HttpHeaders.CONTENT_LENGTH);
+      if (protocolVersion == "1.1") {
+        chunkedTransferEncoding = true;
+      }
+    }
+  }
+
+  bool get chunkedTransferEncoding => _chunkedTransferEncoding;
+
+  void set chunkedTransferEncoding(bool chunkedTransferEncoding) {
+    _checkMutable();
+    if (chunkedTransferEncoding && protocolVersion == "1.0") {
+      throw new HttpException(
+          "Trying to set 'Transfer-Encoding: Chunked' on HTTP 1.0 headers");
+    }
+    if (chunkedTransferEncoding == _chunkedTransferEncoding) return;
+    if (chunkedTransferEncoding) {
+      List<String> values = _headers[HttpHeaders.TRANSFER_ENCODING];
+      if ((values == null || values.last != "chunked")) {
+        // Headers does not specify chunked encoding - add it if set.
+        _addValue(HttpHeaders.TRANSFER_ENCODING, "chunked");
+      }
+      contentLength = -1;
+    } else {
+      // Headers does specify chunked encoding - remove it if not set.
+      remove(HttpHeaders.TRANSFER_ENCODING, "chunked");
+    }
+    _chunkedTransferEncoding = chunkedTransferEncoding;
+  }
+
+  String get host => _host;
+
+  void set host(String host) {
+    _checkMutable();
+    _host = host;
+    _updateHostHeader();
+  }
+
+  int get port => _port;
+
+  void set port(int port) {
+    _checkMutable();
+    _port = port;
+    _updateHostHeader();
+  }
+
+  DateTime get ifModifiedSince {
+    List<String> values = _headers[HttpHeaders.IF_MODIFIED_SINCE];
+    if (values != null) {
+      try {
+        return HttpDate.parse(values[0]);
+      } on Exception catch (e) {
+        return null;
+      }
+    }
+    return null;
+  }
+
+  void set ifModifiedSince(DateTime ifModifiedSince) {
+    _checkMutable();
+    // Format "ifModifiedSince" header with date in Greenwich Mean Time (GMT).
+    String formatted = HttpDate.format(ifModifiedSince.toUtc());
+    _set(HttpHeaders.IF_MODIFIED_SINCE, formatted);
+  }
+
+  DateTime get date {
+    List<String> values = _headers[HttpHeaders.DATE];
+    if (values != null) {
+      try {
+        return HttpDate.parse(values[0]);
+      } on Exception catch (e) {
+        return null;
+      }
+    }
+    return null;
+  }
+
+  void set date(DateTime date) {
+    _checkMutable();
+    // Format "DateTime" header with date in Greenwich Mean Time (GMT).
+    String formatted = HttpDate.format(date.toUtc());
+    _set("date", formatted);
+  }
+
+  DateTime get expires {
+    List<String> values = _headers[HttpHeaders.EXPIRES];
+    if (values != null) {
+      try {
+        return HttpDate.parse(values[0]);
+      } on Exception catch (e) {
+        return null;
+      }
+    }
+    return null;
+  }
+
+  void set expires(DateTime expires) {
+    _checkMutable();
+    // Format "Expires" header with date in Greenwich Mean Time (GMT).
+    String formatted = HttpDate.format(expires.toUtc());
+    _set(HttpHeaders.EXPIRES, formatted);
+  }
+
+  ContentType get contentType {
+    var values = _headers["content-type"];
+    if (values != null) {
+      return ContentType.parse(values[0]);
+    } else {
+      return null;
+    }
+  }
+
+  void set contentType(ContentType contentType) {
+    _checkMutable();
+    _set(HttpHeaders.CONTENT_TYPE, contentType.toString());
+  }
+
+  void clear() {
+    _checkMutable();
+    _headers.clear();
+    _contentLength = -1;
+    _persistentConnection = true;
+    _chunkedTransferEncoding = false;
+    _host = null;
+    _port = null;
+  }
+
+  // [name] must be a lower-case version of the name.
+  void _add(String name, value) {
+    assert(name == _validateField(name));
+    // Use the length as index on what method to call. This is notable
+    // faster than computing hash and looking up in a hash-map.
+    switch (name.length) {
+      case 4:
+        if (HttpHeaders.DATE == name) {
+          _addDate(name, value);
+          return;
+        }
+        if (HttpHeaders.HOST == name) {
+          _addHost(name, value);
+          return;
+        }
+        break;
+      case 7:
+        if (HttpHeaders.EXPIRES == name) {
+          _addExpires(name, value);
+          return;
+        }
+        break;
+      case 10:
+        if (HttpHeaders.CONNECTION == name) {
+          _addConnection(name, value);
+          return;
+        }
+        break;
+      case 12:
+        if (HttpHeaders.CONTENT_TYPE == name) {
+          _addContentType(name, value);
+          return;
+        }
+        break;
+      case 14:
+        if (HttpHeaders.CONTENT_LENGTH == name) {
+          _addContentLength(name, value);
+          return;
+        }
+        break;
+      case 17:
+        if (HttpHeaders.TRANSFER_ENCODING == name) {
+          _addTransferEncoding(name, value);
+          return;
+        }
+        if (HttpHeaders.IF_MODIFIED_SINCE == name) {
+          _addIfModifiedSince(name, value);
+          return;
+        }
+    }
+    _addValue(name, value);
+  }
+
+  void _addContentLength(String name, value) {
+    if (value is int) {
+      contentLength = value;
+    } else if (value is String) {
+      contentLength = int.parse(value);
+    } else {
+      throw new HttpException("Unexpected type for header named $name");
+    }
+  }
+
+  void _addTransferEncoding(String name, value) {
+    if (value == "chunked") {
+      chunkedTransferEncoding = true;
+    } else {
+      _addValue(HttpHeaders.TRANSFER_ENCODING, value);
+    }
+  }
+
+  void _addDate(String name, value) {
+    if (value is DateTime) {
+      date = value;
+    } else if (value is String) {
+      _set(HttpHeaders.DATE, value);
+    } else {
+      throw new HttpException("Unexpected type for header named $name");
+    }
+  }
+
+  void _addExpires(String name, value) {
+    if (value is DateTime) {
+      expires = value;
+    } else if (value is String) {
+      _set(HttpHeaders.EXPIRES, value);
+    } else {
+      throw new HttpException("Unexpected type for header named $name");
+    }
+  }
+
+  void _addIfModifiedSince(String name, value) {
+    if (value is DateTime) {
+      ifModifiedSince = value;
+    } else if (value is String) {
+      _set(HttpHeaders.IF_MODIFIED_SINCE, value);
+    } else {
+      throw new HttpException("Unexpected type for header named $name");
+    }
+  }
+
+  void _addHost(String name, value) {
+    if (value is String) {
+      int pos = value.indexOf(":");
+      if (pos == -1) {
+        _host = value;
+        _port = HttpClient.DEFAULT_HTTP_PORT;
+      } else {
+        if (pos > 0) {
+          _host = value.substring(0, pos);
+        } else {
+          _host = null;
+        }
+        if (pos + 1 == value.length) {
+          _port = HttpClient.DEFAULT_HTTP_PORT;
+        } else {
+          try {
+            _port = int.parse(value.substring(pos + 1));
+          } on FormatException catch (e) {
+            _port = null;
+          }
+        }
+      }
+      _set(HttpHeaders.HOST, value);
+    } else {
+      throw new HttpException("Unexpected type for header named $name");
+    }
+  }
+
+  void _addConnection(String name, value) {
+    var lowerCaseValue = value.toLowerCase();
+    if (lowerCaseValue == 'close') {
+      _persistentConnection = false;
+    } else if (lowerCaseValue == 'keep-alive') {
+      _persistentConnection = true;
+    }
+    _addValue(name, value);
+  }
+
+  void _addContentType(String name, value) {
+    _set(HttpHeaders.CONTENT_TYPE, value);
+  }
+
+  void _addValue(String name, Object value) {
+    List<String> values = _headers[name];
+    if (values == null) {
+      values = new List<String>();
+      _headers[name] = values;
+    }
+    if (value is DateTime) {
+      values.add(HttpDate.format(value));
+    } else if (value is String) {
+      values.add(value);
+    } else {
+      values.add(_validateValue(value.toString()));
+    }
+  }
+
+  void _set(String name, String value) {
+    assert(name == _validateField(name));
+    List<String> values = new List<String>();
+    _headers[name] = values;
+    values.add(value);
+  }
+
+  _checkMutable() {
+    if (!_mutable) throw new HttpException("HTTP headers are not mutable");
+  }
+
+  _updateHostHeader() {
+    bool defaultPort = _port == null || _port == _defaultPortForScheme;
+    _set("host", defaultPort ? host : "$host:$_port");
+  }
+
+  _foldHeader(String name) {
+    if (name == HttpHeaders.SET_COOKIE ||
+        (_noFoldingHeaders != null && _noFoldingHeaders.indexOf(name) != -1)) {
+      return false;
+    }
+    return true;
+  }
+
+  void _finalize() {
+    _mutable = false;
+  }
+
+  void _build(BytesBuilder builder) {
+    for (String name in _headers.keys) {
+      List<String> values = _headers[name];
+      bool fold = _foldHeader(name);
+      var nameData = name.codeUnits;
+      builder.add(nameData);
+      builder.addByte(_CharCode.COLON);
+      builder.addByte(_CharCode.SP);
+      for (int i = 0; i < values.length; i++) {
+        if (i > 0) {
+          if (fold) {
+            builder.addByte(_CharCode.COMMA);
+            builder.addByte(_CharCode.SP);
+          } else {
+            builder.addByte(_CharCode.CR);
+            builder.addByte(_CharCode.LF);
+            builder.add(nameData);
+            builder.addByte(_CharCode.COLON);
+            builder.addByte(_CharCode.SP);
+          }
+        }
+        builder.add(values[i].codeUnits);
+      }
+      builder.addByte(_CharCode.CR);
+      builder.addByte(_CharCode.LF);
+    }
+  }
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    _headers.forEach((String name, List<String> values) {
+      sb..write(name)..write(": ");
+      bool fold = _foldHeader(name);
+      for (int i = 0; i < values.length; i++) {
+        if (i > 0) {
+          if (fold) {
+            sb.write(", ");
+          } else {
+            sb..write("\n")..write(name)..write(": ");
+          }
+        }
+        sb.write(values[i]);
+      }
+      sb.write("\n");
+    });
+    return sb.toString();
+  }
+
+  List<Cookie> _parseCookies() {
+    // Parse a Cookie header value according to the rules in RFC 6265.
+    var cookies = new List<Cookie>();
+    void parseCookieString(String s) {
+      int index = 0;
+
+      bool done() => index == -1 || index == s.length;
+
+      void skipWS() {
+        while (!done()) {
+          if (s[index] != " " && s[index] != "\t") return;
+          index++;
+        }
+      }
+
+      String parseName() {
+        int start = index;
+        while (!done()) {
+          if (s[index] == " " || s[index] == "\t" || s[index] == "=") break;
+          index++;
+        }
+        return s.substring(start, index);
+      }
+
+      String parseValue() {
+        int start = index;
+        while (!done()) {
+          if (s[index] == " " || s[index] == "\t" || s[index] == ";") break;
+          index++;
+        }
+        return s.substring(start, index);
+      }
+
+      bool expect(String expected) {
+        if (done()) return false;
+        if (s[index] != expected) return false;
+        index++;
+        return true;
+      }
+
+      while (!done()) {
+        skipWS();
+        if (done()) return;
+        String name = parseName();
+        skipWS();
+        if (!expect("=")) {
+          index = s.indexOf(';', index);
+          continue;
+        }
+        skipWS();
+        String value = parseValue();
+        try {
+          cookies.add(new _Cookie(name, value));
+        } catch (_) {
+          // Skip it, invalid cookie data.
+        }
+        skipWS();
+        if (done()) return;
+        if (!expect(";")) {
+          index = s.indexOf(';', index);
+          continue;
+        }
+      }
+    }
+
+    List<String> values = _headers[HttpHeaders.COOKIE];
+    if (values != null) {
+      values.forEach((headerValue) => parseCookieString(headerValue));
+    }
+    return cookies;
+  }
+
+  static String _validateField(String field) {
+    for (var i = 0; i < field.length; i++) {
+      if (!_HttpParser._isTokenChar(field.codeUnitAt(i))) {
+        throw new FormatException(
+            "Invalid HTTP header field name: ${json.encode(field)}");
+      }
+    }
+    return field.toLowerCase();
+  }
+
+  static _validateValue(value) {
+    if (value is! String) return value;
+    for (var i = 0; i < value.length; i++) {
+      if (!_HttpParser._isValueChar(value.codeUnitAt(i))) {
+        throw new FormatException(
+            "Invalid HTTP header field value: ${json.encode(value)}");
+      }
+    }
+    return value;
+  }
+}
+
+class _HeaderValue implements HeaderValue {
+  String _value;
+  Map<String, String> _parameters;
+  Map<String, String> _unmodifiableParameters;
+
+  _HeaderValue([this._value = "", Map<String, String> parameters]) {
+    if (parameters != null) {
+      _parameters = new HashMap<String, String>.from(parameters);
+    }
+  }
+
+  static _HeaderValue parse(String value,
+      {parameterSeparator: ";",
+      valueSeparator: null,
+      preserveBackslash: false}) {
+    // Parse the string.
+    var result = new _HeaderValue();
+    result._parse(value, parameterSeparator, valueSeparator, preserveBackslash);
+    return result;
+  }
+
+  String get value => _value;
+
+  void _ensureParameters() {
+    if (_parameters == null) {
+      _parameters = new HashMap<String, String>();
+    }
+  }
+
+  Map<String, String> get parameters {
+    _ensureParameters();
+    if (_unmodifiableParameters == null) {
+      _unmodifiableParameters = new UnmodifiableMapView(_parameters);
+    }
+    return _unmodifiableParameters;
+  }
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb.write(_value);
+    if (parameters != null && parameters.length > 0) {
+      _parameters.forEach((String name, String value) {
+        sb..write("; ")..write(name)..write("=")..write(value);
+      });
+    }
+    return sb.toString();
+  }
+
+  void _parse(String s, String parameterSeparator, String valueSeparator,
+      bool preserveBackslash) {
+    int index = 0;
+
+    bool done() => index == s.length;
+
+    void skipWS() {
+      while (!done()) {
+        if (s[index] != " " && s[index] != "\t") return;
+        index++;
+      }
+    }
+
+    String parseValue() {
+      int start = index;
+      while (!done()) {
+        if (s[index] == " " ||
+            s[index] == "\t" ||
+            s[index] == valueSeparator ||
+            s[index] == parameterSeparator) break;
+        index++;
+      }
+      return s.substring(start, index);
+    }
+
+    void expect(String expected) {
+      if (done() || s[index] != expected) {
+        throw new HttpException("Failed to parse header value");
+      }
+      index++;
+    }
+
+    void maybeExpect(String expected) {
+      if (s[index] == expected) index++;
+    }
+
+    void parseParameters() {
+      var parameters = new HashMap<String, String>();
+      _parameters = new UnmodifiableMapView(parameters);
+
+      String parseParameterName() {
+        int start = index;
+        while (!done()) {
+          if (s[index] == " " ||
+              s[index] == "\t" ||
+              s[index] == "=" ||
+              s[index] == parameterSeparator ||
+              s[index] == valueSeparator) break;
+          index++;
+        }
+        return s.substring(start, index).toLowerCase();
+      }
+
+      String parseParameterValue() {
+        if (!done() && s[index] == "\"") {
+          // Parse quoted value.
+          StringBuffer sb = new StringBuffer();
+          index++;
+          while (!done()) {
+            if (s[index] == "\\") {
+              if (index + 1 == s.length) {
+                throw new HttpException("Failed to parse header value");
+              }
+              if (preserveBackslash && s[index + 1] != "\"") {
+                sb.write(s[index]);
+              }
+              index++;
+            } else if (s[index] == "\"") {
+              index++;
+              break;
+            }
+            sb.write(s[index]);
+            index++;
+          }
+          return sb.toString();
+        } else {
+          // Parse non-quoted value.
+          var val = parseValue();
+          return val == "" ? null : val;
+        }
+      }
+
+      while (!done()) {
+        skipWS();
+        if (done()) return;
+        String name = parseParameterName();
+        skipWS();
+        if (done()) {
+          parameters[name] = null;
+          return;
+        }
+        maybeExpect("=");
+        skipWS();
+        if (done()) {
+          parameters[name] = null;
+          return;
+        }
+        String value = parseParameterValue();
+        if (name == 'charset' && this is _ContentType && value != null) {
+          // Charset parameter of ContentTypes are always lower-case.
+          value = value.toLowerCase();
+        }
+        parameters[name] = value;
+        skipWS();
+        if (done()) return;
+        // TODO: Implement support for multi-valued parameters.
+        if (s[index] == valueSeparator) return;
+        expect(parameterSeparator);
+      }
+    }
+
+    skipWS();
+    _value = parseValue();
+    skipWS();
+    if (done()) return;
+    maybeExpect(parameterSeparator);
+    parseParameters();
+  }
+}
+
+class _ContentType extends _HeaderValue implements ContentType {
+  String _primaryType = "";
+  String _subType = "";
+
+  _ContentType(String primaryType, String subType, String charset,
+      Map<String, String> parameters)
+      : _primaryType = primaryType,
+        _subType = subType,
+        super("") {
+    if (_primaryType == null) _primaryType = "";
+    if (_subType == null) _subType = "";
+    _value = "$_primaryType/$_subType";
+    if (parameters != null) {
+      _ensureParameters();
+      parameters.forEach((String key, String value) {
+        String lowerCaseKey = key.toLowerCase();
+        if (lowerCaseKey == "charset") {
+          value = value.toLowerCase();
+        }
+        this._parameters[lowerCaseKey] = value;
+      });
+    }
+    if (charset != null) {
+      _ensureParameters();
+      this._parameters["charset"] = charset.toLowerCase();
+    }
+  }
+
+  _ContentType._();
+
+  static _ContentType parse(String value) {
+    var result = new _ContentType._();
+    result._parse(value, ";", null, false);
+    int index = result._value.indexOf("/");
+    if (index == -1 || index == (result._value.length - 1)) {
+      result._primaryType = result._value.trim().toLowerCase();
+      result._subType = "";
+    } else {
+      result._primaryType =
+          result._value.substring(0, index).trim().toLowerCase();
+      result._subType = result._value.substring(index + 1).trim().toLowerCase();
+    }
+    return result;
+  }
+
+  String get mimeType => '$primaryType/$subType';
+
+  String get primaryType => _primaryType;
+
+  String get subType => _subType;
+
+  String get charset => parameters["charset"];
+}
+
+class _Cookie implements Cookie {
+  String name;
+  String value;
+  DateTime expires;
+  int maxAge;
+  String domain;
+  String path;
+  bool httpOnly = false;
+  bool secure = false;
+
+  _Cookie([this.name, this.value]) {
+    // Default value of httponly is true.
+    httpOnly = true;
+    _validate();
+  }
+
+  _Cookie.fromSetCookieValue(String value) {
+    // Parse the 'set-cookie' header value.
+    _parseSetCookieValue(value);
+  }
+
+  // Parse a 'set-cookie' header value according to the rules in RFC 6265.
+  void _parseSetCookieValue(String s) {
+    int index = 0;
+
+    bool done() => index == s.length;
+
+    String parseName() {
+      int start = index;
+      while (!done()) {
+        if (s[index] == "=") break;
+        index++;
+      }
+      return s.substring(start, index).trim();
+    }
+
+    String parseValue() {
+      int start = index;
+      while (!done()) {
+        if (s[index] == ";") break;
+        index++;
+      }
+      return s.substring(start, index).trim();
+    }
+
+    void expect(String expected) {
+      if (done()) throw new HttpException("Failed to parse header value [$s]");
+      if (s[index] != expected) {
+        throw new HttpException("Failed to parse header value [$s]");
+      }
+      index++;
+    }
+
+    void parseAttributes() {
+      String parseAttributeName() {
+        int start = index;
+        while (!done()) {
+          if (s[index] == "=" || s[index] == ";") break;
+          index++;
+        }
+        return s.substring(start, index).trim().toLowerCase();
+      }
+
+      String parseAttributeValue() {
+        int start = index;
+        while (!done()) {
+          if (s[index] == ";") break;
+          index++;
+        }
+        return s.substring(start, index).trim().toLowerCase();
+      }
+
+      while (!done()) {
+        String name = parseAttributeName();
+        String value = "";
+        if (!done() && s[index] == "=") {
+          index++; // Skip the = character.
+          value = parseAttributeValue();
+        }
+        if (name == "expires") {
+          expires = HttpDate._parseCookieDate(value);
+        } else if (name == "max-age") {
+          maxAge = int.parse(value);
+        } else if (name == "domain") {
+          domain = value;
+        } else if (name == "path") {
+          path = value;
+        } else if (name == "httponly") {
+          httpOnly = true;
+        } else if (name == "secure") {
+          secure = true;
+        }
+        if (!done()) index++; // Skip the ; character
+      }
+    }
+
+    name = parseName();
+    if (done() || name.length == 0) {
+      throw new HttpException("Failed to parse header value [$s]");
+    }
+    index++; // Skip the = character.
+    value = parseValue();
+    _validate();
+    if (done()) return;
+    index++; // Skip the ; character.
+    parseAttributes();
+  }
+
+  String toString() {
+    StringBuffer sb = new StringBuffer();
+    sb..write(name)..write("=")..write(value);
+    if (expires != null) {
+      sb..write("; Expires=")..write(HttpDate.format(expires));
+    }
+    if (maxAge != null) {
+      sb..write("; Max-Age=")..write(maxAge);
+    }
+    if (domain != null) {
+      sb..write("; Domain=")..write(domain);
+    }
+    if (path != null) {
+      sb..write("; Path=")..write(path);
+    }
+    if (secure) sb.write("; Secure");
+    if (httpOnly) sb.write("; HttpOnly");
+    return sb.toString();
+  }
+
+  void _validate() {
+    const SEPERATORS = const [
+      "(",
+      ")",
+      "<",
+      ">",
+      "@",
+      ",",
+      ";",
+      ":",
+      "\\",
+      '"',
+      "/",
+      "[",
+      "]",
+      "?",
+      "=",
+      "{",
+      "}"
+    ];
+    for (int i = 0; i < name.length; i++) {
+      int codeUnit = name.codeUnits[i];
+      if (codeUnit <= 32 ||
+          codeUnit >= 127 ||
+          SEPERATORS.indexOf(name[i]) >= 0) {
+        throw new FormatException(
+            "Invalid character in cookie name, code unit: '$codeUnit'");
+      }
+    }
+    for (int i = 0; i < value.length; i++) {
+      int codeUnit = value.codeUnits[i];
+      if (!(codeUnit == 0x21 ||
+          (codeUnit >= 0x23 && codeUnit <= 0x2B) ||
+          (codeUnit >= 0x2D && codeUnit <= 0x3A) ||
+          (codeUnit >= 0x3C && codeUnit <= 0x5B) ||
+          (codeUnit >= 0x5D && codeUnit <= 0x7E))) {
+        throw new FormatException(
+            "Invalid character in cookie value, code unit: '$codeUnit'");
+      }
+    }
+  }
+}
diff --git a/lib/src/http_impl.dart b/lib/src/http_impl.dart
new file mode 100644
index 0000000..d7b7628
--- /dev/null
+++ b/lib/src/http_impl.dart
@@ -0,0 +1,3137 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http_io;
+
+int _nextServiceId = 1;
+
+// TODO(ajohnsen): Use other way of getting a unique id.
+abstract class _ServiceObject {
+  int __serviceId = 0;
+  int get _serviceId {
+    if (__serviceId == 0) __serviceId = _nextServiceId++;
+    return __serviceId;
+  }
+
+  Map _toJSON(bool ref);
+
+  String get _servicePath => "$_serviceTypePath/$_serviceId";
+
+  String get _serviceTypePath;
+
+  String get _serviceTypeName;
+
+  String _serviceType(bool ref) {
+    if (ref) return "@$_serviceTypeName";
+    return _serviceTypeName;
+  }
+}
+
+class _CopyingBytesBuilder implements BytesBuilder {
+  // Start with 1024 bytes.
+  static const int _INIT_SIZE = 1024;
+
+  static final _emptyList = new Uint8List(0);
+
+  int _length = 0;
+  Uint8List _buffer;
+
+  _CopyingBytesBuilder([int initialCapacity = 0])
+      : _buffer = (initialCapacity <= 0)
+            ? _emptyList
+            : new Uint8List(_pow2roundup(initialCapacity));
+
+  void add(List<int> bytes) {
+    int bytesLength = bytes.length;
+    if (bytesLength == 0) return;
+    int required = _length + bytesLength;
+    if (_buffer.length < required) {
+      _grow(required);
+    }
+    assert(_buffer.length >= required);
+    if (bytes is Uint8List) {
+      _buffer.setRange(_length, required, bytes);
+    } else {
+      for (int i = 0; i < bytesLength; i++) {
+        _buffer[_length + i] = bytes[i];
+      }
+    }
+    _length = required;
+  }
+
+  void addByte(int byte) {
+    if (_buffer.length == _length) {
+      // The grow algorithm always at least doubles.
+      // If we added one to _length it would quadruple unnecessarily.
+      _grow(_length);
+    }
+    assert(_buffer.length > _length);
+    _buffer[_length] = byte;
+    _length++;
+  }
+
+  void _grow(int required) {
+    // We will create a list in the range of 2-4 times larger than
+    // required.
+    int newSize = required * 2;
+    if (newSize < _INIT_SIZE) {
+      newSize = _INIT_SIZE;
+    } else {
+      newSize = _pow2roundup(newSize);
+    }
+    var newBuffer = new Uint8List(newSize);
+    newBuffer.setRange(0, _buffer.length, _buffer);
+    _buffer = newBuffer;
+  }
+
+  List<int> takeBytes() {
+    if (_length == 0) return _emptyList;
+    var buffer = new Uint8List.view(_buffer.buffer, 0, _length);
+    clear();
+    return buffer;
+  }
+
+  List<int> toBytes() {
+    if (_length == 0) return _emptyList;
+    return new Uint8List.fromList(
+        new Uint8List.view(_buffer.buffer, 0, _length));
+  }
+
+  int get length => _length;
+
+  bool get isEmpty => _length == 0;
+
+  bool get isNotEmpty => _length != 0;
+
+  void clear() {
+    _length = 0;
+    _buffer = _emptyList;
+  }
+
+  static int _pow2roundup(int x) {
+    assert(x > 0);
+    --x;
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    return x + 1;
+  }
+}
+
+const int _OUTGOING_BUFFER_SIZE = 8 * 1024;
+
+typedef void _BytesConsumer(List<int> bytes);
+
+class _HttpIncoming extends Stream<List<int>> {
+  final int _transferLength;
+  final Completer _dataCompleter = new Completer();
+  Stream<List<int>> _stream;
+
+  bool fullBodyRead = false;
+
+  // Common properties.
+  final _HttpHeaders headers;
+  bool upgraded = false;
+
+  // ClientResponse properties.
+  int statusCode;
+  String reasonPhrase;
+
+  // Request properties.
+  String method;
+  Uri uri;
+
+  bool hasSubscriber = false;
+
+  // The transfer length if the length of the message body as it
+  // appears in the message (RFC 2616 section 4.4). This can be -1 if
+  // the length of the massage body is not known due to transfer
+  // codings.
+  int get transferLength => _transferLength;
+
+  _HttpIncoming(this.headers, this._transferLength, this._stream);
+
+  StreamSubscription<List<int>> listen(void onData(List<int> event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    hasSubscriber = true;
+    return _stream.handleError((error) {
+      throw new HttpException(error.message, uri: uri);
+    }).listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  // Is completed once all data have been received.
+  Future get dataDone => _dataCompleter.future;
+
+  void close(bool closing) {
+    fullBodyRead = true;
+    hasSubscriber = true;
+    _dataCompleter.complete(closing);
+  }
+}
+
+abstract class _HttpInboundMessage extends Stream<List<int>> {
+  final _HttpIncoming _incoming;
+  List<Cookie> _cookies;
+
+  _HttpInboundMessage(this._incoming);
+
+  List<Cookie> get cookies {
+    if (_cookies != null) return _cookies;
+    return _cookies = headers._parseCookies();
+  }
+
+  _HttpHeaders get headers => _incoming.headers;
+  String get protocolVersion => headers.protocolVersion;
+  int get contentLength => headers.contentLength;
+  bool get persistentConnection => headers.persistentConnection;
+}
+
+class _HttpRequest extends _HttpInboundMessage implements HttpRequest {
+  final HttpResponse response;
+
+  final _HttpServer _httpServer;
+
+  final _HttpConnection _httpConnection;
+
+  _HttpSession _session;
+
+  Uri _requestedUri;
+
+  _HttpRequest(this.response, _HttpIncoming _incoming, this._httpServer,
+      this._httpConnection)
+      : super(_incoming) {
+    if (headers.protocolVersion == "1.1") {
+      response.headers
+        ..chunkedTransferEncoding = true
+        ..persistentConnection = headers.persistentConnection;
+    }
+
+    if (_httpServer._sessionManagerInstance != null) {
+      // Map to session if exists.
+      var sessionIds = cookies
+          .where((cookie) => cookie.name.toUpperCase() == _DART_SESSION_ID)
+          .map((cookie) => cookie.value);
+      for (var sessionId in sessionIds) {
+        _session = _httpServer._sessionManager.getSession(sessionId);
+        if (_session != null) {
+          _session._markSeen();
+          break;
+        }
+      }
+    }
+  }
+
+  StreamSubscription<List<int>> listen(void onData(List<int> event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    return _incoming.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  Uri get uri => _incoming.uri;
+
+  Uri get requestedUri {
+    if (_requestedUri == null) {
+      var proto = headers['x-forwarded-proto'];
+      var scheme = proto != null
+          ? proto.first
+          : _httpConnection._socket is SecureSocket ? "https" : "http";
+      var hostList = headers['x-forwarded-host'];
+      String host;
+      if (hostList != null) {
+        host = hostList.first;
+      } else {
+        hostList = headers['host'];
+        if (hostList != null) {
+          host = hostList.first;
+        } else {
+          host = "${_httpServer.address.host}:${_httpServer.port}";
+        }
+      }
+      _requestedUri = Uri.parse("$scheme://$host$uri");
+    }
+    return _requestedUri;
+  }
+
+  String get method => _incoming.method;
+
+  HttpSession get session {
+    if (_session != null) {
+      if (_session._destroyed) {
+        // It's destroyed, clear it.
+        _session = null;
+        // Create new session object by calling recursive.
+        return session;
+      }
+      // It's already mapped, use it.
+      return _session;
+    }
+    // Create session, store it in connection, and return.
+    return _session = _httpServer._sessionManager.createSession();
+  }
+
+  HttpConnectionInfo get connectionInfo => _httpConnection.connectionInfo;
+
+  X509Certificate get certificate {
+    var socket = _httpConnection._socket;
+    if (socket is SecureSocket) return socket.peerCertificate;
+    return null;
+  }
+}
+
+class _HttpClientResponse extends _HttpInboundMessage
+    implements HttpClientResponse {
+  List<RedirectInfo> get redirects => _httpRequest._responseRedirects;
+
+  // The HttpClient this response belongs to.
+  final _HttpClient _httpClient;
+
+  // The HttpClientRequest of this response.
+  final _HttpClientRequest _httpRequest;
+
+  _HttpClientResponse(
+      _HttpIncoming _incoming, this._httpRequest, this._httpClient)
+      : super(_incoming) {
+    // Set uri for potential exceptions.
+    _incoming.uri = _httpRequest.uri;
+  }
+
+  int get statusCode => _incoming.statusCode;
+  String get reasonPhrase => _incoming.reasonPhrase;
+
+  X509Certificate get certificate {
+    var socket = _httpRequest._httpClientConnection._socket;
+    if (socket is SecureSocket) return socket.peerCertificate;
+    throw new UnsupportedError("Socket is not a SecureSocket");
+  }
+
+  List<Cookie> get cookies {
+    if (_cookies != null) return _cookies;
+    _cookies = new List<Cookie>();
+    List<String> values = headers[HttpHeaders.SET_COOKIE];
+    if (values != null) {
+      values.forEach((value) {
+        _cookies.add(new Cookie.fromSetCookieValue(value));
+      });
+    }
+    return _cookies;
+  }
+
+  bool get isRedirect {
+    if (_httpRequest.method == "GET" || _httpRequest.method == "HEAD") {
+      return statusCode == HttpStatus.MOVED_PERMANENTLY ||
+          statusCode == HttpStatus.FOUND ||
+          statusCode == HttpStatus.SEE_OTHER ||
+          statusCode == HttpStatus.TEMPORARY_REDIRECT;
+    } else if (_httpRequest.method == "POST") {
+      return statusCode == HttpStatus.SEE_OTHER;
+    }
+    return false;
+  }
+
+  Future<HttpClientResponse> redirect(
+      [String method, Uri url, bool followLoops]) {
+    if (method == null) {
+      // Set method as defined by RFC 2616 section 10.3.4.
+      if (statusCode == HttpStatus.SEE_OTHER && _httpRequest.method == "POST") {
+        method = "GET";
+      } else {
+        method = _httpRequest.method;
+      }
+    }
+    if (url == null) {
+      String location = headers.value(HttpHeaders.LOCATION);
+      if (location == null) {
+        throw new StateError("Response has no Location header for redirect");
+      }
+      url = Uri.parse(location);
+    }
+    if (followLoops != true) {
+      for (var redirect in redirects) {
+        if (redirect.location == url) {
+          return new Future.error(
+              new RedirectException("Redirect loop detected", redirects));
+        }
+      }
+    }
+    return _httpClient
+        ._openUrlFromRequest(method, url, _httpRequest)
+        .then((request) {
+      request._responseRedirects
+        ..addAll(this.redirects)
+        ..add(new _RedirectInfo(statusCode, method, url));
+      return request.close();
+    });
+  }
+
+  StreamSubscription<List<int>> listen(void onData(List<int> event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    if (_incoming.upgraded) {
+      // If upgraded, the connection is already 'removed' form the client.
+      // Since listening to upgraded data is 'bogus', simply close and
+      // return empty stream subscription.
+      _httpRequest._httpClientConnection.destroy();
+      return new Stream<List<int>>.empty().listen(null, onDone: onDone);
+    }
+    var stream = _incoming;
+    if (_httpClient.autoUncompress &&
+        headers.value(HttpHeaders.CONTENT_ENCODING) == "gzip") {
+      stream = stream.transform(GZIP.decoder);
+    }
+    return stream.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  Future<Socket> detachSocket() {
+    _httpClient._connectionClosed(_httpRequest._httpClientConnection);
+    return _httpRequest._httpClientConnection.detachSocket();
+  }
+
+  HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo;
+
+  bool get _shouldAuthenticateProxy {
+    // Only try to authenticate if there is a challenge in the response.
+    List<String> challenge = headers[HttpHeaders.PROXY_AUTHENTICATE];
+    return statusCode == HttpStatus.PROXY_AUTHENTICATION_REQUIRED &&
+        challenge != null &&
+        challenge.length == 1;
+  }
+
+  bool get _shouldAuthenticate {
+    // Only try to authenticate if there is a challenge in the response.
+    List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE];
+    return statusCode == HttpStatus.UNAUTHORIZED &&
+        challenge != null &&
+        challenge.length == 1;
+  }
+
+  Future<HttpClientResponse> _authenticate(bool proxyAuth) {
+    Future<HttpClientResponse> retry() {
+      // Drain body and retry.
+      return drain().then((_) {
+        return _httpClient
+            ._openUrlFromRequest(
+                _httpRequest.method, _httpRequest.uri, _httpRequest)
+            .then((request) => request.close());
+      });
+    }
+
+    List<String> authChallenge() {
+      return proxyAuth
+          ? headers[HttpHeaders.PROXY_AUTHENTICATE]
+          : headers[HttpHeaders.WWW_AUTHENTICATE];
+    }
+
+    _Credentials findCredentials(_AuthenticationScheme scheme) {
+      return proxyAuth
+          ? _httpClient._findProxyCredentials(_httpRequest._proxy, scheme)
+          : _httpClient._findCredentials(_httpRequest.uri, scheme);
+    }
+
+    void removeCredentials(_Credentials cr) {
+      if (proxyAuth) {
+        _httpClient._removeProxyCredentials(cr);
+      } else {
+        _httpClient._removeCredentials(cr);
+      }
+    }
+
+    Future requestAuthentication(_AuthenticationScheme scheme, String realm) {
+      if (proxyAuth) {
+        if (_httpClient._authenticateProxy == null) {
+          return new Future.value(false);
+        }
+        var proxy = _httpRequest._proxy;
+        return _httpClient._authenticateProxy(
+            proxy.host, proxy.port, scheme.toString(), realm);
+      } else {
+        if (_httpClient._authenticate == null) {
+          return new Future.value(false);
+        }
+        return _httpClient._authenticate(
+            _httpRequest.uri, scheme.toString(), realm);
+      }
+    }
+
+    List<String> challenge = authChallenge();
+    assert(challenge != null || challenge.length == 1);
+    _HeaderValue header =
+        _HeaderValue.parse(challenge[0], parameterSeparator: ",");
+    _AuthenticationScheme scheme =
+        new _AuthenticationScheme.fromString(header.value);
+    String realm = header.parameters["realm"];
+
+    // See if any matching credentials are available.
+    _Credentials cr = findCredentials(scheme);
+    if (cr != null) {
+      // For basic authentication don't retry already used credentials
+      // as they must have already been added to the request causing
+      // this authenticate response.
+      if (cr.scheme == _AuthenticationScheme.BASIC && !cr.used) {
+        // Credentials where found, prepare for retrying the request.
+        return retry();
+      }
+
+      // Digest authentication only supports the MD5 algorithm.
+      if (cr.scheme == _AuthenticationScheme.DIGEST &&
+          (header.parameters["algorithm"] == null ||
+              header.parameters["algorithm"].toLowerCase() == "md5")) {
+        if (cr.nonce == null || cr.nonce == header.parameters["nonce"]) {
+          // If the nonce is not set then this is the first authenticate
+          // response for these credentials. Set up authentication state.
+          if (cr.nonce == null) {
+            cr
+              ..nonce = header.parameters["nonce"]
+              ..algorithm = "MD5"
+              ..qop = header.parameters["qop"]
+              ..nonceCount = 0;
+          }
+          // Credentials where found, prepare for retrying the request.
+          return retry();
+        } else if (header.parameters["stale"] != null &&
+            header.parameters["stale"].toLowerCase() == "true") {
+          // If stale is true retry with new nonce.
+          cr.nonce = header.parameters["nonce"];
+          // Credentials where found, prepare for retrying the request.
+          return retry();
+        }
+      }
+    }
+
+    // Ask for more credentials if none found or the one found has
+    // already been used. If it has already been used it must now be
+    // invalid and is removed.
+    if (cr != null) {
+      removeCredentials(cr);
+      cr = null;
+    }
+    return requestAuthentication(scheme, realm).then((credsAvailable) {
+      if (credsAvailable) {
+        cr = _httpClient._findCredentials(_httpRequest.uri, scheme);
+        return retry();
+      } else {
+        // No credentials available, complete with original response.
+        return this;
+      }
+    });
+  }
+}
+
+class _StreamSinkImpl<T> implements StreamSink<T> {
+  final StreamConsumer<T> _target;
+  final Completer _doneCompleter = new Completer();
+  StreamController<T> _controllerInstance;
+  Completer _controllerCompleter;
+  bool _isClosed = false;
+  bool _isBound = false;
+  bool _hasError = false;
+
+  _StreamSinkImpl(this._target);
+
+  void _reportClosedSink() {
+    stderr.writeln("StreamSink is closed and adding to it is an error.");
+    stderr.writeln("  See http://dartbug.com/29554.");
+    stderr.writeln(StackTrace.current);
+  }
+
+  void add(T data) {
+    if (_isClosed) {
+      _reportClosedSink();
+      return;
+    }
+    _controller.add(data);
+  }
+
+  void addError(error, [StackTrace stackTrace]) {
+    if (_isClosed) {
+      _reportClosedSink();
+      return;
+    }
+    _controller.addError(error, stackTrace);
+  }
+
+  Future addStream(Stream<T> stream) {
+    if (_isBound) {
+      throw new StateError("StreamSink is already bound to a stream");
+    }
+    _isBound = true;
+    if (_hasError) return done;
+    // Wait for any sync operations to complete.
+    Future targetAddStream() {
+      return _target.addStream(stream).whenComplete(() {
+        _isBound = false;
+      });
+    }
+
+    if (_controllerInstance == null) return targetAddStream();
+    var future = _controllerCompleter.future;
+    _controllerInstance.close();
+    return future.then((_) => targetAddStream());
+  }
+
+  Future flush() {
+    if (_isBound) {
+      throw new StateError("StreamSink is bound to a stream");
+    }
+    if (_controllerInstance == null) return new Future.value(this);
+    // Adding an empty stream-controller will return a future that will complete
+    // when all data is done.
+    _isBound = true;
+    var future = _controllerCompleter.future;
+    _controllerInstance.close();
+    return future.whenComplete(() {
+      _isBound = false;
+    });
+  }
+
+  Future close() {
+    if (_isBound) {
+      throw new StateError("StreamSink is bound to a stream");
+    }
+    if (!_isClosed) {
+      _isClosed = true;
+      if (_controllerInstance != null) {
+        _controllerInstance.close();
+      } else {
+        _closeTarget();
+      }
+    }
+    return done;
+  }
+
+  void _closeTarget() {
+    _target.close().then(_completeDoneValue, onError: _completeDoneError);
+  }
+
+  Future get done => _doneCompleter.future;
+
+  void _completeDoneValue(value) {
+    if (!_doneCompleter.isCompleted) {
+      _doneCompleter.complete(value);
+    }
+  }
+
+  void _completeDoneError(error, StackTrace stackTrace) {
+    if (!_doneCompleter.isCompleted) {
+      _hasError = true;
+      _doneCompleter.completeError(error, stackTrace);
+    }
+  }
+
+  StreamController<T> get _controller {
+    if (_isBound) {
+      throw new StateError("StreamSink is bound to a stream");
+    }
+    if (_isClosed) {
+      throw new StateError("StreamSink is closed");
+    }
+    if (_controllerInstance == null) {
+      _controllerInstance = new StreamController<T>(sync: true);
+      _controllerCompleter = new Completer();
+      _target.addStream(_controller.stream).then((_) {
+        if (_isBound) {
+          // A new stream takes over - forward values to that stream.
+          _controllerCompleter.complete(this);
+          _controllerCompleter = null;
+          _controllerInstance = null;
+        } else {
+          // No new stream, .close was called. Close _target.
+          _closeTarget();
+        }
+      }, onError: (error, stackTrace) {
+        if (_isBound) {
+          // A new stream takes over - forward errors to that stream.
+          _controllerCompleter.completeError(error, stackTrace);
+          _controllerCompleter = null;
+          _controllerInstance = null;
+        } else {
+          // No new stream. No need to close target, as it has already
+          // failed.
+          _completeDoneError(error, stackTrace);
+        }
+      });
+    }
+    return _controllerInstance;
+  }
+}
+
+class _IOSinkImpl extends _StreamSinkImpl<List<int>> implements IOSink {
+  Encoding _encoding;
+  bool _encodingMutable = true;
+
+  _IOSinkImpl(StreamConsumer<List<int>> target, this._encoding) : super(target);
+
+  Encoding get encoding => _encoding;
+
+  void set encoding(Encoding value) {
+    if (!_encodingMutable) {
+      throw new StateError("IOSink encoding is not mutable");
+    }
+    _encoding = value;
+  }
+
+  void write(Object obj) {
+    String string = '$obj';
+    if (string.isEmpty) return;
+    add(_encoding.encode(string));
+  }
+
+  void writeAll(Iterable objects, [String separator = ""]) {
+    Iterator iterator = objects.iterator;
+    if (!iterator.moveNext()) return;
+    if (separator.isEmpty) {
+      do {
+        write(iterator.current);
+      } while (iterator.moveNext());
+    } else {
+      write(iterator.current);
+      while (iterator.moveNext()) {
+        write(separator);
+        write(iterator.current);
+      }
+    }
+  }
+
+  void writeln([Object object = ""]) {
+    write(object);
+    write("\n");
+  }
+
+  void writeCharCode(int charCode) {
+    write(new String.fromCharCode(charCode));
+  }
+}
+
+abstract class _HttpOutboundMessage<T> extends _IOSinkImpl {
+  // Used to mark when the body should be written. This is used for HEAD
+  // requests and in error handling.
+  bool _encodingSet = false;
+
+  bool _bufferOutput = true;
+
+  final Uri _uri;
+  final _HttpOutgoing _outgoing;
+
+  final _HttpHeaders headers;
+
+  _HttpOutboundMessage(Uri uri, String protocolVersion, _HttpOutgoing outgoing,
+      {_HttpHeaders initialHeaders})
+      : _uri = uri,
+        headers = new _HttpHeaders(protocolVersion,
+            defaultPortForScheme: uri.scheme == 'https'
+                ? HttpClient.DEFAULT_HTTPS_PORT
+                : HttpClient.DEFAULT_HTTP_PORT,
+            initialHeaders: initialHeaders),
+        _outgoing = outgoing,
+        super(outgoing, null) {
+    _outgoing.outbound = this;
+    _encodingMutable = false;
+  }
+
+  int get contentLength => headers.contentLength;
+  void set contentLength(int contentLength) {
+    headers.contentLength = contentLength;
+  }
+
+  bool get persistentConnection => headers.persistentConnection;
+  void set persistentConnection(bool p) {
+    headers.persistentConnection = p;
+  }
+
+  bool get bufferOutput => _bufferOutput;
+  void set bufferOutput(bool bufferOutput) {
+    if (_outgoing.headersWritten) throw new StateError("Header already sent");
+    _bufferOutput = bufferOutput;
+  }
+
+  Encoding get encoding {
+    if (_encodingSet && _outgoing.headersWritten) {
+      return _encoding;
+    }
+    var charset;
+    if (headers.contentType != null && headers.contentType.charset != null) {
+      charset = headers.contentType.charset;
+    } else {
+      charset = "iso-8859-1";
+    }
+    return Encoding.getByName(charset);
+  }
+
+  void add(List<int> data) {
+    if (data.length == 0) return;
+    super.add(data);
+  }
+
+  void write(Object obj) {
+    if (!_encodingSet) {
+      _encoding = encoding;
+      _encodingSet = true;
+    }
+    super.write(obj);
+  }
+
+  void _writeHeader();
+
+  bool get _isConnectionClosed => false;
+}
+
+class _HttpResponse extends _HttpOutboundMessage<HttpResponse>
+    implements HttpResponse {
+  int _statusCode = 200;
+  String _reasonPhrase;
+  List<Cookie> _cookies;
+  _HttpRequest _httpRequest;
+  Duration _deadline;
+  Timer _deadlineTimer;
+
+  _HttpResponse(Uri uri, String protocolVersion, _HttpOutgoing outgoing,
+      HttpHeaders defaultHeaders, String serverHeader)
+      : super(uri, protocolVersion, outgoing, initialHeaders: defaultHeaders) {
+    if (serverHeader != null) headers.set('server', serverHeader);
+  }
+
+  bool get _isConnectionClosed => _httpRequest._httpConnection._isClosing;
+
+  List<Cookie> get cookies {
+    if (_cookies == null) _cookies = new List<Cookie>();
+    return _cookies;
+  }
+
+  int get statusCode => _statusCode;
+  void set statusCode(int statusCode) {
+    if (_outgoing.headersWritten) throw new StateError("Header already sent");
+    _statusCode = statusCode;
+  }
+
+  String get reasonPhrase => _findReasonPhrase(statusCode);
+  void set reasonPhrase(String reasonPhrase) {
+    if (_outgoing.headersWritten) throw new StateError("Header already sent");
+    _reasonPhrase = reasonPhrase;
+  }
+
+  Future redirect(Uri location, {int status: HttpStatus.MOVED_TEMPORARILY}) {
+    if (_outgoing.headersWritten) throw new StateError("Header already sent");
+    statusCode = status;
+    headers.set("location", location.toString());
+    return close();
+  }
+
+  Future<Socket> detachSocket({bool writeHeaders: true}) {
+    if (_outgoing.headersWritten) throw new StateError("Headers already sent");
+    deadline = null; // Be sure to stop any deadline.
+    var future = _httpRequest._httpConnection.detachSocket();
+    if (writeHeaders) {
+      var headersFuture =
+          _outgoing.writeHeaders(drainRequest: false, setOutgoing: false);
+      assert(headersFuture == null);
+    } else {
+      // Imitate having written the headers.
+      _outgoing.headersWritten = true;
+    }
+    // Close connection so the socket is 'free'.
+    close();
+    done.catchError((_) {
+      // Catch any error on done, as they automatically will be
+      // propagated to the websocket.
+    });
+    return future;
+  }
+
+  HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo;
+
+  Duration get deadline => _deadline;
+
+  void set deadline(Duration d) {
+    if (_deadlineTimer != null) _deadlineTimer.cancel();
+    _deadline = d;
+
+    if (_deadline == null) return;
+    _deadlineTimer = new Timer(_deadline, () {
+      _httpRequest._httpConnection.destroy();
+    });
+  }
+
+  void _writeHeader() {
+    BytesBuilder buffer = new _CopyingBytesBuilder(_OUTGOING_BUFFER_SIZE);
+
+    // Write status line.
+    if (headers.protocolVersion == "1.1") {
+      buffer.add(_Const.HTTP11);
+    } else {
+      buffer.add(_Const.HTTP10);
+    }
+    buffer.addByte(_CharCode.SP);
+    buffer.add(statusCode.toString().codeUnits);
+    buffer.addByte(_CharCode.SP);
+    buffer.add(reasonPhrase.codeUnits);
+    buffer.addByte(_CharCode.CR);
+    buffer.addByte(_CharCode.LF);
+
+    var session = _httpRequest._session;
+    if (session != null && !session._destroyed) {
+      // Mark as not new.
+      session._isNew = false;
+      // Make sure we only send the current session id.
+      bool found = false;
+      for (int i = 0; i < cookies.length; i++) {
+        if (cookies[i].name.toUpperCase() == _DART_SESSION_ID) {
+          cookies[i]
+            ..value = session.id
+            ..httpOnly = true
+            ..path = "/";
+          found = true;
+        }
+      }
+      if (!found) {
+        var cookie = new Cookie(_DART_SESSION_ID, session.id);
+        cookies.add(cookie
+          ..httpOnly = true
+          ..path = "/");
+      }
+    }
+    // Add all the cookies set to the headers.
+    if (_cookies != null) {
+      _cookies.forEach((cookie) {
+        headers.add(HttpHeaders.SET_COOKIE, cookie);
+      });
+    }
+
+    headers._finalize();
+
+    // Write headers.
+    headers._build(buffer);
+    buffer.addByte(_CharCode.CR);
+    buffer.addByte(_CharCode.LF);
+    Uint8List headerBytes = buffer.takeBytes();
+    _outgoing.setHeader(headerBytes, headerBytes.length);
+  }
+
+  String _findReasonPhrase(int statusCode) {
+    if (_reasonPhrase != null) {
+      return _reasonPhrase;
+    }
+
+    switch (statusCode) {
+      case HttpStatus.CONTINUE:
+        return "Continue";
+      case HttpStatus.SWITCHING_PROTOCOLS:
+        return "Switching Protocols";
+      case HttpStatus.OK:
+        return "OK";
+      case HttpStatus.CREATED:
+        return "Created";
+      case HttpStatus.ACCEPTED:
+        return "Accepted";
+      case HttpStatus.NON_AUTHORITATIVE_INFORMATION:
+        return "Non-Authoritative Information";
+      case HttpStatus.NO_CONTENT:
+        return "No Content";
+      case HttpStatus.RESET_CONTENT:
+        return "Reset Content";
+      case HttpStatus.PARTIAL_CONTENT:
+        return "Partial Content";
+      case HttpStatus.MULTIPLE_CHOICES:
+        return "Multiple Choices";
+      case HttpStatus.MOVED_PERMANENTLY:
+        return "Moved Permanently";
+      case HttpStatus.FOUND:
+        return "Found";
+      case HttpStatus.SEE_OTHER:
+        return "See Other";
+      case HttpStatus.NOT_MODIFIED:
+        return "Not Modified";
+      case HttpStatus.USE_PROXY:
+        return "Use Proxy";
+      case HttpStatus.TEMPORARY_REDIRECT:
+        return "Temporary Redirect";
+      case HttpStatus.BAD_REQUEST:
+        return "Bad Request";
+      case HttpStatus.UNAUTHORIZED:
+        return "Unauthorized";
+      case HttpStatus.PAYMENT_REQUIRED:
+        return "Payment Required";
+      case HttpStatus.FORBIDDEN:
+        return "Forbidden";
+      case HttpStatus.NOT_FOUND:
+        return "Not Found";
+      case HttpStatus.METHOD_NOT_ALLOWED:
+        return "Method Not Allowed";
+      case HttpStatus.NOT_ACCEPTABLE:
+        return "Not Acceptable";
+      case HttpStatus.PROXY_AUTHENTICATION_REQUIRED:
+        return "Proxy Authentication Required";
+      case HttpStatus.REQUEST_TIMEOUT:
+        return "Request Time-out";
+      case HttpStatus.CONFLICT:
+        return "Conflict";
+      case HttpStatus.GONE:
+        return "Gone";
+      case HttpStatus.LENGTH_REQUIRED:
+        return "Length Required";
+      case HttpStatus.PRECONDITION_FAILED:
+        return "Precondition Failed";
+      case HttpStatus.REQUEST_ENTITY_TOO_LARGE:
+        return "Request Entity Too Large";
+      case HttpStatus.REQUEST_URI_TOO_LONG:
+        return "Request-URI Too Large";
+      case HttpStatus.UNSUPPORTED_MEDIA_TYPE:
+        return "Unsupported Media Type";
+      case HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE:
+        return "Requested range not satisfiable";
+      case HttpStatus.EXPECTATION_FAILED:
+        return "Expectation Failed";
+      case HttpStatus.INTERNAL_SERVER_ERROR:
+        return "Internal Server Error";
+      case HttpStatus.NOT_IMPLEMENTED:
+        return "Not Implemented";
+      case HttpStatus.BAD_GATEWAY:
+        return "Bad Gateway";
+      case HttpStatus.SERVICE_UNAVAILABLE:
+        return "Service Unavailable";
+      case HttpStatus.GATEWAY_TIMEOUT:
+        return "Gateway Time-out";
+      case HttpStatus.HTTP_VERSION_NOT_SUPPORTED:
+        return "Http Version not supported";
+      default:
+        return "Status $statusCode";
+    }
+  }
+}
+
+class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse>
+    implements HttpClientRequest {
+  final String method;
+  final Uri uri;
+  final List<Cookie> cookies = new List<Cookie>();
+
+  // The HttpClient this request belongs to.
+  final _HttpClient _httpClient;
+  final _HttpClientConnection _httpClientConnection;
+
+  final Completer<HttpClientResponse> _responseCompleter =
+      new Completer<HttpClientResponse>();
+
+  final _Proxy _proxy;
+
+  Future<HttpClientResponse> _response;
+
+  // TODO(ajohnsen): Get default value from client?
+  bool _followRedirects = true;
+
+  int _maxRedirects = 5;
+
+  List<RedirectInfo> _responseRedirects = [];
+
+  _HttpClientRequest(_HttpOutgoing outgoing, Uri uri, this.method, this._proxy,
+      this._httpClient, this._httpClientConnection)
+      : uri = uri,
+        super(uri, "1.1", outgoing) {
+    // GET and HEAD have 'content-length: 0' by default.
+    if (method == "GET" || method == "HEAD") {
+      contentLength = 0;
+    } else {
+      headers.chunkedTransferEncoding = true;
+    }
+  }
+
+  Future<HttpClientResponse> get done {
+    if (_response == null) {
+      _response = Future.wait([_responseCompleter.future, super.done],
+          eagerError: true).then((list) => list[0]);
+    }
+    return _response;
+  }
+
+  Future<HttpClientResponse> close() {
+    super.close();
+    return done;
+  }
+
+  int get maxRedirects => _maxRedirects;
+  void set maxRedirects(int maxRedirects) {
+    if (_outgoing.headersWritten) throw new StateError("Request already sent");
+    _maxRedirects = maxRedirects;
+  }
+
+  bool get followRedirects => _followRedirects;
+  void set followRedirects(bool followRedirects) {
+    if (_outgoing.headersWritten) throw new StateError("Request already sent");
+    _followRedirects = followRedirects;
+  }
+
+  HttpConnectionInfo get connectionInfo => _httpClientConnection.connectionInfo;
+
+  void _onIncoming(_HttpIncoming incoming) {
+    var response = new _HttpClientResponse(incoming, this, _httpClient);
+    Future<HttpClientResponse> future;
+    if (followRedirects && response.isRedirect) {
+      if (response.redirects.length < maxRedirects) {
+        // Redirect and drain response.
+        future = response
+            .drain()
+            .then<HttpClientResponse>((_) => response.redirect());
+      } else {
+        // End with exception, too many redirects.
+        future = response.drain().then<HttpClientResponse>((_) {
+          return new Future<HttpClientResponse>.error(new RedirectException(
+              "Redirect limit exceeded", response.redirects));
+        });
+      }
+    } else if (response._shouldAuthenticateProxy) {
+      future = response._authenticate(true);
+    } else if (response._shouldAuthenticate) {
+      future = response._authenticate(false);
+    } else {
+      future = new Future<HttpClientResponse>.value(response);
+    }
+    future.then((v) => _responseCompleter.complete(v),
+        onError: _responseCompleter.completeError);
+  }
+
+  void _onError(error, StackTrace stackTrace) {
+    _responseCompleter.completeError(error, stackTrace);
+  }
+
+  // Generate the request URI based on the method and proxy.
+  String _requestUri() {
+    // Generate the request URI starting from the path component.
+    String uriStartingFromPath() {
+      String result = uri.path;
+      if (result.isEmpty) result = "/";
+      if (uri.hasQuery) {
+        result = "${result}?${uri.query}";
+      }
+      return result;
+    }
+
+    if (_proxy.isDirect) {
+      return uriStartingFromPath();
+    } else {
+      if (method == "CONNECT") {
+        // For the connect method the request URI is the host:port of
+        // the requested destination of the tunnel (see RFC 2817
+        // section 5.2)
+        return "${uri.host}:${uri.port}";
+      } else {
+        if (_httpClientConnection._proxyTunnel) {
+          return uriStartingFromPath();
+        } else {
+          return uri.removeFragment().toString();
+        }
+      }
+    }
+  }
+
+  void _writeHeader() {
+    BytesBuilder buffer = new _CopyingBytesBuilder(_OUTGOING_BUFFER_SIZE);
+
+    // Write the request method.
+    buffer.add(method.codeUnits);
+    buffer.addByte(_CharCode.SP);
+    // Write the request URI.
+    buffer.add(_requestUri().codeUnits);
+    buffer.addByte(_CharCode.SP);
+    // Write HTTP/1.1.
+    buffer.add(_Const.HTTP11);
+    buffer.addByte(_CharCode.CR);
+    buffer.addByte(_CharCode.LF);
+
+    // Add the cookies to the headers.
+    if (!cookies.isEmpty) {
+      StringBuffer sb = new StringBuffer();
+      for (int i = 0; i < cookies.length; i++) {
+        if (i > 0) sb.write("; ");
+        sb..write(cookies[i].name)..write("=")..write(cookies[i].value);
+      }
+      headers.add(HttpHeaders.COOKIE, sb.toString());
+    }
+
+    headers._finalize();
+
+    // Write headers.
+    headers._build(buffer);
+    buffer.addByte(_CharCode.CR);
+    buffer.addByte(_CharCode.LF);
+    Uint8List headerBytes = buffer.takeBytes();
+    _outgoing.setHeader(headerBytes, headerBytes.length);
+  }
+}
+
+// Used by _HttpOutgoing as a target of a chunked converter for gzip
+// compression.
+class _HttpGZipSink extends ByteConversionSink {
+  final _BytesConsumer _consume;
+  _HttpGZipSink(this._consume);
+
+  void add(List<int> chunk) {
+    _consume(chunk);
+  }
+
+  void addSlice(List<int> chunk, int start, int end, bool isLast) {
+    if (chunk is Uint8List) {
+      _consume(new Uint8List.view(chunk.buffer, start, end - start));
+    } else {
+      _consume(chunk.sublist(start, end - start));
+    }
+  }
+
+  void close() {}
+}
+
+// The _HttpOutgoing handles all of the following:
+//  - Buffering
+//  - GZip compression
+//  - Content-Length validation.
+//  - Errors.
+//
+// Most notable is the GZip compression, that uses a double-buffering system,
+// one before gzip (_gzipBuffer) and one after (_buffer).
+class _HttpOutgoing implements StreamConsumer<List<int>> {
+  static const List<int> _footerAndChunk0Length = const [
+    _CharCode.CR,
+    _CharCode.LF,
+    0x30,
+    _CharCode.CR,
+    _CharCode.LF,
+    _CharCode.CR,
+    _CharCode.LF
+  ];
+
+  static const List<int> _chunk0Length = const [
+    0x30,
+    _CharCode.CR,
+    _CharCode.LF,
+    _CharCode.CR,
+    _CharCode.LF
+  ];
+
+  final Completer<Socket> _doneCompleter = new Completer<Socket>();
+  final Socket socket;
+
+  bool ignoreBody = false;
+  bool headersWritten = false;
+
+  Uint8List _buffer;
+  int _length = 0;
+
+  Future _closeFuture;
+
+  bool chunked = false;
+  int _pendingChunkedFooter = 0;
+
+  int contentLength;
+  int _bytesWritten = 0;
+
+  bool _gzip = false;
+  ByteConversionSink _gzipSink;
+  // _gzipAdd is set iff the sink is being added to. It's used to specify where
+  // gzipped data should be taken (sometimes a controller, sometimes a socket).
+  _BytesConsumer _gzipAdd;
+  Uint8List _gzipBuffer;
+  int _gzipBufferLength = 0;
+
+  bool _socketError = false;
+
+  _HttpOutboundMessage outbound;
+
+  _HttpOutgoing(this.socket);
+
+  // Returns either a future or 'null', if it was able to write headers
+  // immediately.
+  Future writeHeaders({bool drainRequest: true, bool setOutgoing: true}) {
+    if (headersWritten) return null;
+    headersWritten = true;
+    Future drainFuture;
+    bool gzip = false;
+    if (outbound is _HttpResponse) {
+      // Server side.
+      _HttpResponse response = outbound;
+      if (response._httpRequest._httpServer.autoCompress &&
+          outbound.bufferOutput &&
+          outbound.headers.chunkedTransferEncoding) {
+        List acceptEncodings =
+            response._httpRequest.headers[HttpHeaders.ACCEPT_ENCODING];
+        List contentEncoding = outbound.headers[HttpHeaders.CONTENT_ENCODING];
+        if (acceptEncodings != null &&
+            acceptEncodings
+                .expand((list) => list.split(","))
+                .any((encoding) => encoding.trim().toLowerCase() == "gzip") &&
+            contentEncoding == null) {
+          outbound.headers.set(HttpHeaders.CONTENT_ENCODING, "gzip");
+          gzip = true;
+        }
+      }
+      if (drainRequest && !response._httpRequest._incoming.hasSubscriber) {
+        drainFuture = response._httpRequest.drain().catchError((_) {});
+      }
+    } else {
+      drainRequest = false;
+    }
+    if (!ignoreBody) {
+      if (setOutgoing) {
+        int contentLength = outbound.headers.contentLength;
+        if (outbound.headers.chunkedTransferEncoding) {
+          chunked = true;
+          if (gzip) this.gzip = true;
+        } else if (contentLength >= 0) {
+          this.contentLength = contentLength;
+        }
+      }
+      if (drainFuture != null) {
+        return drainFuture.then((_) => outbound._writeHeader());
+      }
+    }
+    outbound._writeHeader();
+    return null;
+  }
+
+  Future addStream(Stream<List<int>> stream) {
+    if (_socketError) {
+      stream.listen(null).cancel();
+      return new Future.value(outbound);
+    }
+    if (ignoreBody) {
+      stream.drain().catchError((_) {});
+      var future = writeHeaders();
+      if (future != null) {
+        return future.then((_) => close());
+      }
+      return close();
+    }
+    StreamSubscription<List<int>> sub;
+    // Use new stream so we are able to pause (see below listen). The
+    // alternative is to use stream.extand, but that won't give us a way of
+    // pausing.
+    var controller = new StreamController<List<int>>(
+        onPause: () => sub.pause(), onResume: () => sub.resume(), sync: true);
+
+    void onData(List<int> data) {
+      if (_socketError) return;
+      if (data.length == 0) return;
+      if (chunked) {
+        if (_gzip) {
+          _gzipAdd = controller.add;
+          _addGZipChunk(data, _gzipSink.add);
+          _gzipAdd = null;
+          return;
+        }
+        _addChunk(_chunkHeader(data.length), controller.add);
+        _pendingChunkedFooter = 2;
+      } else {
+        if (contentLength != null) {
+          _bytesWritten += data.length;
+          if (_bytesWritten > contentLength) {
+            controller.addError(new HttpException(
+                "Content size exceeds specified contentLength. "
+                "$_bytesWritten bytes written while expected "
+                "$contentLength. "
+                "[${new String.fromCharCodes(data)}]"));
+            return;
+          }
+        }
+      }
+      _addChunk(data, controller.add);
+    }
+
+    sub = stream.listen(onData,
+        onError: controller.addError,
+        onDone: controller.close,
+        cancelOnError: true);
+    // Write headers now that we are listening to the stream.
+    if (!headersWritten) {
+      var future = writeHeaders();
+      if (future != null) {
+        // While incoming is being drained, the pauseFuture is non-null. Pause
+        // output until it's drained.
+        sub.pause(future);
+      }
+    }
+    return socket.addStream(controller.stream).then((_) {
+      return outbound;
+    }, onError: (error, stackTrace) {
+      // Be sure to close it in case of an error.
+      if (_gzip) _gzipSink.close();
+      _socketError = true;
+      _doneCompleter.completeError(error, stackTrace);
+      if (_ignoreError(error)) {
+        return outbound;
+      } else {
+        throw error;
+      }
+    });
+  }
+
+  Future close() {
+    // If we are already closed, return that future.
+    if (_closeFuture != null) return _closeFuture;
+    // If we earlier saw an error, return immediate. The notification to
+    // _Http*Connection is already done.
+    if (_socketError) return new Future.value(outbound);
+    if (outbound._isConnectionClosed) return new Future.value(outbound);
+    if (!headersWritten && !ignoreBody) {
+      if (outbound.headers.contentLength == -1) {
+        // If no body was written, ignoreBody is false (it's not a HEAD
+        // request) and the content-length is unspecified, set contentLength to
+        // 0.
+        outbound.headers.chunkedTransferEncoding = false;
+        outbound.headers.contentLength = 0;
+      } else if (outbound.headers.contentLength > 0) {
+        var error = new HttpException(
+            "No content even though contentLength was specified to be "
+            "greater than 0: ${outbound.headers.contentLength}.",
+            uri: outbound._uri);
+        _doneCompleter.completeError(error);
+        return _closeFuture = new Future.error(error);
+      }
+    }
+    // If contentLength was specified, validate it.
+    if (contentLength != null) {
+      if (_bytesWritten < contentLength) {
+        var error = new HttpException(
+            "Content size below specified contentLength. "
+            " $_bytesWritten bytes written but expected "
+            "$contentLength.",
+            uri: outbound._uri);
+        _doneCompleter.completeError(error);
+        return _closeFuture = new Future.error(error);
+      }
+    }
+
+    Future finalize() {
+      // In case of chunked encoding (and gzip), handle remaining gzip data and
+      // append the 'footer' for chunked encoding.
+      if (chunked) {
+        if (_gzip) {
+          _gzipAdd = socket.add;
+          if (_gzipBufferLength > 0) {
+            _gzipSink.add(
+                new Uint8List.view(_gzipBuffer.buffer, 0, _gzipBufferLength));
+          }
+          _gzipBuffer = null;
+          _gzipSink.close();
+          _gzipAdd = null;
+        }
+        _addChunk(_chunkHeader(0), socket.add);
+      }
+      // Add any remaining data in the buffer.
+      if (_length > 0) {
+        socket.add(new Uint8List.view(_buffer.buffer, 0, _length));
+      }
+      // Clear references, for better GC.
+      _buffer = null;
+      // And finally flush it. As we support keep-alive, never close it from
+      // here. Once the socket is flushed, we'll be able to reuse it (signaled
+      // by the 'done' future).
+      return socket.flush().then((_) {
+        _doneCompleter.complete(socket);
+        return outbound;
+      }, onError: (error, stackTrace) {
+        _doneCompleter.completeError(error, stackTrace);
+        if (_ignoreError(error)) {
+          return outbound;
+        } else {
+          throw error;
+        }
+      });
+    }
+
+    var future = writeHeaders();
+    if (future != null) {
+      return _closeFuture = future.whenComplete(finalize);
+    }
+    return _closeFuture = finalize();
+  }
+
+  Future<Socket> get done => _doneCompleter.future;
+
+  void setHeader(List<int> data, int length) {
+    assert(_length == 0);
+    _buffer = data;
+    _length = length;
+  }
+
+  void set gzip(bool value) {
+    _gzip = value;
+    if (_gzip) {
+      _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
+      assert(_gzipSink == null);
+      _gzipSink = new ZLibEncoder(gzip: true)
+          .startChunkedConversion(new _HttpGZipSink((data) {
+        // We are closing down prematurely, due to an error. Discard.
+        if (_gzipAdd == null) return;
+        _addChunk(_chunkHeader(data.length), _gzipAdd);
+        _pendingChunkedFooter = 2;
+        _addChunk(data, _gzipAdd);
+      }));
+    }
+  }
+
+  bool _ignoreError(error) =>
+      (error is SocketException || error is TlsException) &&
+      outbound is HttpResponse;
+
+  void _addGZipChunk(List<int> chunk, void add(List<int> data)) {
+    if (!outbound.bufferOutput) {
+      add(chunk);
+      return;
+    }
+    if (chunk.length > _gzipBuffer.length - _gzipBufferLength) {
+      add(new Uint8List.view(_gzipBuffer.buffer, 0, _gzipBufferLength));
+      _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
+      _gzipBufferLength = 0;
+    }
+    if (chunk.length > _OUTGOING_BUFFER_SIZE) {
+      add(chunk);
+    } else {
+      _gzipBuffer.setRange(
+          _gzipBufferLength, _gzipBufferLength + chunk.length, chunk);
+      _gzipBufferLength += chunk.length;
+    }
+  }
+
+  void _addChunk(List<int> chunk, void add(List<int> data)) {
+    if (!outbound.bufferOutput) {
+      if (_buffer != null) {
+        // If _buffer is not null, we have not written the header yet. Write
+        // it now.
+        add(new Uint8List.view(_buffer.buffer, 0, _length));
+        _buffer = null;
+        _length = 0;
+      }
+      add(chunk);
+      return;
+    }
+    if (chunk.length > _buffer.length - _length) {
+      add(new Uint8List.view(_buffer.buffer, 0, _length));
+      _buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
+      _length = 0;
+    }
+    if (chunk.length > _OUTGOING_BUFFER_SIZE) {
+      add(chunk);
+    } else {
+      _buffer.setRange(_length, _length + chunk.length, chunk);
+      _length += chunk.length;
+    }
+  }
+
+  List<int> _chunkHeader(int length) {
+    const hexDigits = const [
+      0x30,
+      0x31,
+      0x32,
+      0x33,
+      0x34,
+      0x35,
+      0x36,
+      0x37,
+      0x38,
+      0x39,
+      0x41,
+      0x42,
+      0x43,
+      0x44,
+      0x45,
+      0x46
+    ];
+    if (length == 0) {
+      if (_pendingChunkedFooter == 2) return _footerAndChunk0Length;
+      return _chunk0Length;
+    }
+    int size = _pendingChunkedFooter;
+    int len = length;
+    // Compute a fast integer version of (log(length + 1) / log(16)).ceil().
+    while (len > 0) {
+      size++;
+      len >>= 4;
+    }
+    var footerAndHeader = new Uint8List(size + 2);
+    if (_pendingChunkedFooter == 2) {
+      footerAndHeader[0] = _CharCode.CR;
+      footerAndHeader[1] = _CharCode.LF;
+    }
+    int index = size;
+    while (index > _pendingChunkedFooter) {
+      footerAndHeader[--index] = hexDigits[length & 15];
+      length = length >> 4;
+    }
+    footerAndHeader[size + 0] = _CharCode.CR;
+    footerAndHeader[size + 1] = _CharCode.LF;
+    return footerAndHeader;
+  }
+}
+
+class _HttpClientConnection {
+  final String key;
+  final Socket _socket;
+  final bool _proxyTunnel;
+  final SecurityContext _context;
+  final _HttpParser _httpParser;
+  StreamSubscription _subscription;
+  final _HttpClient _httpClient;
+  bool _dispose = false;
+  Timer _idleTimer;
+  bool closed = false;
+  Uri _currentUri;
+
+  Completer<_HttpIncoming> _nextResponseCompleter;
+  Future<Socket> _streamFuture;
+
+  _HttpClientConnection(this.key, this._socket, this._httpClient,
+      [this._proxyTunnel = false, this._context])
+      : _httpParser = new _HttpParser.responseParser() {
+    _httpParser.listenToStream(_socket);
+
+    // Set up handlers on the parser here, so we are sure to get 'onDone' from
+    // the parser.
+    _subscription = _httpParser.listen((incoming) {
+      // Only handle one incoming response at the time. Keep the
+      // stream paused until the response have been processed.
+      _subscription.pause();
+      // We assume the response is not here, until we have send the request.
+      if (_nextResponseCompleter == null) {
+        throw new HttpException(
+            "Unexpected response (unsolicited response without request).",
+            uri: _currentUri);
+      }
+
+      // Check for status code '100 Continue'. In that case just
+      // consume that response as the final response will follow
+      // it. There is currently no API for the client to wait for
+      // the '100 Continue' response.
+      if (incoming.statusCode == 100) {
+        incoming.drain().then((_) {
+          _subscription.resume();
+        }).catchError((error, [StackTrace stackTrace]) {
+          _nextResponseCompleter.completeError(
+              new HttpException(error.message, uri: _currentUri), stackTrace);
+          _nextResponseCompleter = null;
+        });
+      } else {
+        _nextResponseCompleter.complete(incoming);
+        _nextResponseCompleter = null;
+      }
+    }, onError: (error, [StackTrace stackTrace]) {
+      if (_nextResponseCompleter != null) {
+        _nextResponseCompleter.completeError(
+            new HttpException(error.message, uri: _currentUri), stackTrace);
+        _nextResponseCompleter = null;
+      }
+    }, onDone: () {
+      if (_nextResponseCompleter != null) {
+        _nextResponseCompleter.completeError(new HttpException(
+            "Connection closed before response was received",
+            uri: _currentUri));
+        _nextResponseCompleter = null;
+      }
+      close();
+    });
+  }
+
+  _HttpClientRequest send(Uri uri, int port, String method, _Proxy proxy) {
+    if (closed) {
+      throw new HttpException("Socket closed before request was sent",
+          uri: uri);
+    }
+    _currentUri = uri;
+    // Start with pausing the parser.
+    _subscription.pause();
+    _ProxyCredentials proxyCreds; // Credentials used to authorize proxy.
+    _SiteCredentials creds; // Credentials used to authorize this request.
+    var outgoing = new _HttpOutgoing(_socket);
+    // Create new request object, wrapping the outgoing connection.
+    var request =
+        new _HttpClientRequest(outgoing, uri, method, proxy, _httpClient, this);
+    // For the Host header an IPv6 address must be enclosed in []'s.
+    var host = uri.host;
+    if (host.contains(':')) host = "[$host]";
+    request.headers
+      ..host = host
+      ..port = port
+      .._add(HttpHeaders.ACCEPT_ENCODING, "gzip");
+    if (_httpClient.userAgent != null) {
+      request.headers._add('user-agent', _httpClient.userAgent);
+    }
+    if (proxy.isAuthenticated) {
+      // If the proxy configuration contains user information use that
+      // for proxy basic authorization.
+      String auth = _CryptoUtils
+          .bytesToBase64(utf8.encode("${proxy.username}:${proxy.password}"));
+      request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic $auth");
+    } else if (!proxy.isDirect && _httpClient._proxyCredentials.length > 0) {
+      proxyCreds = _httpClient._findProxyCredentials(proxy);
+      if (proxyCreds != null) {
+        proxyCreds.authorize(request);
+      }
+    }
+    if (uri.userInfo != null && !uri.userInfo.isEmpty) {
+      // If the URL contains user information use that for basic
+      // authorization.
+      String auth = _CryptoUtils.bytesToBase64(utf8.encode(uri.userInfo));
+      request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth");
+    } else {
+      // Look for credentials.
+      creds = _httpClient._findCredentials(uri);
+      if (creds != null) {
+        creds.authorize(request);
+      }
+    }
+    // Start sending the request (lazy, delayed until the user provides
+    // data).
+    _httpParser.isHead = method == "HEAD";
+    _streamFuture = outgoing.done.then<Socket>((Socket s) {
+      // Request sent, set up response completer.
+      _nextResponseCompleter = new Completer<_HttpIncoming>();
+
+      // Listen for response.
+      _nextResponseCompleter.future.then((incoming) {
+        _currentUri = null;
+        incoming.dataDone.then((closing) {
+          if (incoming.upgraded) {
+            _httpClient._connectionClosed(this);
+            startTimer();
+            return;
+          }
+          if (closed) return;
+          if (!closing &&
+              !_dispose &&
+              incoming.headers.persistentConnection &&
+              request.persistentConnection) {
+            // Return connection, now we are done.
+            _httpClient._returnConnection(this);
+            _subscription.resume();
+          } else {
+            destroy();
+          }
+        });
+        // For digest authentication if proxy check if the proxy
+        // requests the client to start using a new nonce for proxy
+        // authentication.
+        if (proxyCreds != null &&
+            proxyCreds.scheme == _AuthenticationScheme.DIGEST) {
+          var authInfo = incoming.headers["proxy-authentication-info"];
+          if (authInfo != null && authInfo.length == 1) {
+            var header =
+                _HeaderValue.parse(authInfo[0], parameterSeparator: ',');
+            var nextnonce = header.parameters["nextnonce"];
+            if (nextnonce != null) proxyCreds.nonce = nextnonce;
+          }
+        }
+        // For digest authentication check if the server requests the
+        // client to start using a new nonce.
+        if (creds != null && creds.scheme == _AuthenticationScheme.DIGEST) {
+          var authInfo = incoming.headers["authentication-info"];
+          if (authInfo != null && authInfo.length == 1) {
+            var header =
+                _HeaderValue.parse(authInfo[0], parameterSeparator: ',');
+            var nextnonce = header.parameters["nextnonce"];
+            if (nextnonce != null) creds.nonce = nextnonce;
+          }
+        }
+        request._onIncoming(incoming);
+      })
+          // If we see a state error, we failed to get the 'first'
+          // element.
+          .catchError((error) {
+        throw new HttpException("Connection closed before data was received",
+            uri: uri);
+      }, test: (error) => error is StateError).catchError((error, stackTrace) {
+        // We are done with the socket.
+        destroy();
+        request._onError(error, stackTrace);
+      });
+
+      // Resume the parser now we have a handler.
+      _subscription.resume();
+      return s;
+    }, onError: (e) {
+      destroy();
+    });
+    return request;
+  }
+
+  Future<Socket> detachSocket() {
+    return _streamFuture.then(
+        (_) => new _DetachedSocket(_socket, _httpParser.detachIncoming()));
+  }
+
+  void destroy() {
+    closed = true;
+    _httpClient._connectionClosed(this);
+    _socket.destroy();
+  }
+
+  void close() {
+    closed = true;
+    _httpClient._connectionClosed(this);
+    _streamFuture
+        // TODO(ajohnsen): Add timeout.
+        .then((_) => _socket.destroy());
+  }
+
+  Future<_HttpClientConnection> createProxyTunnel(String host, int port,
+      _Proxy proxy, bool callback(X509Certificate certificate)) {
+    _HttpClientRequest request =
+        send(new Uri(host: host, port: port), port, "CONNECT", proxy);
+    if (proxy.isAuthenticated) {
+      // If the proxy configuration contains user information use that
+      // for proxy basic authorization.
+      String auth = _CryptoUtils
+          .bytesToBase64(utf8.encode("${proxy.username}:${proxy.password}"));
+      request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic $auth");
+    }
+    return request.close().then((response) {
+      if (response.statusCode != HttpStatus.OK) {
+        throw "Proxy failed to establish tunnel "
+            "(${response.statusCode} ${response.reasonPhrase})";
+      }
+      var socket = (response as _HttpClientResponse)
+          ._httpRequest
+          ._httpClientConnection
+          ._socket;
+      return SecureSocket.secure(socket,
+          host: host, context: _context, onBadCertificate: callback);
+    }).then((secureSocket) {
+      String key = _HttpClientConnection.makeKey(true, host, port);
+      return new _HttpClientConnection(
+          key, secureSocket, request._httpClient, true);
+    });
+  }
+
+  HttpConnectionInfo get connectionInfo => _HttpConnectionInfo.create(_socket);
+
+  static makeKey(bool isSecure, String host, int port) {
+    return isSecure ? "ssh:$host:$port" : "$host:$port";
+  }
+
+  void stopTimer() {
+    if (_idleTimer != null) {
+      _idleTimer.cancel();
+      _idleTimer = null;
+    }
+  }
+
+  void startTimer() {
+    assert(_idleTimer == null);
+    _idleTimer = new Timer(_httpClient.idleTimeout, () {
+      _idleTimer = null;
+      close();
+    });
+  }
+}
+
+class _ConnectionInfo {
+  final _HttpClientConnection connection;
+  final _Proxy proxy;
+
+  _ConnectionInfo(this.connection, this.proxy);
+}
+
+class _ConnectionTarget {
+  // Unique key for this connection target.
+  final String key;
+  final String host;
+  final int port;
+  final bool isSecure;
+  final SecurityContext context;
+  final Set<_HttpClientConnection> _idle = new HashSet();
+  final Set<_HttpClientConnection> _active = new HashSet();
+  final Queue _pending = new ListQueue();
+  int _connecting = 0;
+
+  _ConnectionTarget(
+      this.key, this.host, this.port, this.isSecure, this.context);
+
+  bool get isEmpty => _idle.isEmpty && _active.isEmpty && _connecting == 0;
+
+  bool get hasIdle => _idle.isNotEmpty;
+
+  bool get hasActive => _active.isNotEmpty || _connecting > 0;
+
+  _HttpClientConnection takeIdle() {
+    assert(hasIdle);
+    _HttpClientConnection connection = _idle.first;
+    _idle.remove(connection);
+    connection.stopTimer();
+    _active.add(connection);
+    return connection;
+  }
+
+  _checkPending() {
+    if (_pending.isNotEmpty) {
+      _pending.removeFirst()();
+    }
+  }
+
+  void addNewActive(_HttpClientConnection connection) {
+    _active.add(connection);
+  }
+
+  void returnConnection(_HttpClientConnection connection) {
+    assert(_active.contains(connection));
+    _active.remove(connection);
+    _idle.add(connection);
+    connection.startTimer();
+    _checkPending();
+  }
+
+  void connectionClosed(_HttpClientConnection connection) {
+    assert(!_active.contains(connection) || !_idle.contains(connection));
+    _active.remove(connection);
+    _idle.remove(connection);
+    _checkPending();
+  }
+
+  void close(bool force) {
+    for (var c in _idle.toList()) {
+      c.close();
+    }
+    if (force) {
+      for (var c in _active.toList()) {
+        c.destroy();
+      }
+    }
+  }
+
+  Future<_ConnectionInfo> connect(
+      String uriHost, int uriPort, _Proxy proxy, _HttpClient client) {
+    if (hasIdle) {
+      var connection = takeIdle();
+      client._connectionsChanged();
+      return new Future.value(new _ConnectionInfo(connection, proxy));
+    }
+    if (client.maxConnectionsPerHost != null &&
+        _active.length + _connecting >= client.maxConnectionsPerHost) {
+      var completer = new Completer<_ConnectionInfo>();
+      _pending.add(() {
+        completer.complete(connect(uriHost, uriPort, proxy, client));
+      });
+      return completer.future;
+    }
+    var currentBadCertificateCallback = client._badCertificateCallback;
+
+    bool callback(X509Certificate certificate) {
+      if (currentBadCertificateCallback == null) return false;
+      return currentBadCertificateCallback(certificate, uriHost, uriPort);
+    }
+
+    Future socketFuture = (isSecure && proxy.isDirect
+        ? SecureSocket.connect(host, port,
+            context: context, onBadCertificate: callback)
+        : Socket.connect(host, port));
+    _connecting++;
+    return socketFuture.then((socket) {
+      _connecting--;
+      socket.setOption(SocketOption.TCP_NODELAY, true);
+      var connection =
+          new _HttpClientConnection(key, socket, client, false, context);
+      if (isSecure && !proxy.isDirect) {
+        connection._dispose = true;
+        return connection
+            .createProxyTunnel(uriHost, uriPort, proxy, callback)
+            .then((tunnel) {
+          client
+              ._getConnectionTarget(uriHost, uriPort, true)
+              .addNewActive(tunnel);
+          return new _ConnectionInfo(tunnel, proxy);
+        });
+      } else {
+        addNewActive(connection);
+        return new _ConnectionInfo(connection, proxy);
+      }
+    }, onError: (error) {
+      _connecting--;
+      _checkPending();
+      throw error;
+    });
+  }
+}
+
+typedef bool BadCertificateCallback(X509Certificate cr, String host, int port);
+
+class _HttpClient implements HttpClient {
+  bool _closing = false;
+  bool _closingForcefully = false;
+  final Map<String, _ConnectionTarget> _connectionTargets =
+      new HashMap<String, _ConnectionTarget>();
+  final List<_Credentials> _credentials = [];
+  final List<_ProxyCredentials> _proxyCredentials = [];
+  final SecurityContext _context;
+  Function _authenticate;
+  Function _authenticateProxy;
+  Function _findProxy = HttpClient.findProxyFromEnvironment;
+  Duration _idleTimeout = const Duration(seconds: 15);
+  BadCertificateCallback _badCertificateCallback;
+
+  Duration get idleTimeout => _idleTimeout;
+
+  int maxConnectionsPerHost;
+
+  bool autoUncompress = true;
+
+  String userAgent = _getHttpVersion();
+
+  _HttpClient(this._context);
+
+  void set idleTimeout(Duration timeout) {
+    _idleTimeout = timeout;
+    for (var c in _connectionTargets.values) {
+      for (var idle in c._idle) {
+        // Reset timer. This is fine, as it's not happening often.
+        idle.stopTimer();
+        idle.startTimer();
+      }
+    }
+  }
+
+  set badCertificateCallback(
+      bool callback(X509Certificate cert, String host, int port)) {
+    _badCertificateCallback = callback;
+  }
+
+  Future<HttpClientRequest> open(
+      String method, String host, int port, String path) {
+    const int hashMark = 0x23;
+    const int questionMark = 0x3f;
+    int fragmentStart = path.length;
+    int queryStart = path.length;
+    for (int i = path.length - 1; i >= 0; i--) {
+      var char = path.codeUnitAt(i);
+      if (char == hashMark) {
+        fragmentStart = i;
+        queryStart = i;
+      } else if (char == questionMark) {
+        queryStart = i;
+      }
+    }
+    String query = null;
+    if (queryStart < fragmentStart) {
+      query = path.substring(queryStart + 1, fragmentStart);
+      path = path.substring(0, queryStart);
+    }
+    Uri uri = new Uri(
+        scheme: "http", host: host, port: port, path: path, query: query);
+    return _openUrl(method, uri);
+  }
+
+  Future<HttpClientRequest> openUrl(String method, Uri url) =>
+      _openUrl(method, url);
+
+  Future<HttpClientRequest> get(String host, int port, String path) =>
+      open("get", host, port, path);
+
+  Future<HttpClientRequest> getUrl(Uri url) => _openUrl("get", url);
+
+  Future<HttpClientRequest> post(String host, int port, String path) =>
+      open("post", host, port, path);
+
+  Future<HttpClientRequest> postUrl(Uri url) => _openUrl("post", url);
+
+  Future<HttpClientRequest> put(String host, int port, String path) =>
+      open("put", host, port, path);
+
+  Future<HttpClientRequest> putUrl(Uri url) => _openUrl("put", url);
+
+  Future<HttpClientRequest> delete(String host, int port, String path) =>
+      open("delete", host, port, path);
+
+  Future<HttpClientRequest> deleteUrl(Uri url) => _openUrl("delete", url);
+
+  Future<HttpClientRequest> head(String host, int port, String path) =>
+      open("head", host, port, path);
+
+  Future<HttpClientRequest> headUrl(Uri url) => _openUrl("head", url);
+
+  Future<HttpClientRequest> patch(String host, int port, String path) =>
+      open("patch", host, port, path);
+
+  Future<HttpClientRequest> patchUrl(Uri url) => _openUrl("patch", url);
+
+  void close({bool force: false}) {
+    _closing = true;
+    _closingForcefully = force;
+    _closeConnections(_closingForcefully);
+    assert(!_connectionTargets.values.any((s) => s.hasIdle));
+    assert(
+        !force || !_connectionTargets.values.any((s) => s._active.isNotEmpty));
+  }
+
+  set authenticate(Future<bool> f(Uri url, String scheme, String realm)) {
+    _authenticate = f;
+  }
+
+  void addCredentials(Uri url, String realm, HttpClientCredentials cr) {
+    _credentials.add(new _SiteCredentials(url, realm, cr));
+  }
+
+  set authenticateProxy(
+      Future<bool> f(String host, int port, String scheme, String realm)) {
+    _authenticateProxy = f;
+  }
+
+  void addProxyCredentials(
+      String host, int port, String realm, HttpClientCredentials cr) {
+    _proxyCredentials.add(new _ProxyCredentials(host, port, realm, cr));
+  }
+
+  set findProxy(String f(Uri uri)) => _findProxy = f;
+
+  Future<_HttpClientRequest> _openUrl(String method, Uri uri) {
+    // Ignore any fragments on the request URI.
+    uri = uri.removeFragment();
+
+    if (method == null) {
+      throw new ArgumentError(method);
+    }
+    if (method != "CONNECT") {
+      if (uri.host.isEmpty) {
+        throw new ArgumentError("No host specified in URI $uri");
+      } else if (uri.scheme != "http" && uri.scheme != "https") {
+        throw new ArgumentError(
+            "Unsupported scheme '${uri.scheme}' in URI $uri");
+      }
+    }
+
+    bool isSecure = (uri.scheme == "https");
+    int port = uri.port;
+    if (port == 0) {
+      port = isSecure
+          ? HttpClient.DEFAULT_HTTPS_PORT
+          : HttpClient.DEFAULT_HTTP_PORT;
+    }
+    // Check to see if a proxy server should be used for this connection.
+    var proxyConf = const _ProxyConfiguration.direct();
+    if (_findProxy != null) {
+      // TODO(sgjesse): Keep a map of these as normally only a few
+      // configuration strings will be used.
+      try {
+        proxyConf = new _ProxyConfiguration(_findProxy(uri));
+      } catch (error, stackTrace) {
+        return new Future.error(error, stackTrace);
+      }
+    }
+    return _getConnection(uri.host, port, proxyConf, isSecure)
+        .then((_ConnectionInfo info) {
+      _HttpClientRequest send(_ConnectionInfo info) {
+        return info.connection
+            .send(uri, port, method.toUpperCase(), info.proxy);
+      }
+
+      // If the connection was closed before the request was sent, create
+      // and use another connection.
+      if (info.connection.closed) {
+        return _getConnection(uri.host, port, proxyConf, isSecure).then(send);
+      }
+      return send(info);
+    });
+  }
+
+  Future<_HttpClientRequest> _openUrlFromRequest(
+      String method, Uri uri, _HttpClientRequest previous) {
+    // If the new URI is relative (to either '/' or some sub-path),
+    // construct a full URI from the previous one.
+    Uri resolved = previous.uri.resolveUri(uri);
+    return _openUrl(method, resolved).then((_HttpClientRequest request) {
+      request
+        // Only follow redirects if initial request did.
+        ..followRedirects = previous.followRedirects
+        // Allow same number of redirects.
+        ..maxRedirects = previous.maxRedirects;
+      // Copy headers.
+      for (var header in previous.headers._headers.keys) {
+        if (request.headers[header] == null) {
+          request.headers.set(header, previous.headers[header]);
+        }
+      }
+      return request
+        ..headers.chunkedTransferEncoding = false
+        ..contentLength = 0;
+    });
+  }
+
+  // Return a live connection to the idle pool.
+  void _returnConnection(_HttpClientConnection connection) {
+    _connectionTargets[connection.key].returnConnection(connection);
+    _connectionsChanged();
+  }
+
+  // Remove a closed connection from the active set.
+  void _connectionClosed(_HttpClientConnection connection) {
+    connection.stopTimer();
+    var connectionTarget = _connectionTargets[connection.key];
+    if (connectionTarget != null) {
+      connectionTarget.connectionClosed(connection);
+      if (connectionTarget.isEmpty) {
+        _connectionTargets.remove(connection.key);
+      }
+      _connectionsChanged();
+    }
+  }
+
+  void _connectionsChanged() {
+    if (_closing) {
+      _closeConnections(_closingForcefully);
+    }
+  }
+
+  void _closeConnections(bool force) {
+    for (var connectionTarget in _connectionTargets.values.toList()) {
+      connectionTarget.close(force);
+    }
+  }
+
+  _ConnectionTarget _getConnectionTarget(String host, int port, bool isSecure) {
+    String key = _HttpClientConnection.makeKey(isSecure, host, port);
+    return _connectionTargets.putIfAbsent(key, () {
+      return new _ConnectionTarget(key, host, port, isSecure, _context);
+    });
+  }
+
+  // Get a new _HttpClientConnection, from the matching _ConnectionTarget.
+  Future<_ConnectionInfo> _getConnection(String uriHost, int uriPort,
+      _ProxyConfiguration proxyConf, bool isSecure) {
+    Iterator<_Proxy> proxies = proxyConf.proxies.iterator;
+
+    Future<_ConnectionInfo> connect(error) {
+      if (!proxies.moveNext()) return new Future.error(error);
+      _Proxy proxy = proxies.current;
+      String host = proxy.isDirect ? uriHost : proxy.host;
+      int port = proxy.isDirect ? uriPort : proxy.port;
+      return _getConnectionTarget(host, port, isSecure)
+          .connect(uriHost, uriPort, proxy, this)
+          // On error, continue with next proxy.
+          .catchError(connect);
+    }
+
+    // Make sure we go through the event loop before taking a
+    // connection from the pool. For long-running synchronous code the
+    // server might have closed the connection, so this lowers the
+    // probability of getting a connection that was already closed.
+    return new Future<_ConnectionInfo>(
+        () => connect(new HttpException("No proxies given")));
+  }
+
+  _SiteCredentials _findCredentials(Uri url, [_AuthenticationScheme scheme]) {
+    // Look for credentials.
+    _SiteCredentials cr =
+        _credentials.fold(null, (_SiteCredentials prev, value) {
+      var siteCredentials = value as _SiteCredentials;
+      if (siteCredentials.applies(url, scheme)) {
+        if (prev == null) return value;
+        return siteCredentials.uri.path.length > prev.uri.path.length
+            ? siteCredentials
+            : prev;
+      } else {
+        return prev;
+      }
+    });
+    return cr;
+  }
+
+  _ProxyCredentials _findProxyCredentials(_Proxy proxy,
+      [_AuthenticationScheme scheme]) {
+    // Look for credentials.
+    var it = _proxyCredentials.iterator;
+    while (it.moveNext()) {
+      if (it.current.applies(proxy, scheme)) {
+        return it.current;
+      }
+    }
+    return null;
+  }
+
+  void _removeCredentials(_Credentials cr) {
+    int index = _credentials.indexOf(cr);
+    if (index != -1) {
+      _credentials.removeAt(index);
+    }
+  }
+
+  void _removeProxyCredentials(_Credentials cr) {
+    int index = _proxyCredentials.indexOf(cr);
+    if (index != -1) {
+      _proxyCredentials.removeAt(index);
+    }
+  }
+
+  static String _findProxyFromEnvironment(
+      Uri url, Map<String, String> environment) {
+    checkNoProxy(String option) {
+      if (option == null) return null;
+      Iterator<String> names = option.split(",").map((s) => s.trim()).iterator;
+      while (names.moveNext()) {
+        var name = names.current;
+        if ((name.startsWith("[") &&
+                name.endsWith("]") &&
+                "[${url.host}]" == name) ||
+            (name.isNotEmpty && url.host.endsWith(name))) {
+          return "DIRECT";
+        }
+      }
+      return null;
+    }
+
+    checkProxy(String option) {
+      if (option == null) return null;
+      option = option.trim();
+      if (option.isEmpty) return null;
+      int pos = option.indexOf("://");
+      if (pos >= 0) {
+        option = option.substring(pos + 3);
+      }
+      pos = option.indexOf("/");
+      if (pos >= 0) {
+        option = option.substring(0, pos);
+      }
+      // Add default port if no port configured.
+      if (option.indexOf("[") == 0) {
+        var pos = option.lastIndexOf(":");
+        if (option.indexOf("]") > pos) option = "$option:1080";
+      } else {
+        if (option.indexOf(":") == -1) option = "$option:1080";
+      }
+      return "PROXY $option";
+    }
+
+    // Default to using the process current environment.
+    if (environment == null) environment = _platformEnvironmentCache;
+
+    String proxyCfg;
+
+    String noProxy = environment["no_proxy"];
+    if (noProxy == null) noProxy = environment["NO_PROXY"];
+    if ((proxyCfg = checkNoProxy(noProxy)) != null) {
+      return proxyCfg;
+    }
+
+    if (url.scheme == "http") {
+      String proxy = environment["http_proxy"];
+      if (proxy == null) proxy = environment["HTTP_PROXY"];
+      if ((proxyCfg = checkProxy(proxy)) != null) {
+        return proxyCfg;
+      }
+    } else if (url.scheme == "https") {
+      String proxy = environment["https_proxy"];
+      if (proxy == null) proxy = environment["HTTPS_PROXY"];
+      if ((proxyCfg = checkProxy(proxy)) != null) {
+        return proxyCfg;
+      }
+    }
+    return "DIRECT";
+  }
+
+  static Map<String, String> _platformEnvironmentCache = Platform.environment;
+}
+
+class _HttpConnection extends LinkedListEntry<_HttpConnection>
+    with _ServiceObject {
+  static const _ACTIVE = 0;
+  static const _IDLE = 1;
+  static const _CLOSING = 2;
+  static const _DETACHED = 3;
+
+  // Use HashMap, as we don't need to keep order.
+  static Map<int, _HttpConnection> _connections =
+      new HashMap<int, _HttpConnection>();
+
+  final /*_ServerSocket*/ _socket;
+  final _HttpServer _httpServer;
+  final _HttpParser _httpParser;
+  int _state = _IDLE;
+  StreamSubscription _subscription;
+  bool _idleMark = false;
+  Future _streamFuture;
+
+  _HttpConnection(this._socket, this._httpServer)
+      : _httpParser = new _HttpParser.requestParser() {
+    _connections[_serviceId] = this;
+    _httpParser.listenToStream(_socket);
+    _subscription = _httpParser.listen((incoming) {
+      _httpServer._markActive(this);
+      // If the incoming was closed, close the connection.
+      incoming.dataDone.then((closing) {
+        if (closing) destroy();
+      });
+      // Only handle one incoming request at the time. Keep the
+      // stream paused until the request has been send.
+      _subscription.pause();
+      _state = _ACTIVE;
+      var outgoing = new _HttpOutgoing(_socket);
+      var response = new _HttpResponse(
+          incoming.uri,
+          incoming.headers.protocolVersion,
+          outgoing,
+          _httpServer.defaultResponseHeaders,
+          _httpServer.serverHeader);
+      var request = new _HttpRequest(response, incoming, _httpServer, this);
+      _streamFuture = outgoing.done.then((_) {
+        response.deadline = null;
+        if (_state == _DETACHED) return;
+        if (response.persistentConnection &&
+            request.persistentConnection &&
+            incoming.fullBodyRead &&
+            !_httpParser.upgrade &&
+            !_httpServer.closed) {
+          _state = _IDLE;
+          _idleMark = false;
+          _httpServer._markIdle(this);
+          // Resume the subscription for incoming requests as the
+          // request is now processed.
+          _subscription.resume();
+        } else {
+          // Close socket, keep-alive not used or body sent before
+          // received data was handled.
+          destroy();
+        }
+      }, onError: (_) {
+        destroy();
+      });
+      outgoing.ignoreBody = request.method == "HEAD";
+      response._httpRequest = request;
+      _httpServer._handleRequest(request);
+    }, onDone: () {
+      destroy();
+    }, onError: (error) {
+      // Ignore failed requests that was closed before headers was received.
+      destroy();
+    });
+  }
+
+  void markIdle() {
+    _idleMark = true;
+  }
+
+  bool get isMarkedIdle => _idleMark;
+
+  void destroy() {
+    if (_state == _CLOSING || _state == _DETACHED) return;
+    _state = _CLOSING;
+    _socket.destroy();
+    _httpServer._connectionClosed(this);
+    _connections.remove(_serviceId);
+  }
+
+  Future<Socket> detachSocket() {
+    _state = _DETACHED;
+    // Remove connection from server.
+    _httpServer._connectionClosed(this);
+
+    _HttpDetachedIncoming detachedIncoming = _httpParser.detachIncoming();
+
+    return _streamFuture.then((_) {
+      _connections.remove(_serviceId);
+      return new _DetachedSocket(_socket, detachedIncoming);
+    });
+  }
+
+  HttpConnectionInfo get connectionInfo => _HttpConnectionInfo.create(_socket);
+
+  bool get _isActive => _state == _ACTIVE;
+  bool get _isIdle => _state == _IDLE;
+  bool get _isClosing => _state == _CLOSING;
+  bool get _isDetached => _state == _DETACHED;
+
+  String get _serviceTypePath => 'io/http/serverconnections';
+  String get _serviceTypeName => 'HttpServerConnection';
+
+  Map _toJSON(bool ref) {
+    var name = "${_socket.address.host}:${_socket.port} <-> "
+        "${_socket.remoteAddress.host}:${_socket.remotePort}";
+    var r = <String, dynamic>{
+      'id': _servicePath,
+      'type': _serviceType(ref),
+      'name': name,
+      'user_name': name,
+    };
+    if (ref) {
+      return r;
+    }
+    r['server'] = _httpServer._toJSON(true);
+    try {
+      r['socket'] = _socket._toJSON(true);
+    } catch (_) {
+      r['socket'] = {
+        'id': _servicePath,
+        'type': '@Socket',
+        'name': 'UserSocket',
+        'user_name': 'UserSocket',
+      };
+    }
+    switch (_state) {
+      case _ACTIVE:
+        r['state'] = "Active";
+        break;
+      case _IDLE:
+        r['state'] = "Idle";
+        break;
+      case _CLOSING:
+        r['state'] = "Closing";
+        break;
+      case _DETACHED:
+        r['state'] = "Detached";
+        break;
+      default:
+        r['state'] = 'Unknown';
+        break;
+    }
+    return r;
+  }
+}
+
+// HTTP server waiting for socket connections.
+class _HttpServer extends Stream<HttpRequest>
+    with _ServiceObject
+    implements HttpServer {
+  // Use default Map so we keep order.
+  static Map<int, _HttpServer> _servers = new Map<int, _HttpServer>();
+
+  String serverHeader;
+  final HttpHeaders defaultResponseHeaders = _initDefaultResponseHeaders();
+  bool autoCompress = false;
+
+  Duration _idleTimeout;
+  Timer _idleTimer;
+
+  static Future<HttpServer> bind(
+      address, int port, int backlog, bool v6Only, bool shared) {
+    return ServerSocket
+        .bind(address, port, backlog: backlog, v6Only: v6Only, shared: shared)
+        .then<HttpServer>((socket) {
+      return new _HttpServer._(socket, true);
+    });
+  }
+
+  static Future<HttpServer> bindSecure(
+      address,
+      int port,
+      SecurityContext context,
+      int backlog,
+      bool v6Only,
+      bool requestClientCertificate,
+      bool shared) {
+    return SecureServerSocket
+        .bind(address, port, context,
+            backlog: backlog,
+            v6Only: v6Only,
+            requestClientCertificate: requestClientCertificate,
+            shared: shared)
+        .then<HttpServer>((socket) {
+      return new _HttpServer._(socket, true);
+    });
+  }
+
+  _HttpServer._(this._serverSocket, this._closeServer) {
+    _controller =
+        new StreamController<HttpRequest>(sync: true, onCancel: close);
+    idleTimeout = const Duration(seconds: 120);
+    _servers[_serviceId] = this;
+  }
+
+  _HttpServer.listenOn(this._serverSocket) : _closeServer = false {
+    _controller =
+        new StreamController<HttpRequest>(sync: true, onCancel: close);
+    idleTimeout = const Duration(seconds: 120);
+    _servers[_serviceId] = this;
+  }
+
+  static HttpHeaders _initDefaultResponseHeaders() {
+    var defaultResponseHeaders = new _HttpHeaders('1.1');
+    defaultResponseHeaders.contentType = ContentType.TEXT;
+    defaultResponseHeaders.set('X-Frame-Options', 'SAMEORIGIN');
+    defaultResponseHeaders.set('X-Content-Type-Options', 'nosniff');
+    defaultResponseHeaders.set('X-XSS-Protection', '1; mode=block');
+    return defaultResponseHeaders;
+  }
+
+  Duration get idleTimeout => _idleTimeout;
+
+  void set idleTimeout(Duration duration) {
+    if (_idleTimer != null) {
+      _idleTimer.cancel();
+      _idleTimer = null;
+    }
+    _idleTimeout = duration;
+    if (_idleTimeout != null) {
+      _idleTimer = new Timer.periodic(_idleTimeout, (_) {
+        for (var idle in _idleConnections.toList()) {
+          if (idle.isMarkedIdle) {
+            idle.destroy();
+          } else {
+            idle.markIdle();
+          }
+        }
+      });
+    }
+  }
+
+  StreamSubscription<HttpRequest> listen(void onData(HttpRequest event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    _serverSocket.listen((Socket socket) {
+      socket.setOption(SocketOption.TCP_NODELAY, true);
+      // Accept the client connection.
+      _HttpConnection connection = new _HttpConnection(socket, this);
+      _idleConnections.add(connection);
+    }, onError: (error, stackTrace) {
+      // Ignore HandshakeExceptions as they are bound to a single request,
+      // and are not fatal for the server.
+      if (error is! HandshakeException) {
+        _controller.addError(error, stackTrace);
+      }
+    }, onDone: _controller.close);
+    return _controller.stream.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  Future close({bool force: false}) {
+    closed = true;
+    Future result;
+    if (_serverSocket != null && _closeServer) {
+      result = _serverSocket.close();
+    } else {
+      result = new Future.value();
+    }
+    idleTimeout = null;
+    if (force) {
+      for (var c in _activeConnections.toList()) {
+        c.destroy();
+      }
+      assert(_activeConnections.isEmpty);
+    }
+    for (var c in _idleConnections.toList()) {
+      c.destroy();
+    }
+    _maybePerformCleanup();
+    return result;
+  }
+
+  void _maybePerformCleanup() {
+    if (closed &&
+        _idleConnections.isEmpty &&
+        _activeConnections.isEmpty &&
+        _sessionManagerInstance != null) {
+      _sessionManagerInstance.close();
+      _sessionManagerInstance = null;
+      _servers.remove(_serviceId);
+    }
+  }
+
+  int get port {
+    if (closed) throw new HttpException("HttpServer is not bound to a socket");
+    return _serverSocket.port;
+  }
+
+  InternetAddress get address {
+    if (closed) throw new HttpException("HttpServer is not bound to a socket");
+    return _serverSocket.address;
+  }
+
+  set sessionTimeout(int timeout) {
+    _sessionManager.sessionTimeout = timeout;
+  }
+
+  void _handleRequest(_HttpRequest request) {
+    if (!closed) {
+      _controller.add(request);
+    } else {
+      request._httpConnection.destroy();
+    }
+  }
+
+  void _connectionClosed(_HttpConnection connection) {
+    // Remove itself from either idle or active connections.
+    connection.unlink();
+    _maybePerformCleanup();
+  }
+
+  void _markIdle(_HttpConnection connection) {
+    _activeConnections.remove(connection);
+    _idleConnections.add(connection);
+  }
+
+  void _markActive(_HttpConnection connection) {
+    _idleConnections.remove(connection);
+    _activeConnections.add(connection);
+  }
+
+  _HttpSessionManager get _sessionManager {
+    // Lazy init.
+    if (_sessionManagerInstance == null) {
+      _sessionManagerInstance = new _HttpSessionManager();
+    }
+    return _sessionManagerInstance;
+  }
+
+  HttpConnectionsInfo connectionsInfo() {
+    HttpConnectionsInfo result = new HttpConnectionsInfo();
+    result.total = _activeConnections.length + _idleConnections.length;
+    _activeConnections.forEach((_HttpConnection conn) {
+      if (conn._isActive) {
+        result.active++;
+      } else {
+        assert(conn._isClosing);
+        result.closing++;
+      }
+    });
+    _idleConnections.forEach((_HttpConnection conn) {
+      result.idle++;
+      assert(conn._isIdle);
+    });
+    return result;
+  }
+
+  String get _serviceTypePath => 'io/http/servers';
+  String get _serviceTypeName => 'HttpServer';
+
+  Map<String, dynamic> _toJSON(bool ref) {
+    var r = <String, dynamic>{
+      'id': _servicePath,
+      'type': _serviceType(ref),
+      'name': '${address.host}:$port',
+      'user_name': '${address.host}:$port',
+    };
+    if (ref) {
+      return r;
+    }
+    try {
+      r['socket'] = _serverSocket._toJSON(true);
+    } catch (_) {
+      r['socket'] = {
+        'id': _servicePath,
+        'type': '@Socket',
+        'name': 'UserSocket',
+        'user_name': 'UserSocket',
+      };
+    }
+    r['port'] = port;
+    r['address'] = address.host;
+    r['active'] = _activeConnections.map((c) => c._toJSON(true)).toList();
+    r['idle'] = _idleConnections.map((c) => c._toJSON(true)).toList();
+    r['closed'] = closed;
+    return r;
+  }
+
+  _HttpSessionManager _sessionManagerInstance;
+
+  // Indicated if the http server has been closed.
+  bool closed = false;
+
+  // The server listen socket. Untyped as it can be both ServerSocket and
+  // SecureServerSocket.
+  final dynamic /*ServerSocket|SecureServerSocket*/ _serverSocket;
+  final bool _closeServer;
+
+  // Set of currently connected clients.
+  final LinkedList<_HttpConnection> _activeConnections =
+      new LinkedList<_HttpConnection>();
+  final LinkedList<_HttpConnection> _idleConnections =
+      new LinkedList<_HttpConnection>();
+  StreamController<HttpRequest> _controller;
+}
+
+class _ProxyConfiguration {
+  static const String PROXY_PREFIX = "PROXY ";
+  static const String DIRECT_PREFIX = "DIRECT";
+
+  _ProxyConfiguration(String configuration) : proxies = new List<_Proxy>() {
+    if (configuration == null) {
+      throw new HttpException("Invalid proxy configuration $configuration");
+    }
+    List<String> list = configuration.split(";");
+    list.forEach((String proxy) {
+      proxy = proxy.trim();
+      if (!proxy.isEmpty) {
+        if (proxy.startsWith(PROXY_PREFIX)) {
+          String username;
+          String password;
+          // Skip the "PROXY " prefix.
+          proxy = proxy.substring(PROXY_PREFIX.length).trim();
+          // Look for proxy authentication.
+          int at = proxy.indexOf("@");
+          if (at != -1) {
+            String userinfo = proxy.substring(0, at).trim();
+            proxy = proxy.substring(at + 1).trim();
+            int colon = userinfo.indexOf(":");
+            if (colon == -1 || colon == 0 || colon == proxy.length - 1) {
+              throw new HttpException(
+                  "Invalid proxy configuration $configuration");
+            }
+            username = userinfo.substring(0, colon).trim();
+            password = userinfo.substring(colon + 1).trim();
+          }
+          // Look for proxy host and port.
+          int colon = proxy.lastIndexOf(":");
+          if (colon == -1 || colon == 0 || colon == proxy.length - 1) {
+            throw new HttpException(
+                "Invalid proxy configuration $configuration");
+          }
+          String host = proxy.substring(0, colon).trim();
+          if (host.startsWith("[") && host.endsWith("]")) {
+            host = host.substring(1, host.length - 1);
+          }
+          String portString = proxy.substring(colon + 1).trim();
+          int port;
+          try {
+            port = int.parse(portString);
+          } on FormatException catch (e) {
+            throw new HttpException(
+                "Invalid proxy configuration $configuration, "
+                "invalid port '$portString'");
+          }
+          proxies.add(new _Proxy(host, port, username, password));
+        } else if (proxy.trim() == DIRECT_PREFIX) {
+          proxies.add(new _Proxy.direct());
+        } else {
+          throw new HttpException("Invalid proxy configuration $configuration");
+        }
+      }
+    });
+  }
+
+  const _ProxyConfiguration.direct() : proxies = const [const _Proxy.direct()];
+
+  final List<_Proxy> proxies;
+}
+
+class _Proxy {
+  final String host;
+  final int port;
+  final String username;
+  final String password;
+  final bool isDirect;
+
+  const _Proxy(this.host, this.port, this.username, this.password)
+      : isDirect = false;
+  const _Proxy.direct()
+      : host = null,
+        port = null,
+        username = null,
+        password = null,
+        isDirect = true;
+
+  bool get isAuthenticated => username != null;
+}
+
+class _HttpConnectionInfo implements HttpConnectionInfo {
+  InternetAddress remoteAddress;
+  int remotePort;
+  int localPort;
+
+  static _HttpConnectionInfo create(Socket socket) {
+    if (socket == null) return null;
+    try {
+      _HttpConnectionInfo info = new _HttpConnectionInfo();
+      return info
+        ..remoteAddress = socket.remoteAddress
+        ..remotePort = socket.remotePort
+        ..localPort = socket.port;
+    } catch (e) {}
+    return null;
+  }
+}
+
+class _DetachedSocket extends Stream<List<int>> implements Socket {
+  final Stream<List<int>> _incoming;
+  final Socket _socket;
+
+  _DetachedSocket(this._socket, this._incoming);
+
+  StreamSubscription<List<int>> listen(void onData(List<int> event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    return _incoming.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  Encoding get encoding => _socket.encoding;
+
+  void set encoding(Encoding value) {
+    _socket.encoding = value;
+  }
+
+  void write(Object obj) {
+    _socket.write(obj);
+  }
+
+  void writeln([Object obj = ""]) {
+    _socket.writeln(obj);
+  }
+
+  void writeCharCode(int charCode) {
+    _socket.writeCharCode(charCode);
+  }
+
+  void writeAll(Iterable objects, [String separator = ""]) {
+    _socket.writeAll(objects, separator);
+  }
+
+  void add(List<int> bytes) {
+    _socket.add(bytes);
+  }
+
+  void addError(error, [StackTrace stackTrace]) =>
+      _socket.addError(error, stackTrace);
+
+  Future addStream(Stream<List<int>> stream) {
+    return _socket.addStream(stream);
+  }
+
+  void destroy() {
+    _socket.destroy();
+  }
+
+  Future flush() => _socket.flush();
+
+  Future close() => _socket.close();
+
+  Future get done => _socket.done;
+
+  int get port => _socket.port;
+
+  InternetAddress get address => _socket.address;
+
+  InternetAddress get remoteAddress => _socket.remoteAddress;
+
+  int get remotePort => _socket.remotePort;
+
+  bool setOption(SocketOption option, bool enabled) {
+    return _socket.setOption(option, enabled);
+  }
+
+  Map _toJSON(bool ref) {
+    return (_socket as dynamic)._toJSON(ref);
+  }
+}
+
+class _AuthenticationScheme {
+  final int _scheme;
+
+  static const UNKNOWN = const _AuthenticationScheme(-1);
+  static const BASIC = const _AuthenticationScheme(0);
+  static const DIGEST = const _AuthenticationScheme(1);
+
+  const _AuthenticationScheme(this._scheme);
+
+  factory _AuthenticationScheme.fromString(String scheme) {
+    if (scheme.toLowerCase() == "basic") return BASIC;
+    if (scheme.toLowerCase() == "digest") return DIGEST;
+    return UNKNOWN;
+  }
+
+  String toString() {
+    if (this == BASIC) return "Basic";
+    if (this == DIGEST) return "Digest";
+    return "Unknown";
+  }
+}
+
+abstract class _Credentials {
+  _HttpClientCredentials credentials;
+  String realm;
+  bool used = false;
+
+  // Digest specific fields.
+  String ha1;
+  String nonce;
+  String algorithm;
+  String qop;
+  int nonceCount;
+
+  _Credentials(this.credentials, this.realm) {
+    if (credentials.scheme == _AuthenticationScheme.DIGEST) {
+      // Calculate the H(A1) value once. There is no mentioning of
+      // username/password encoding in RFC 2617. However there is an
+      // open draft for adding an additional accept-charset parameter to
+      // the WWW-Authenticate and Proxy-Authenticate headers, see
+      // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
+      // now always use UTF-8 encoding.
+      _HttpClientDigestCredentials creds = credentials;
+      var hasher = new _MD5()
+        ..add(utf8.encode(creds.username))
+        ..add([_CharCode.COLON])
+        ..add(realm.codeUnits)
+        ..add([_CharCode.COLON])
+        ..add(utf8.encode(creds.password));
+      ha1 = _CryptoUtils.bytesToHex(hasher.close());
+    }
+  }
+
+  _AuthenticationScheme get scheme => credentials.scheme;
+
+  void authorize(HttpClientRequest request);
+}
+
+class _SiteCredentials extends _Credentials {
+  Uri uri;
+
+  _SiteCredentials(this.uri, realm, _HttpClientCredentials creds)
+      : super(creds, realm);
+
+  bool applies(Uri uri, _AuthenticationScheme scheme) {
+    if (scheme != null && credentials.scheme != scheme) return false;
+    if (uri.host != this.uri.host) return false;
+    int thisPort =
+        this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port;
+    int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port;
+    if (otherPort != thisPort) return false;
+    return uri.path.startsWith(this.uri.path);
+  }
+
+  void authorize(HttpClientRequest request) {
+    // Digest credentials cannot be used without a nonce from the
+    // server.
+    if (credentials.scheme == _AuthenticationScheme.DIGEST && nonce == null) {
+      return;
+    }
+    credentials.authorize(this, request);
+    used = true;
+  }
+}
+
+class _ProxyCredentials extends _Credentials {
+  String host;
+  int port;
+
+  _ProxyCredentials(this.host, this.port, realm, _HttpClientCredentials creds)
+      : super(creds, realm);
+
+  bool applies(_Proxy proxy, _AuthenticationScheme scheme) {
+    if (scheme != null && credentials.scheme != scheme) return false;
+    return proxy.host == host && proxy.port == port;
+  }
+
+  void authorize(HttpClientRequest request) {
+    // Digest credentials cannot be used without a nonce from the
+    // server.
+    if (credentials.scheme == _AuthenticationScheme.DIGEST && nonce == null) {
+      return;
+    }
+    credentials.authorizeProxy(this, request);
+  }
+}
+
+abstract class _HttpClientCredentials implements HttpClientCredentials {
+  _AuthenticationScheme get scheme;
+  void authorize(_Credentials credentials, HttpClientRequest request);
+  void authorizeProxy(_ProxyCredentials credentials, HttpClientRequest request);
+}
+
+class _HttpClientBasicCredentials extends _HttpClientCredentials
+    implements HttpClientBasicCredentials {
+  String username;
+  String password;
+
+  _HttpClientBasicCredentials(this.username, this.password);
+
+  _AuthenticationScheme get scheme => _AuthenticationScheme.BASIC;
+
+  String authorization() {
+    // There is no mentioning of username/password encoding in RFC
+    // 2617. However there is an open draft for adding an additional
+    // accept-charset parameter to the WWW-Authenticate and
+    // Proxy-Authenticate headers, see
+    // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
+    // now always use UTF-8 encoding.
+    String auth =
+        _CryptoUtils.bytesToBase64(utf8.encode("$username:$password"));
+    return "Basic $auth";
+  }
+
+  void authorize(_Credentials _, HttpClientRequest request) {
+    request.headers.set(HttpHeaders.AUTHORIZATION, authorization());
+  }
+
+  void authorizeProxy(_ProxyCredentials _, HttpClientRequest request) {
+    request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, authorization());
+  }
+}
+
+class _HttpClientDigestCredentials extends _HttpClientCredentials
+    implements HttpClientDigestCredentials {
+  String username;
+  String password;
+
+  _HttpClientDigestCredentials(this.username, this.password);
+
+  _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST;
+
+  String authorization(_Credentials credentials, _HttpClientRequest request) {
+    String requestUri = request._requestUri();
+    _MD5 hasher = new _MD5()
+      ..add(request.method.codeUnits)
+      ..add([_CharCode.COLON])
+      ..add(requestUri.codeUnits);
+    var ha2 = _CryptoUtils.bytesToHex(hasher.close());
+
+    String qop;
+    String cnonce;
+    String nc;
+    var x;
+    hasher = new _MD5()..add(credentials.ha1.codeUnits)..add([_CharCode.COLON]);
+    if (credentials.qop == "auth") {
+      qop = credentials.qop;
+      cnonce = _CryptoUtils.bytesToHex(_CryptoUtils.getRandomBytes(4));
+      ++credentials.nonceCount;
+      nc = credentials.nonceCount.toRadixString(16);
+      nc = "00000000".substring(0, 8 - nc.length + 1) + nc;
+      hasher
+        ..add(credentials.nonce.codeUnits)
+        ..add([_CharCode.COLON])
+        ..add(nc.codeUnits)
+        ..add([_CharCode.COLON])
+        ..add(cnonce.codeUnits)
+        ..add([_CharCode.COLON])
+        ..add(credentials.qop.codeUnits)
+        ..add([_CharCode.COLON])
+        ..add(ha2.codeUnits);
+    } else {
+      hasher
+        ..add(credentials.nonce.codeUnits)
+        ..add([_CharCode.COLON])
+        ..add(ha2.codeUnits);
+    }
+    var response = _CryptoUtils.bytesToHex(hasher.close());
+
+    StringBuffer buffer = new StringBuffer()
+      ..write('Digest ')
+      ..write('username="$username"')
+      ..write(', realm="${credentials.realm}"')
+      ..write(', nonce="${credentials.nonce}"')
+      ..write(', uri="$requestUri"')
+      ..write(', algorithm="${credentials.algorithm}"');
+    if (qop == "auth") {
+      buffer
+        ..write(', qop="$qop"')
+        ..write(', cnonce="$cnonce"')
+        ..write(', nc="$nc"');
+    }
+    buffer.write(', response="$response"');
+    return buffer.toString();
+  }
+
+  void authorize(_Credentials credentials, HttpClientRequest request) {
+    request.headers
+        .set(HttpHeaders.AUTHORIZATION, authorization(credentials, request));
+  }
+
+  void authorizeProxy(
+      _ProxyCredentials credentials, HttpClientRequest request) {
+    request.headers.set(
+        HttpHeaders.PROXY_AUTHORIZATION, authorization(credentials, request));
+  }
+}
+
+class _RedirectInfo implements RedirectInfo {
+  final int statusCode;
+  final String method;
+  final Uri location;
+  const _RedirectInfo(this.statusCode, this.method, this.location);
+}
+
+String _getHttpVersion() {
+  var version = Platform.version;
+  // Only include major and minor version numbers.
+  int index = version.indexOf('.', version.indexOf('.') + 1);
+  version = version.substring(0, index);
+  return 'Dart/$version (dart:io)';
+}
diff --git a/lib/src/http_parser.dart b/lib/src/http_parser.dart
new file mode 100644
index 0000000..369909b
--- /dev/null
+++ b/lib/src/http_parser.dart
@@ -0,0 +1,1062 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http_io;
+
+// Global constants.
+class _Const {
+  // Bytes for "HTTP".
+  static const HTTP = const [72, 84, 84, 80];
+  // Bytes for "HTTP/1.".
+  static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46];
+  // Bytes for "HTTP/1.0".
+  static const HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48];
+  // Bytes for "HTTP/1.1".
+  static const HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49];
+
+  static const bool T = true;
+  static const bool F = false;
+  // Loopup-map for the following characters: '()<>@,;:\\"/[]?={} \t'.
+  static const SEPARATOR_MAP = const [
+    F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, //
+    F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
+    F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F
+  ];
+}
+
+// Frequently used character codes.
+class _CharCode {
+  static const int HT = 9;
+  static const int LF = 10;
+  static const int CR = 13;
+  static const int SP = 32;
+  static const int AMPERSAND = 38;
+  static const int COMMA = 44;
+  static const int DASH = 45;
+  static const int SLASH = 47;
+  static const int ZERO = 48;
+  static const int ONE = 49;
+  static const int COLON = 58;
+  static const int SEMI_COLON = 59;
+  static const int EQUAL = 61;
+}
+
+// States of the HTTP parser state machine.
+class _State {
+  static const int START = 0;
+  static const int METHOD_OR_RESPONSE_HTTP_VERSION = 1;
+  static const int RESPONSE_HTTP_VERSION = 2;
+  static const int REQUEST_LINE_METHOD = 3;
+  static const int REQUEST_LINE_URI = 4;
+  static const int REQUEST_LINE_HTTP_VERSION = 5;
+  static const int REQUEST_LINE_ENDING = 6;
+  static const int RESPONSE_LINE_STATUS_CODE = 7;
+  static const int RESPONSE_LINE_REASON_PHRASE = 8;
+  static const int RESPONSE_LINE_ENDING = 9;
+  static const int HEADER_START = 10;
+  static const int HEADER_FIELD = 11;
+  static const int HEADER_VALUE_START = 12;
+  static const int HEADER_VALUE = 13;
+  static const int HEADER_VALUE_FOLDING_OR_ENDING = 14;
+  static const int HEADER_VALUE_FOLD_OR_END = 15;
+  static const int HEADER_ENDING = 16;
+
+  static const int CHUNK_SIZE_STARTING_CR = 17;
+  static const int CHUNK_SIZE_STARTING_LF = 18;
+  static const int CHUNK_SIZE = 19;
+  static const int CHUNK_SIZE_EXTENSION = 20;
+  static const int CHUNK_SIZE_ENDING = 21;
+  static const int CHUNKED_BODY_DONE_CR = 22;
+  static const int CHUNKED_BODY_DONE_LF = 23;
+  static const int BODY = 24;
+  static const int CLOSED = 25;
+  static const int UPGRADED = 26;
+  static const int FAILURE = 27;
+
+  static const int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR;
+}
+
+// HTTP version of the request or response being parsed.
+class _HttpVersion {
+  static const int UNDETERMINED = 0;
+  static const int HTTP10 = 1;
+  static const int HTTP11 = 2;
+}
+
+// States of the HTTP parser state machine.
+class _MessageType {
+  static const int UNDETERMINED = 0;
+  static const int REQUEST = 1;
+  static const int RESPONSE = 0;
+}
+
+/**
+ * The _HttpDetachedStreamSubscription takes a subscription and some extra data,
+ * and makes it possible to "inject" the data in from of other data events
+ * from the subscription.
+ *
+ * It does so by overriding pause/resume, so that once the
+ * _HttpDetachedStreamSubscription is resumed, it'll deliver the data before
+ * resuming the underlaying subscription.
+ */
+class _HttpDetachedStreamSubscription implements StreamSubscription<List<int>> {
+  StreamSubscription<List<int>> _subscription;
+  List<int> _injectData;
+  bool _isCanceled = false;
+  int _pauseCount = 1;
+  Function _userOnData;
+  bool _scheduled = false;
+
+  _HttpDetachedStreamSubscription(
+      this._subscription, this._injectData, this._userOnData);
+
+  bool get isPaused => _subscription.isPaused;
+
+  Future<T> asFuture<T>([T futureValue]) =>
+      _subscription.asFuture<T>(futureValue);
+
+  Future cancel() {
+    _isCanceled = true;
+    _injectData = null;
+    return _subscription.cancel();
+  }
+
+  void onData(void handleData(List<int> data)) {
+    _userOnData = handleData;
+    _subscription.onData(handleData);
+  }
+
+  void onDone(void handleDone()) {
+    _subscription.onDone(handleDone);
+  }
+
+  void onError(Function handleError) {
+    _subscription.onError(handleError);
+  }
+
+  void pause([Future resumeSignal]) {
+    if (_injectData == null) {
+      _subscription.pause(resumeSignal);
+    } else {
+      _pauseCount++;
+      if (resumeSignal != null) {
+        resumeSignal.whenComplete(resume);
+      }
+    }
+  }
+
+  void resume() {
+    if (_injectData == null) {
+      _subscription.resume();
+    } else {
+      _pauseCount--;
+      _maybeScheduleData();
+    }
+  }
+
+  void _maybeScheduleData() {
+    if (_scheduled) return;
+    if (_pauseCount != 0) return;
+    _scheduled = true;
+    scheduleMicrotask(() {
+      _scheduled = false;
+      if (_pauseCount > 0 || _isCanceled) return;
+      var data = _injectData;
+      _injectData = null;
+      // To ensure that 'subscription.isPaused' is false, we resume the
+      // subscription here. This is fine as potential events are delayed.
+      _subscription.resume();
+      if (_userOnData != null) {
+        _userOnData(data);
+      }
+    });
+  }
+}
+
+class _HttpDetachedIncoming extends Stream<List<int>> {
+  final StreamSubscription<List<int>> subscription;
+  final List<int> bufferedData;
+
+  _HttpDetachedIncoming(this.subscription, this.bufferedData);
+
+  StreamSubscription<List<int>> listen(void onData(List<int> event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    if (subscription != null) {
+      subscription
+        ..onData(onData)
+        ..onError(onError)
+        ..onDone(onDone);
+      if (bufferedData == null) {
+        return subscription..resume();
+      }
+      return new _HttpDetachedStreamSubscription(
+          subscription, bufferedData, onData)
+        ..resume();
+    } else {
+      // TODO(26379): add test for this branch.
+      return new Stream<List<int>>.fromIterable([bufferedData]).listen(onData,
+          onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+    }
+  }
+}
+
+/**
+ * HTTP parser which parses the data stream given to [consume].
+ *
+ * If an HTTP parser error occurs, the parser will signal an error to either
+ * the current _HttpIncoming or the _parser itself.
+ *
+ * The connection upgrades (e.g. switching from HTTP/1.1 to the
+ * WebSocket protocol) is handled in a special way. If connection
+ * upgrade is specified in the headers, then on the callback to
+ * [:responseStart:] the [:upgrade:] property on the [:HttpParser:]
+ * object will be [:true:] indicating that from now on the protocol is
+ * not HTTP anymore and no more callbacks will happen, that is
+ * [:dataReceived:] and [:dataEnd:] are not called in this case as
+ * there is no more HTTP data. After the upgrade the method
+ * [:readUnparsedData:] can be used to read any remaining bytes in the
+ * HTTP parser which are part of the protocol the connection is
+ * upgrading to. These bytes cannot be processed by the HTTP parser
+ * and should be handled according to whatever protocol is being
+ * upgraded to.
+ */
+class _HttpParser extends Stream<_HttpIncoming> {
+  // State.
+  bool _parserCalled = false;
+
+  // The data that is currently being parsed.
+  Uint8List _buffer;
+  int _index;
+
+  final bool _requestParser;
+  int _state;
+  int _httpVersionIndex;
+  int _messageType;
+  int _statusCode = 0;
+  int _statusCodeLength = 0;
+  final List<int> _method = [];
+  final List<int> _uri_or_reason_phrase = [];
+  final List<int> _headerField = [];
+  final List<int> _headerValue = [];
+
+  int _httpVersion;
+  int _transferLength = -1;
+  bool _persistentConnection;
+  bool _connectionUpgrade;
+  bool _chunked;
+
+  bool _noMessageBody = false;
+  int _remainingContent = -1;
+
+  _HttpHeaders _headers;
+
+  // The current incoming connection.
+  _HttpIncoming _incoming;
+  StreamSubscription<List<int>> _socketSubscription;
+  bool _paused = true;
+  bool _bodyPaused = false;
+  StreamController<_HttpIncoming> _controller;
+  StreamController<List<int>> _bodyController;
+
+  factory _HttpParser.requestParser() {
+    return new _HttpParser._(true);
+  }
+
+  factory _HttpParser.responseParser() {
+    return new _HttpParser._(false);
+  }
+
+  _HttpParser._(this._requestParser) {
+    _controller = new StreamController<_HttpIncoming>(
+        sync: true,
+        onListen: () {
+          _paused = false;
+        },
+        onPause: () {
+          _paused = true;
+          _pauseStateChanged();
+        },
+        onResume: () {
+          _paused = false;
+          _pauseStateChanged();
+        },
+        onCancel: () {
+          if (_socketSubscription != null) {
+            _socketSubscription.cancel();
+          }
+        });
+    _reset();
+  }
+
+  StreamSubscription<_HttpIncoming> listen(void onData(_HttpIncoming event),
+      {Function onError, void onDone(), bool cancelOnError}) {
+    return _controller.stream.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  void listenToStream(Stream<List<int>> stream) {
+    // Listen to the stream and handle data accordingly. When a
+    // _HttpIncoming is created, _dataPause, _dataResume, _dataDone is
+    // given to provide a way of controlling the parser.
+    // TODO(ajohnsen): Remove _dataPause, _dataResume and _dataDone and clean up
+    // how the _HttpIncoming signals the parser.
+    _socketSubscription =
+        stream.listen(_onData, onError: _controller.addError, onDone: _onDone);
+  }
+
+  void _parse() {
+    try {
+      _doParse();
+    } catch (e, s) {
+      _state = _State.FAILURE;
+      _reportError(e, s);
+    }
+  }
+
+  // Process end of headers. Returns true if the parser should stop
+  // parsing and return. This will be in case of either an upgrade
+  // request or a request or response with an empty body.
+  bool _headersEnd() {
+    _headers._mutable = false;
+
+    _transferLength = _headers.contentLength;
+    // Ignore the Content-Length header if Transfer-Encoding
+    // is chunked (RFC 2616 section 4.4)
+    if (_chunked) _transferLength = -1;
+
+    // If a request message has neither Content-Length nor
+    // Transfer-Encoding the message must not have a body (RFC
+    // 2616 section 4.3).
+    if (_messageType == _MessageType.REQUEST &&
+        _transferLength < 0 &&
+        _chunked == false) {
+      _transferLength = 0;
+    }
+    if (_connectionUpgrade) {
+      _state = _State.UPGRADED;
+      _transferLength = 0;
+    }
+    _createIncoming(_transferLength);
+    if (_requestParser) {
+      _incoming.method = new String.fromCharCodes(_method);
+      _incoming.uri =
+          Uri.parse(new String.fromCharCodes(_uri_or_reason_phrase));
+    } else {
+      _incoming.statusCode = _statusCode;
+      _incoming.reasonPhrase = new String.fromCharCodes(_uri_or_reason_phrase);
+    }
+    _method.clear();
+    _uri_or_reason_phrase.clear();
+    if (_connectionUpgrade) {
+      _incoming.upgraded = true;
+      _parserCalled = false;
+      var tmp = _incoming;
+      _closeIncoming();
+      _controller.add(tmp);
+      return true;
+    }
+    if (_transferLength == 0 ||
+        (_messageType == _MessageType.RESPONSE && _noMessageBody)) {
+      _reset();
+      var tmp = _incoming;
+      _closeIncoming();
+      _controller.add(tmp);
+      return false;
+    } else if (_chunked) {
+      _state = _State.CHUNK_SIZE;
+      _remainingContent = 0;
+    } else if (_transferLength > 0) {
+      _remainingContent = _transferLength;
+      _state = _State.BODY;
+    } else {
+      // Neither chunked nor content length. End of body
+      // indicated by close.
+      _state = _State.BODY;
+    }
+    _parserCalled = false;
+    _controller.add(_incoming);
+    return true;
+  }
+
+  // From RFC 2616.
+  // generic-message = start-line
+  //                   *(message-header CRLF)
+  //                   CRLF
+  //                   [ message-body ]
+  // start-line      = Request-Line | Status-Line
+  // Request-Line    = Method SP Request-URI SP HTTP-Version CRLF
+  // Status-Line     = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+  // message-header  = field-name ":" [ field-value ]
+  void _doParse() {
+    assert(!_parserCalled);
+    _parserCalled = true;
+    if (_state == _State.CLOSED) {
+      throw new HttpException("Data on closed connection");
+    }
+    if (_state == _State.FAILURE) {
+      throw new HttpException("Data on failed connection");
+    }
+    while (_buffer != null &&
+        _index < _buffer.length &&
+        _state != _State.FAILURE &&
+        _state != _State.UPGRADED) {
+      // Depending on _incoming, we either break on _bodyPaused or _paused.
+      if ((_incoming != null && _bodyPaused) ||
+          (_incoming == null && _paused)) {
+        _parserCalled = false;
+        return;
+      }
+      int byte = _buffer[_index++];
+      switch (_state) {
+        case _State.START:
+          if (byte == _Const.HTTP[0]) {
+            // Start parsing method or HTTP version.
+            _httpVersionIndex = 1;
+            _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION;
+          } else {
+            // Start parsing method.
+            if (!_isTokenChar(byte)) {
+              throw new HttpException("Invalid request method");
+            }
+            _method.add(byte);
+            if (!_requestParser) {
+              throw new HttpException("Invalid response line");
+            }
+            _state = _State.REQUEST_LINE_METHOD;
+          }
+          break;
+
+        case _State.METHOD_OR_RESPONSE_HTTP_VERSION:
+          if (_httpVersionIndex < _Const.HTTP.length &&
+              byte == _Const.HTTP[_httpVersionIndex]) {
+            // Continue parsing HTTP version.
+            _httpVersionIndex++;
+          } else if (_httpVersionIndex == _Const.HTTP.length &&
+              byte == _CharCode.SLASH) {
+            // HTTP/ parsed. As method is a token this cannot be a
+            // method anymore.
+            _httpVersionIndex++;
+            if (_requestParser) {
+              throw new HttpException("Invalid request line");
+            }
+            _state = _State.RESPONSE_HTTP_VERSION;
+          } else {
+            // Did not parse HTTP version. Expect method instead.
+            for (int i = 0; i < _httpVersionIndex; i++) {
+              _method.add(_Const.HTTP[i]);
+            }
+            if (byte == _CharCode.SP) {
+              _state = _State.REQUEST_LINE_URI;
+            } else {
+              _method.add(byte);
+              _httpVersion = _HttpVersion.UNDETERMINED;
+              if (!_requestParser) {
+                throw new HttpException("Invalid response line");
+              }
+              _state = _State.REQUEST_LINE_METHOD;
+            }
+          }
+          break;
+
+        case _State.RESPONSE_HTTP_VERSION:
+          if (_httpVersionIndex < _Const.HTTP1DOT.length) {
+            // Continue parsing HTTP version.
+            _expect(byte, _Const.HTTP1DOT[_httpVersionIndex]);
+            _httpVersionIndex++;
+          } else if (_httpVersionIndex == _Const.HTTP1DOT.length &&
+              byte == _CharCode.ONE) {
+            // HTTP/1.1 parsed.
+            _httpVersion = _HttpVersion.HTTP11;
+            _persistentConnection = true;
+            _httpVersionIndex++;
+          } else if (_httpVersionIndex == _Const.HTTP1DOT.length &&
+              byte == _CharCode.ZERO) {
+            // HTTP/1.0 parsed.
+            _httpVersion = _HttpVersion.HTTP10;
+            _persistentConnection = false;
+            _httpVersionIndex++;
+          } else if (_httpVersionIndex == _Const.HTTP1DOT.length + 1) {
+            _expect(byte, _CharCode.SP);
+            // HTTP version parsed.
+            _state = _State.RESPONSE_LINE_STATUS_CODE;
+          } else {
+            throw new HttpException("Invalid response line");
+          }
+          break;
+
+        case _State.REQUEST_LINE_METHOD:
+          if (byte == _CharCode.SP) {
+            _state = _State.REQUEST_LINE_URI;
+          } else {
+            if (_Const.SEPARATOR_MAP[byte] ||
+                byte == _CharCode.CR ||
+                byte == _CharCode.LF) {
+              throw new HttpException("Invalid request method");
+            }
+            _method.add(byte);
+          }
+          break;
+
+        case _State.REQUEST_LINE_URI:
+          if (byte == _CharCode.SP) {
+            if (_uri_or_reason_phrase.length == 0) {
+              throw new HttpException("Invalid request URI");
+            }
+            _state = _State.REQUEST_LINE_HTTP_VERSION;
+            _httpVersionIndex = 0;
+          } else {
+            if (byte == _CharCode.CR || byte == _CharCode.LF) {
+              throw new HttpException("Invalid request URI");
+            }
+            _uri_or_reason_phrase.add(byte);
+          }
+          break;
+
+        case _State.REQUEST_LINE_HTTP_VERSION:
+          if (_httpVersionIndex < _Const.HTTP1DOT.length) {
+            _expect(byte, _Const.HTTP11[_httpVersionIndex]);
+            _httpVersionIndex++;
+          } else if (_httpVersionIndex == _Const.HTTP1DOT.length) {
+            if (byte == _CharCode.ONE) {
+              // HTTP/1.1 parsed.
+              _httpVersion = _HttpVersion.HTTP11;
+              _persistentConnection = true;
+              _httpVersionIndex++;
+            } else if (byte == _CharCode.ZERO) {
+              // HTTP/1.0 parsed.
+              _httpVersion = _HttpVersion.HTTP10;
+              _persistentConnection = false;
+              _httpVersionIndex++;
+            } else {
+              throw new HttpException("Invalid response line");
+            }
+          } else {
+            if (byte == _CharCode.CR) {
+              _state = _State.REQUEST_LINE_ENDING;
+            } else {
+              _expect(byte, _CharCode.LF);
+              _messageType = _MessageType.REQUEST;
+              _state = _State.HEADER_START;
+            }
+          }
+          break;
+
+        case _State.REQUEST_LINE_ENDING:
+          _expect(byte, _CharCode.LF);
+          _messageType = _MessageType.REQUEST;
+          _state = _State.HEADER_START;
+          break;
+
+        case _State.RESPONSE_LINE_STATUS_CODE:
+          if (byte == _CharCode.SP) {
+            _state = _State.RESPONSE_LINE_REASON_PHRASE;
+          } else if (byte == _CharCode.CR) {
+            // Some HTTP servers does not follow the spec. and send
+            // \r\n right after the status code.
+            _state = _State.RESPONSE_LINE_ENDING;
+          } else {
+            _statusCodeLength++;
+            if ((byte < 0x30 && 0x39 < byte) || _statusCodeLength > 3) {
+              throw new HttpException("Invalid response status code");
+            } else {
+              _statusCode = _statusCode * 10 + byte - 0x30;
+            }
+          }
+          break;
+
+        case _State.RESPONSE_LINE_REASON_PHRASE:
+          if (byte == _CharCode.CR) {
+            _state = _State.RESPONSE_LINE_ENDING;
+          } else {
+            if (byte == _CharCode.CR || byte == _CharCode.LF) {
+              throw new HttpException("Invalid response reason phrase");
+            }
+            _uri_or_reason_phrase.add(byte);
+          }
+          break;
+
+        case _State.RESPONSE_LINE_ENDING:
+          _expect(byte, _CharCode.LF);
+          _messageType == _MessageType.RESPONSE;
+          if (_statusCode < 100 || _statusCode > 599) {
+            throw new HttpException("Invalid response status code");
+          } else {
+            // Check whether this response will never have a body.
+            if (_statusCode <= 199 ||
+                _statusCode == 204 ||
+                _statusCode == 304) {
+              _noMessageBody = true;
+            }
+          }
+          _state = _State.HEADER_START;
+          break;
+
+        case _State.HEADER_START:
+          _headers = new _HttpHeaders(version);
+          if (byte == _CharCode.CR) {
+            _state = _State.HEADER_ENDING;
+          } else if (byte == _CharCode.LF) {
+            _state = _State.HEADER_ENDING;
+            _index--; // Make the new state see the LF again.
+          } else {
+            // Start of new header field.
+            _headerField.add(_toLowerCaseByte(byte));
+            _state = _State.HEADER_FIELD;
+          }
+          break;
+
+        case _State.HEADER_FIELD:
+          if (byte == _CharCode.COLON) {
+            _state = _State.HEADER_VALUE_START;
+          } else {
+            if (!_isTokenChar(byte)) {
+              throw new HttpException("Invalid header field name");
+            }
+            _headerField.add(_toLowerCaseByte(byte));
+          }
+          break;
+
+        case _State.HEADER_VALUE_START:
+          if (byte == _CharCode.CR) {
+            _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
+          } else if (byte == _CharCode.LF) {
+            _state = _State.HEADER_VALUE_FOLD_OR_END;
+          } else if (byte != _CharCode.SP && byte != _CharCode.HT) {
+            // Start of new header value.
+            _headerValue.add(byte);
+            _state = _State.HEADER_VALUE;
+          }
+          break;
+
+        case _State.HEADER_VALUE:
+          if (byte == _CharCode.CR) {
+            _state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
+          } else if (byte == _CharCode.LF) {
+            _state = _State.HEADER_VALUE_FOLD_OR_END;
+          } else {
+            _headerValue.add(byte);
+          }
+          break;
+
+        case _State.HEADER_VALUE_FOLDING_OR_ENDING:
+          _expect(byte, _CharCode.LF);
+          _state = _State.HEADER_VALUE_FOLD_OR_END;
+          break;
+
+        case _State.HEADER_VALUE_FOLD_OR_END:
+          if (byte == _CharCode.SP || byte == _CharCode.HT) {
+            _state = _State.HEADER_VALUE_START;
+          } else {
+            String headerField = new String.fromCharCodes(_headerField);
+            String headerValue = new String.fromCharCodes(_headerValue);
+            if (headerField == "transfer-encoding" &&
+                _caseInsensitiveCompare("chunked".codeUnits, _headerValue)) {
+              _chunked = true;
+            }
+            if (headerField == "connection") {
+              List<String> tokens = _tokenizeFieldValue(headerValue);
+              final bool isResponse = _messageType == _MessageType.RESPONSE;
+              final bool isUpgradeCode =
+                  (_statusCode == HttpStatus.UPGRADE_REQUIRED) ||
+                      (_statusCode == HttpStatus.SWITCHING_PROTOCOLS);
+              for (int i = 0; i < tokens.length; i++) {
+                final bool isUpgrade = _caseInsensitiveCompare(
+                    "upgrade".codeUnits, tokens[i].codeUnits);
+                if ((isUpgrade && !isResponse) ||
+                    (isUpgrade && isResponse && isUpgradeCode)) {
+                  _connectionUpgrade = true;
+                }
+                _headers._add(headerField, tokens[i]);
+              }
+            } else {
+              _headers._add(headerField, headerValue);
+            }
+            _headerField.clear();
+            _headerValue.clear();
+
+            if (byte == _CharCode.CR) {
+              _state = _State.HEADER_ENDING;
+            } else if (byte == _CharCode.LF) {
+              _state = _State.HEADER_ENDING;
+              _index--; // Make the new state see the LF again.
+            } else {
+              // Start of new header field.
+              _headerField.add(_toLowerCaseByte(byte));
+              _state = _State.HEADER_FIELD;
+            }
+          }
+          break;
+
+        case _State.HEADER_ENDING:
+          _expect(byte, _CharCode.LF);
+          if (_headersEnd()) {
+            return;
+          } else {
+            break;
+          }
+          return;
+
+        case _State.CHUNK_SIZE_STARTING_CR:
+          _expect(byte, _CharCode.CR);
+          _state = _State.CHUNK_SIZE_STARTING_LF;
+          break;
+
+        case _State.CHUNK_SIZE_STARTING_LF:
+          _expect(byte, _CharCode.LF);
+          _state = _State.CHUNK_SIZE;
+          break;
+
+        case _State.CHUNK_SIZE:
+          if (byte == _CharCode.CR) {
+            _state = _State.CHUNK_SIZE_ENDING;
+          } else if (byte == _CharCode.SEMI_COLON) {
+            _state = _State.CHUNK_SIZE_EXTENSION;
+          } else {
+            int value = _expectHexDigit(byte);
+            _remainingContent = _remainingContent * 16 + value;
+          }
+          break;
+
+        case _State.CHUNK_SIZE_EXTENSION:
+          if (byte == _CharCode.CR) {
+            _state = _State.CHUNK_SIZE_ENDING;
+          }
+          break;
+
+        case _State.CHUNK_SIZE_ENDING:
+          _expect(byte, _CharCode.LF);
+          if (_remainingContent > 0) {
+            _state = _State.BODY;
+          } else {
+            _state = _State.CHUNKED_BODY_DONE_CR;
+          }
+          break;
+
+        case _State.CHUNKED_BODY_DONE_CR:
+          _expect(byte, _CharCode.CR);
+          _state = _State.CHUNKED_BODY_DONE_LF;
+          break;
+
+        case _State.CHUNKED_BODY_DONE_LF:
+          _expect(byte, _CharCode.LF);
+          _reset();
+          _closeIncoming();
+          break;
+
+        case _State.BODY:
+          // The body is not handled one byte at a time but in blocks.
+          _index--;
+          int dataAvailable = _buffer.length - _index;
+          if (_remainingContent >= 0 && dataAvailable > _remainingContent) {
+            dataAvailable = _remainingContent;
+          }
+          // Always present the data as a view. This way we can handle all
+          // cases like this, and the user will not experience different data
+          // typed (which could lead to polymorphic user code).
+          List<int> data = new Uint8List.view(
+              _buffer.buffer, _buffer.offsetInBytes + _index, dataAvailable);
+          _bodyController.add(data);
+          if (_remainingContent != -1) {
+            _remainingContent -= data.length;
+          }
+          _index += data.length;
+          if (_remainingContent == 0) {
+            if (!_chunked) {
+              _reset();
+              _closeIncoming();
+            } else {
+              _state = _State.CHUNK_SIZE_STARTING_CR;
+            }
+          }
+          break;
+
+        case _State.FAILURE:
+          // Should be unreachable.
+          assert(false);
+          break;
+
+        default:
+          // Should be unreachable.
+          assert(false);
+          break;
+      }
+    }
+
+    _parserCalled = false;
+    if (_buffer != null && _index == _buffer.length) {
+      // If all data is parsed release the buffer and resume receiving
+      // data.
+      _releaseBuffer();
+      if (_state != _State.UPGRADED && _state != _State.FAILURE) {
+        _socketSubscription.resume();
+      }
+    }
+  }
+
+  void _onData(List<int> buffer) {
+    _socketSubscription.pause();
+    assert(_buffer == null);
+    _buffer = buffer;
+    _index = 0;
+    _parse();
+  }
+
+  void _onDone() {
+    // onDone cancels the subscription.
+    _socketSubscription = null;
+    if (_state == _State.CLOSED || _state == _State.FAILURE) return;
+
+    if (_incoming != null) {
+      if (_state != _State.UPGRADED &&
+          !(_state == _State.START && !_requestParser) &&
+          !(_state == _State.BODY && !_chunked && _transferLength == -1)) {
+        _bodyController.addError(
+            new HttpException("Connection closed while receiving data"));
+      }
+      _closeIncoming(true);
+      _controller.close();
+      return;
+    }
+    // If the connection is idle the HTTP stream is closed.
+    if (_state == _State.START) {
+      if (!_requestParser) {
+        _reportError(new HttpException(
+            "Connection closed before full header was received"));
+      }
+      _controller.close();
+      return;
+    }
+
+    if (_state == _State.UPGRADED) {
+      _controller.close();
+      return;
+    }
+
+    if (_state < _State.FIRST_BODY_STATE) {
+      _state = _State.FAILURE;
+      // Report the error through the error callback if any. Otherwise
+      // throw the error.
+      _reportError(new HttpException(
+          "Connection closed before full header was received"));
+      _controller.close();
+      return;
+    }
+
+    if (!_chunked && _transferLength == -1) {
+      _state = _State.CLOSED;
+    } else {
+      _state = _State.FAILURE;
+      // Report the error through the error callback if any. Otherwise
+      // throw the error.
+      _reportError(
+          new HttpException("Connection closed before full body was received"));
+    }
+    _controller.close();
+  }
+
+  String get version {
+    switch (_httpVersion) {
+      case _HttpVersion.HTTP10:
+        return "1.0";
+      case _HttpVersion.HTTP11:
+        return "1.1";
+    }
+    return null;
+  }
+
+  int get messageType => _messageType;
+  int get transferLength => _transferLength;
+  bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED;
+  bool get persistentConnection => _persistentConnection;
+
+  void set isHead(bool value) {
+    if (value) _noMessageBody = true;
+  }
+
+  _HttpDetachedIncoming detachIncoming() {
+    // Simulate detached by marking as upgraded.
+    _state = _State.UPGRADED;
+    return new _HttpDetachedIncoming(_socketSubscription, readUnparsedData());
+  }
+
+  List<int> readUnparsedData() {
+    if (_buffer == null) return null;
+    if (_index == _buffer.length) return null;
+    var result = _buffer.sublist(_index);
+    _releaseBuffer();
+    return result;
+  }
+
+  void _reset() {
+    if (_state == _State.UPGRADED) return;
+    _state = _State.START;
+    _messageType = _MessageType.UNDETERMINED;
+    _headerField.clear();
+    _headerValue.clear();
+    _method.clear();
+    _uri_or_reason_phrase.clear();
+
+    _statusCode = 0;
+    _statusCodeLength = 0;
+
+    _httpVersion = _HttpVersion.UNDETERMINED;
+    _transferLength = -1;
+    _persistentConnection = false;
+    _connectionUpgrade = false;
+    _chunked = false;
+
+    _noMessageBody = false;
+    _remainingContent = -1;
+
+    _headers = null;
+  }
+
+  void _releaseBuffer() {
+    _buffer = null;
+    _index = null;
+  }
+
+  static bool _isTokenChar(int byte) {
+    return byte > 31 && byte < 128 && !_Const.SEPARATOR_MAP[byte];
+  }
+
+  static bool _isValueChar(int byte) {
+    return (byte > 31 && byte < 128) ||
+        (byte == _CharCode.SP) ||
+        (byte == _CharCode.HT);
+  }
+
+  static List<String> _tokenizeFieldValue(String headerValue) {
+    List<String> tokens = new List<String>();
+    int start = 0;
+    int index = 0;
+    while (index < headerValue.length) {
+      if (headerValue[index] == ",") {
+        tokens.add(headerValue.substring(start, index));
+        start = index + 1;
+      } else if (headerValue[index] == " " || headerValue[index] == "\t") {
+        start++;
+      }
+      index++;
+    }
+    tokens.add(headerValue.substring(start, index));
+    return tokens;
+  }
+
+  static int _toLowerCaseByte(int x) {
+    // Optimized version:
+    //  -  0x41 is 'A'
+    //  -  0x7f is ASCII mask
+    //  -  26 is the number of alpha characters.
+    //  -  0x20 is the delta between lower and upper chars.
+    return (((x - 0x41) & 0x7f) < 26) ? (x | 0x20) : x;
+  }
+
+  // expected should already be lowercase.
+  bool _caseInsensitiveCompare(List<int> expected, List<int> value) {
+    if (expected.length != value.length) return false;
+    for (int i = 0; i < expected.length; i++) {
+      if (expected[i] != _toLowerCaseByte(value[i])) return false;
+    }
+    return true;
+  }
+
+  int _expect(int val1, int val2) {
+    if (val1 != val2) {
+      throw new HttpException("Failed to parse HTTP");
+    }
+  }
+
+  int _expectHexDigit(int byte) {
+    if (0x30 <= byte && byte <= 0x39) {
+      return byte - 0x30; // 0 - 9
+    } else if (0x41 <= byte && byte <= 0x46) {
+      return byte - 0x41 + 10; // A - F
+    } else if (0x61 <= byte && byte <= 0x66) {
+      return byte - 0x61 + 10; // a - f
+    } else {
+      throw new HttpException("Failed to parse HTTP");
+    }
+  }
+
+  void _createIncoming(int transferLength) {
+    assert(_incoming == null);
+    assert(_bodyController == null);
+    assert(!_bodyPaused);
+    var incoming;
+    _bodyController = new StreamController<List<int>>(
+        sync: true,
+        onListen: () {
+          if (incoming != _incoming) return;
+          assert(_bodyPaused);
+          _bodyPaused = false;
+          _pauseStateChanged();
+        },
+        onPause: () {
+          if (incoming != _incoming) return;
+          assert(!_bodyPaused);
+          _bodyPaused = true;
+          _pauseStateChanged();
+        },
+        onResume: () {
+          if (incoming != _incoming) return;
+          assert(_bodyPaused);
+          _bodyPaused = false;
+          _pauseStateChanged();
+        },
+        onCancel: () {
+          if (incoming != _incoming) return;
+          if (_socketSubscription != null) {
+            _socketSubscription.cancel();
+          }
+          _closeIncoming(true);
+          _controller.close();
+        });
+    incoming = _incoming =
+        new _HttpIncoming(_headers, transferLength, _bodyController.stream);
+    _bodyPaused = true;
+    _pauseStateChanged();
+  }
+
+  void _closeIncoming([bool closing = false]) {
+    // Ignore multiple close (can happen in re-entrance).
+    if (_incoming == null) return;
+    var tmp = _incoming;
+    tmp.close(closing);
+    _incoming = null;
+    if (_bodyController != null) {
+      _bodyController.close();
+      _bodyController = null;
+    }
+    _bodyPaused = false;
+    _pauseStateChanged();
+  }
+
+  void _pauseStateChanged() {
+    if (_incoming != null) {
+      if (!_bodyPaused && !_parserCalled) {
+        _parse();
+      }
+    } else {
+      if (!_paused && !_parserCalled) {
+        _parse();
+      }
+    }
+  }
+
+  void _reportError(error, [stackTrace]) {
+    if (_socketSubscription != null) _socketSubscription.cancel();
+    _state = _State.FAILURE;
+    _controller.addError(error, stackTrace);
+    _controller.close();
+  }
+}
diff --git a/lib/src/http_session.dart b/lib/src/http_session.dart
new file mode 100644
index 0000000..9ef6395
--- /dev/null
+++ b/lib/src/http_session.dart
@@ -0,0 +1,184 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http_io;
+
+const String _DART_SESSION_ID = "DARTSESSID";
+
+// A _HttpSession is a node in a double-linked list, with _next and _prev being
+// the previous and next pointers.
+class _HttpSession implements HttpSession {
+  // Destroyed marked. Used by the http connection to see if a session is valid.
+  bool _destroyed = false;
+  bool _isNew = true;
+  DateTime _lastSeen;
+  Function _timeoutCallback;
+  _HttpSessionManager _sessionManager;
+  // Pointers in timeout queue.
+  _HttpSession _prev;
+  _HttpSession _next;
+  final String id;
+
+  final Map _data = new HashMap();
+
+  _HttpSession(this._sessionManager, this.id) : _lastSeen = new DateTime.now();
+
+  void destroy() {
+    _destroyed = true;
+    _sessionManager._removeFromTimeoutQueue(this);
+    _sessionManager._sessions.remove(id);
+  }
+
+  // Mark the session as seen. This will reset the timeout and move the node to
+  // the end of the timeout queue.
+  void _markSeen() {
+    _lastSeen = new DateTime.now();
+    _sessionManager._bumpToEnd(this);
+  }
+
+  DateTime get lastSeen => _lastSeen;
+
+  bool get isNew => _isNew;
+
+  void set onTimeout(void callback()) {
+    _timeoutCallback = callback;
+  }
+
+  // Map implementation:
+  bool containsValue(value) => _data.containsValue(value);
+  bool containsKey(key) => _data.containsKey(key);
+  operator [](key) => _data[key];
+  void operator []=(key, value) {
+    _data[key] = value;
+  }
+
+  putIfAbsent(key, ifAbsent) => _data.putIfAbsent(key, ifAbsent);
+  addAll(Map other) => _data.addAll(other);
+  remove(key) => _data.remove(key);
+  void clear() {
+    _data.clear();
+  }
+
+  void forEach(void f(key, value)) {
+    _data.forEach(f);
+  }
+
+  Iterable get keys => _data.keys;
+  Iterable get values => _data.values;
+  int get length => _data.length;
+  bool get isEmpty => _data.isEmpty;
+  bool get isNotEmpty => _data.isNotEmpty;
+
+  String toString() => 'HttpSession id:$id $_data';
+}
+
+// Private class used to manage all the active sessions. The sessions are stored
+// in two ways:
+//
+//  * In a map, mapping from ID to HttpSession.
+//  * In a linked list, used as a timeout queue.
+class _HttpSessionManager {
+  Map<String, _HttpSession> _sessions;
+  int _sessionTimeout = 20 * 60; // 20 mins.
+  _HttpSession _head;
+  _HttpSession _tail;
+  Timer _timer;
+
+  _HttpSessionManager() : _sessions = {};
+
+  String createSessionId() {
+    const int _KEY_LENGTH = 16; // 128 bits.
+    var data = _CryptoUtils.getRandomBytes(_KEY_LENGTH);
+    return _CryptoUtils.bytesToHex(data);
+  }
+
+  _HttpSession getSession(String id) => _sessions[id];
+
+  _HttpSession createSession() {
+    var id = createSessionId();
+    // TODO(ajohnsen): Consider adding a limit and throwing an exception.
+    // Should be very unlikely however.
+    while (_sessions.containsKey(id)) {
+      id = createSessionId();
+    }
+    var session = _sessions[id] = new _HttpSession(this, id);
+    _addToTimeoutQueue(session);
+    return session;
+  }
+
+  void set sessionTimeout(int timeout) {
+    _sessionTimeout = timeout;
+    _stopTimer();
+    _startTimer();
+  }
+
+  void close() {
+    _stopTimer();
+  }
+
+  void _bumpToEnd(_HttpSession session) {
+    _removeFromTimeoutQueue(session);
+    _addToTimeoutQueue(session);
+  }
+
+  void _addToTimeoutQueue(_HttpSession session) {
+    if (_head == null) {
+      assert(_tail == null);
+      _tail = _head = session;
+      _startTimer();
+    } else {
+      assert(_timer != null);
+      assert(_tail != null);
+      // Add to end.
+      _tail._next = session;
+      session._prev = _tail;
+      _tail = session;
+    }
+  }
+
+  void _removeFromTimeoutQueue(_HttpSession session) {
+    if (session._next != null) {
+      session._next._prev = session._prev;
+    }
+    if (session._prev != null) {
+      session._prev._next = session._next;
+    }
+    if (_head == session) {
+      // We removed the head element, start new timer.
+      _head = session._next;
+      _stopTimer();
+      _startTimer();
+    }
+    if (_tail == session) {
+      _tail = session._prev;
+    }
+    session._next = session._prev = null;
+  }
+
+  void _timerTimeout() {
+    _stopTimer(); // Clear timer.
+    assert(_head != null);
+    var session = _head;
+    session.destroy(); // Will remove the session from timeout queue and map.
+    if (session._timeoutCallback != null) {
+      session._timeoutCallback();
+    }
+  }
+
+  void _startTimer() {
+    assert(_timer == null);
+    if (_head != null) {
+      int seconds = new DateTime.now().difference(_head.lastSeen).inSeconds;
+      _timer = new Timer(
+          new Duration(seconds: _sessionTimeout - seconds), _timerTimeout);
+    }
+  }
+
+  void _stopTimer() {
+    if (_timer != null) {
+      _timer.cancel();
+      _timer = null;
+    }
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..695336a
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,7 @@
+name: http_io
+version: 0.10.0
+author: "Dart Team <misc@dartlang.org>"
+homepage: https://github.com/dart-lang/http_io
+description: HTTP Client and Server APIs.
+environment:
+  sdk: ">=2.0.0"