| 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)';
|
| -}
|
|
|