Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(532)

Unified Diff: pkg/dev_compiler/tool/input_sdk/lib/io/http_impl.dart

Issue 2698353003: unfork DDC's copy of most SDK libraries (Closed)
Patch Set: revert core_patch Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/dev_compiler/tool/input_sdk/lib/io/http_impl.dart
diff --git a/pkg/dev_compiler/tool/input_sdk/lib/io/http_impl.dart b/pkg/dev_compiler/tool/input_sdk/lib/io/http_impl.dart
deleted file mode 100644
index dff98f04f64c30e13a008bc1de37d282f7082a16..0000000000000000000000000000000000000000
--- a/pkg/dev_compiler/tool/input_sdk/lib/io/http_impl.dart
+++ /dev/null
@@ -1,2890 +0,0 @@
-// 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 dart.io;
-
-const int _OUTGOING_BUFFER_SIZE = 8 * 1024;
-
-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.fromIterable([]).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;
- }
- });
- }
-}
-
-
-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() {
- Uint8List buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
- int offset = 0;
-
- void write(List<int> bytes) {
- int len = bytes.length;
- for (int i = 0; i < len; i++) {
- buffer[offset + i] = bytes[i];
- }
- offset += len;
- }
-
- // Write status line.
- if (headers.protocolVersion == "1.1") {
- write(_Const.HTTP11);
- } else {
- write(_Const.HTTP10);
- }
- buffer[offset++] = _CharCode.SP;
- write(statusCode.toString().codeUnits);
- buffer[offset++] = _CharCode.SP;
- write(reasonPhrase.codeUnits);
- buffer[offset++] = _CharCode.CR;
- buffer[offset++] = _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.
- offset = headers._write(buffer, offset);
- buffer[offset++] = _CharCode.CR;
- buffer[offset++] = _CharCode.LF;
- _outgoing.setHeader(buffer, offset);
- }
-
- 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((_) => response.redirect());
- } else {
- // End with exception, too many redirects.
- future = response.drain()
- .then((_) => new Future.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() {
- Uint8List buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
- int offset = 0;
-
- void write(List<int> bytes) {
- int len = bytes.length;
- for (int i = 0; i < len; i++) {
- buffer[offset + i] = bytes[i];
- }
- offset += len;
- }
-
- // Write the request method.
- write(method.codeUnits);
- buffer[offset++] = _CharCode.SP;
- // Write the request URI.
- write(_requestUri().codeUnits);
- buffer[offset++] = _CharCode.SP;
- // Write HTTP/1.1.
- write(_Const.HTTP11);
- buffer[offset++] = _CharCode.CR;
- buffer[offset++] = _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.
- offset = headers._write(buffer, offset);
- buffer[offset++] = _CharCode.CR;
- buffer[offset++] = _CharCode.LF;
- _outgoing.setHeader(buffer, offset);
- }
-}
-
-// Used by _HttpOutgoing as a target of a chunked converter for gzip
-// compression.
-class _HttpGZipSink extends ByteConversionSink {
- final Function _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 compressionm
-// - 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 _doneCompleter = new Completer();
- 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).
- Function _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}) {
- Future write() {
- try {
- outbound._writeHeader();
- } catch (_) {
- // Headers too large.
- return new Future.error(new HttpException(
- "Headers size exceeded the of '$_OUTGOING_BUFFER_SIZE'"
- " bytes"));
- }
- return null;
- }
-
- 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) {
- return write();
- }
- 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((_) => write());
- }
- return write();
- }
-
-
- 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();
- }
- var 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(
- onPause: () => sub.pause(),
- onResume: () => sub.resume(),
- sync: true);
-
- void onData(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 get done => _doneCompleter.future;
-
- void setHeader(List<int> data, int length) {
- assert(_length == 0);
- assert(data.length == _OUTGOING_BUFFER_SIZE);
- _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(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(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 _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((s) {
- // Request sent, set up response completer.
- _nextResponseCompleter = new Completer();
-
- // 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(host, port, proxy, callback) {
- _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();
- _pending.add(() {
- connect(uriHost, uriPort, proxy, client)
- .then(completer.complete, onError: completer.completeError);
- });
- 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(SecurityContext 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 connnection 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(() => 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 _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() {
- try { _socket._owner = this; } catch (_) { print(_); }
- _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((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((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;
- _serverSocket._owner = this;
- }
-
- _HttpServer.listenOn(this._serverSocket) : _closeServer = false {
- _controller = new StreamController<HttpRequest>(sync: true,
- onCancel: close);
- idleTimeout = const Duration(seconds: 120);
- _servers[_serviceId] = this;
- try { _serverSocket._owner = this; } catch (_) {}
- }
-
- 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 _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;
-
- _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<Socket> addStream(Stream<List<int>> stream) {
- return _socket.addStream(stream);
- }
-
- void destroy() { _socket.destroy(); }
-
- Future flush() => _socket.flush();
-
- Future close() => _socket.close();
-
- Future<Socket> 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) => _socket._toJSON(ref);
- void set _owner(owner) { _socket._owner = owner; }
-}
-
-
-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(_IOCrypto.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)';
-}
« no previous file with comments | « pkg/dev_compiler/tool/input_sdk/lib/io/http_headers.dart ('k') | pkg/dev_compiler/tool/input_sdk/lib/io/http_parser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698