| Index: sdk/lib/io/http_impl.dart
|
| diff --git a/sdk/lib/io/http_impl.dart b/sdk/lib/io/http_impl.dart
|
| index 24aa1f8d7ad802a9ab5710dd7a48378be4071053..f33b8b7991533dfa3db3516737909d417bf789ef 100644
|
| --- a/sdk/lib/io/http_impl.dart
|
| +++ b/sdk/lib/io/http_impl.dart
|
| @@ -605,6 +605,78 @@ class _Cookie implements Cookie {
|
| }
|
|
|
|
|
| +// The close queue handles graceful closing of HTTP connections. When
|
| +// a connection is added to the queue it will enter a wait state
|
| +// waiting for all data written and possibly socket shutdown from
|
| +// peer.
|
| +class _CloseQueue {
|
| + _CloseQueue() : _q = new Set<_HttpConnectionBase>();
|
| +
|
| + void add(_HttpConnectionBase connection) {
|
| + void closeIfDone() {
|
| + // We only check for write closed here. This means that we are
|
| + // not waiting for the client to half-close the socket before
|
| + // fully closing the socket.
|
| + if (!connection._isWriteClosed) return;
|
| + _q.remove(connection);
|
| + connection._socket.close();
|
| + if (connection.onClosed != null) connection.onClosed();
|
| + }
|
| +
|
| + // If the connection is already fully closed don't insert it into the queue.
|
| + if (connection._isFullyClosed) {
|
| + connection._socket.close();
|
| + if (connection.onClosed != null) connection.onClosed();
|
| + return;
|
| + }
|
| +
|
| + _q.add(connection);
|
| +
|
| + // If output stream is not closed for writing close it now and
|
| + // wait for callback when closed.
|
| + if (!connection._isWriteClosed) {
|
| + connection._socket.outputStream.close();
|
| + connection._socket.outputStream.onClosed = () {
|
| + connection._state |= _HttpConnectionBase.WRITE_CLOSED;
|
| + closeIfDone();
|
| + };
|
| + } else {
|
| + connection._socket.outputStream.onClosed = () { assert(false); };
|
| + }
|
| +
|
| + // If socket is not closed for reading wait for callback.
|
| + if (!connection._isReadClosed) {
|
| + connection._socket.onClosed = () {
|
| + connection._state |= _HttpConnectionBase.READ_CLOSED;
|
| + // This is a nop, as we are not using the read closed
|
| + // information for anything. For both server and client
|
| + // connections the inbound message have been read to
|
| + // completion when the socket enters the close queue.
|
| + };
|
| + } else {
|
| + connection._socket.onClosed = () { assert(false); };
|
| + }
|
| +
|
| + // Ignore any data on a socket in the close queue.
|
| + connection._socket.onData = connection._socket.read;
|
| +
|
| + // If an error occurs immediately close the socket.
|
| + connection._socket.onError = (e) {
|
| + connection._state |= _HttpConnectionBase.WRITE_CLOSED;
|
| + closeIfDone();
|
| + };
|
| + }
|
| +
|
| + void shutdown() {
|
| + _q.forEach((_HttpConnectionBase connection) {
|
| + connection._socket.close();
|
| + });
|
| + }
|
| +
|
| + final Set<_HttpConnectionBase> _q;
|
| +}
|
| +
|
| +
|
| class _HttpRequestResponseBase {
|
| final int START = 0;
|
| final int HEADER_SENT = 1;
|
| @@ -696,14 +768,6 @@ class _HttpRequestResponseBase {
|
| }
|
| assert(_headResponse || _bodyBytesWritten == _contentLength);
|
| }
|
| - // If we are done writing the response, and either the client has
|
| - // closed or the connection is not persistent, we can close. Also
|
| - // if using HTTP 1.0 and the content length was not known we must
|
| - // close to indicate end of body.
|
| - if (!persistentConnection || _httpConnection._closing ||
|
| - (_protocolVersion == "1.0" && _contentLength < 0)) {
|
| - _httpConnection._close();
|
| - }
|
| return allWritten;
|
| }
|
|
|
| @@ -1013,17 +1077,6 @@ class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse {
|
| return _httpConnection._detachSocket();
|
| }
|
|
|
| - void _responseEnd() {
|
| - _ensureHeadersSent();
|
| - _state = DONE;
|
| - // Stop tracking no pending write events.
|
| - _httpConnection._onNoPendingWrites = null;
|
| - // Ensure that any trailing data is written.
|
| - _writeDone();
|
| - // Indicate to the connection that the response handling is done.
|
| - _httpConnection._responseDone();
|
| - }
|
| -
|
| // Delegate functions for the HttpOutputStream implementation.
|
| bool _streamWrite(List<int> buffer, bool copyBuffer) {
|
| if (_done) throw new HttpException("Response closed");
|
| @@ -1040,7 +1093,14 @@ class _HttpResponse extends _HttpRequestResponseBase implements HttpResponse {
|
| }
|
|
|
| void _streamClose() {
|
| - _responseEnd();
|
| + _ensureHeadersSent();
|
| + _state = DONE;
|
| + // Stop tracking no pending write events.
|
| + _httpConnection._onNoPendingWrites = null;
|
| + // Ensure that any trailing data is written.
|
| + _writeDone();
|
| + // Indicate to the connection that the response handling is done.
|
| + _httpConnection._responseClosed();
|
| }
|
|
|
| void _streamSetNoPendingWriteHandler(callback()) {
|
| @@ -1257,32 +1317,49 @@ class _HttpOutputStream extends _BaseOutputStream implements OutputStream {
|
|
|
|
|
| abstract class _HttpConnectionBase {
|
| - _HttpConnectionBase() : _httpParser = new _HttpParser(),
|
| - hashCode = _nextHashCode {
|
| + static const int IDLE = 0;
|
| + static const int ACTIVE = 1;
|
| + static const int REQUEST_DONE = 2;
|
| + static const int RESPONSE_DONE = 4;
|
| + static const int ALL_DONE = REQUEST_DONE | RESPONSE_DONE;
|
| + static const int READ_CLOSED = 8;
|
| + static const int WRITE_CLOSED = 16;
|
| + static const int FULLY_CLOSED = READ_CLOSED | WRITE_CLOSED;
|
| +
|
| + _HttpConnectionBase() : hashCode = _nextHashCode {
|
| _nextHashCode = (_nextHashCode + 1) & 0xFFFFFFF;
|
| }
|
|
|
| + bool get _isRequestDone => (_state & REQUEST_DONE) == REQUEST_DONE;
|
| + bool get _isResponseDone => (_state & RESPONSE_DONE) == RESPONSE_DONE;
|
| + bool get _isAllDone => (_state & ALL_DONE) == ALL_DONE;
|
| + bool get _isReadClosed => (_state & READ_CLOSED) == READ_CLOSED;
|
| + bool get _isWriteClosed => (_state & WRITE_CLOSED) == WRITE_CLOSED;
|
| + bool get _isFullyClosed => (_state & FULLY_CLOSED) == FULLY_CLOSED;
|
| +
|
| void _connectionEstablished(Socket socket) {
|
| _socket = socket;
|
| - // Register handler for socket events.
|
| - _socket.onData = _onData;
|
| - _socket.onClosed = _onClosed;
|
| - _socket.onError = _onError;
|
| + // Register handlers for socket events. All socket events are
|
| + // passed to the HTTP parser.
|
| + _socket.onData = () {
|
| + List<int> buffer = _socket.read();
|
| + if (buffer != null) {
|
| + _httpParser.streamData(buffer);
|
| + }
|
| + };
|
| + _socket.onClosed = _httpParser.streamDone;
|
| + _socket.onError = _httpParser.streamError;
|
| // Ignore errors in the socket output stream as this is getting
|
| // the same errors as the socket itself.
|
| _socket.outputStream.onError = (e) => null;
|
| }
|
|
|
| bool _write(List<int> data, [bool copyBuffer = false]) {
|
| - if (!_error && !_closing) {
|
| - return _socket.outputStream.write(data, copyBuffer);
|
| - }
|
| + return _socket.outputStream.write(data, copyBuffer);
|
| }
|
|
|
| bool _writeFrom(List<int> buffer, [int offset, int len]) {
|
| - if (!_error && !_closing) {
|
| - return _socket.outputStream.writeFrom(buffer, offset, len);
|
| - }
|
| + return _socket.outputStream.writeFrom(buffer, offset, len);
|
| }
|
|
|
| bool _flush() {
|
| @@ -1290,36 +1367,13 @@ abstract class _HttpConnectionBase {
|
| }
|
|
|
| bool _close() {
|
| - _closing = true;
|
| _socket.outputStream.close();
|
| }
|
|
|
| bool _destroy() {
|
| - _closing = true;
|
| _socket.close();
|
| }
|
|
|
| - void _onData() {
|
| - List<int> buffer = _socket.read();
|
| - if (buffer != null) {
|
| - _httpParser.writeList(buffer, 0, buffer.length);
|
| - }
|
| - }
|
| -
|
| - void _onClosed() {
|
| - _closing = true;
|
| - _onConnectionClosed(null);
|
| - }
|
| -
|
| - void _onError(e) {
|
| - // If an error occurs, make sure to close the socket if one is associated.
|
| - _error = true;
|
| - if (_socket != null) {
|
| - _socket.close();
|
| - }
|
| - _onConnectionClosed(e);
|
| - }
|
| -
|
| DetachedSocket _detachSocket() {
|
| _socket.onData = null;
|
| _socket.onClosed = null;
|
| @@ -1332,7 +1386,7 @@ abstract class _HttpConnectionBase {
|
| }
|
|
|
| HttpConnectionInfo get connectionInfo {
|
| - if (_socket == null || _closing || _error) return null;
|
| + if (_socket == null) return null;
|
| try {
|
| _HttpConnectionInfo info = new _HttpConnectionInfo();
|
| info.remoteHost = _socket.remoteHost;
|
| @@ -1343,21 +1397,18 @@ abstract class _HttpConnectionBase {
|
| return null;
|
| }
|
|
|
| - void _onConnectionClosed(e);
|
| - void _responseDone();
|
| -
|
| void set _onNoPendingWrites(void callback()) {
|
| - if (!_error) {
|
| - _socket.outputStream.onNoPendingWrites = callback;
|
| - }
|
| + _socket.outputStream.onNoPendingWrites = callback;
|
| }
|
|
|
| + int _state = IDLE;
|
| +
|
| Socket _socket;
|
| - bool _closing = false; // Is the socket closed by the client?
|
| - bool _error = false; // Is the socket closed due to an error?
|
| _HttpParser _httpParser;
|
|
|
| + // Callbacks.
|
| Function onDetach;
|
| + Function onClosed;
|
|
|
| // Hash code for HTTP connection. Currently this is just a counter.
|
| final int hashCode;
|
| @@ -1368,63 +1419,38 @@ abstract class _HttpConnectionBase {
|
| // HTTP server connection over a socket.
|
| class _HttpConnection extends _HttpConnectionBase {
|
| _HttpConnection(HttpServer this._server) {
|
| + _httpParser = new _HttpParser.requestParser();
|
| // Register HTTP parser callbacks.
|
| - _httpParser.requestStart =
|
| - (method, uri, version) => _onRequestStart(method, uri, version);
|
| - _httpParser.responseStart =
|
| - (statusCode, reasonPhrase, version) =>
|
| - _onResponseStart(statusCode, reasonPhrase, version);
|
| - _httpParser.headerReceived =
|
| - (name, value) => _onHeaderReceived(name, value);
|
| - _httpParser.headersComplete = () => _onHeadersComplete();
|
| - _httpParser.dataReceived = (data) => _onDataReceived(data);
|
| - _httpParser.dataEnd = (close) => _onDataEnd(close);
|
| - _httpParser.error = (e) => _onError(e);
|
| - }
|
| -
|
| - void _onConnectionClosed(e) {
|
| - // Don't report errors when HTTP parser is in idle state. Clients
|
| - // can close the connection and cause a connection reset by peer
|
| - // error which is OK.
|
| - if (e != null && !_httpParser.isIdle) {
|
| - onError(e);
|
| - // Propagate the error to the streams.
|
| - if (_request != null && _request._streamErrorHandler != null) {
|
| - _request._streamErrorHandler(e);
|
| - }
|
| - if (_response != null && _response._streamErrorHandler != null) {
|
| - _response._streamErrorHandler(e);
|
| - }
|
| - }
|
| + _httpParser.requestStart = _onRequestStart;
|
| + _httpParser.headerReceived = _onHeaderReceived;
|
| + _httpParser.headersComplete = _onHeadersComplete;
|
| + _httpParser.dataReceived = _onDataReceived;
|
| + _httpParser.dataEnd = _onDataEnd;
|
| + _httpParser.error = _onError;
|
| + _httpParser.closed = _onClosed;
|
| + _httpParser.responseStart = (statusCode, reasonPhrase, version) {
|
| + assert(false);
|
| + };
|
| + }
|
|
|
| - // If currently not processing any request close the socket when
|
| - // we are done writing the response.
|
| - if (_httpParser.isIdle) {
|
| - // If the httpParser is idle and we get an error from the
|
| - // connection we deal with that as a closed connection and not
|
| - // as an error. When the client disappears we get a connection
|
| - // reset by peer and that is OK.
|
| - if (e != null) {
|
| - onClosed();
|
| - } else {
|
| - _socket.outputStream.onClosed = () {
|
| - _destroy();
|
| - onClosed();
|
| - };
|
| - // If the client closes and we are done writing the response
|
| - // the connection should be closed.
|
| - if (_response == null) _close();
|
| - }
|
| - } else {
|
| - // Processing a request.
|
| - if (e == null) {
|
| - // Indicate connection close to the HTTP parser.
|
| - _httpParser.connectionClosed();
|
| - }
|
| + void _onClosed() {
|
| + _state |= _HttpConnectionBase.READ_CLOSED;
|
| + }
|
| +
|
| + void _onError(e) {
|
| + onError(e);
|
| + // Propagate the error to the streams.
|
| + if (_request != null && _request._streamErrorHandler != null) {
|
| + _request._streamErrorHandler(e);
|
| }
|
| + if (_response != null && _response._streamErrorHandler != null) {
|
| + _response._streamErrorHandler(e);
|
| + }
|
| + if (_socket != null) _socket.close();
|
| }
|
|
|
| void _onRequestStart(String method, String uri, String version) {
|
| + _state = _HttpConnectionBase.ACTIVE;
|
| // Create new request and response objects for this request.
|
| _request = new _HttpRequest(this);
|
| _response = new _HttpResponse(this);
|
| @@ -1434,10 +1460,6 @@ class _HttpConnection extends _HttpConnectionBase {
|
| _response._headResponse = method == "HEAD";
|
| }
|
|
|
| - void _onResponseStart(int statusCode, String reasonPhrase, String version) {
|
| - // TODO(sgjesse): Error handling.
|
| - }
|
| -
|
| void _onHeaderReceived(String name, String value) {
|
| _request._onHeaderReceived(name, value);
|
| }
|
| @@ -1454,20 +1476,34 @@ class _HttpConnection extends _HttpConnectionBase {
|
| _request._onDataReceived(data);
|
| }
|
|
|
| + void _checkDone() {
|
| + if (_isAllDone) {
|
| + // If we are done writing the response, and either the client
|
| + // has closed or the connection is not persistent, we must
|
| + // close. Also if using HTTP 1.0 and the content length was not
|
| + // known we must close to indicate end of body.
|
| + bool close =
|
| + !_response.persistentConnection ||
|
| + (_response._protocolVersion == "1.0" && _response._contentLength < 0);
|
| + _request = null;
|
| + _response = null;
|
| + if (_isReadClosed || close) {
|
| + _server._closeQueue.add(this);
|
| + } else {
|
| + _state = _HttpConnectionBase.IDLE;
|
| + }
|
| + }
|
| + }
|
| +
|
| void _onDataEnd(bool close) {
|
| _request._onDataEnd();
|
| + _state |= _HttpConnectionBase.REQUEST_DONE;
|
| + _checkDone();
|
| }
|
|
|
| - void _responseDone() {
|
| - // If the connection is closing then close the output stream to
|
| - // fully close the socket.
|
| - if (_closing) {
|
| - _socket.outputStream.onClosed = () {
|
| - _socket.close();
|
| - onClosed();
|
| - };
|
| - }
|
| - _response = null;
|
| + void _responseClosed() {
|
| + _state |= _HttpConnectionBase.RESPONSE_DONE;
|
| + _checkDone();
|
| }
|
|
|
| HttpServer _server;
|
| @@ -1476,7 +1512,6 @@ class _HttpConnection extends _HttpConnectionBase {
|
|
|
| // Callbacks.
|
| Function onRequestReceived;
|
| - Function onClosed;
|
| Function onError;
|
| }
|
|
|
| @@ -1491,7 +1526,8 @@ class _RequestHandlerRegistration {
|
| // managed by the server and as requests are received the request.
|
| class _HttpServer implements HttpServer {
|
| _HttpServer() : _connections = new Set<_HttpConnection>(),
|
| - _handlers = new List<_RequestHandlerRegistration>();
|
| + _handlers = new List<_RequestHandlerRegistration>(),
|
| + _closeQueue = new _CloseQueue();
|
|
|
| void listen(String host, int port, {int backlog: 128}) {
|
| listenOn(new ServerSocket(host, port, backlog));
|
| @@ -1532,6 +1568,7 @@ class _HttpServer implements HttpServer {
|
| }
|
|
|
| void close() {
|
| + _closeQueue.shutdown();
|
| if (_sessionManagerInstance != null) {
|
| _sessionManagerInstance.close();
|
| _sessionManagerInstance = null;
|
| @@ -1602,6 +1639,7 @@ class _HttpServer implements HttpServer {
|
| List<_RequestHandlerRegistration> _handlers;
|
| Object _defaultHandler;
|
| Function _onError;
|
| + _CloseQueue _closeQueue;
|
| _HttpSessionManager _sessionManagerInstance;
|
| }
|
|
|
| @@ -1659,7 +1697,7 @@ class _HttpClientRequest
|
| _httpConnection._onNoPendingWrites = null;
|
| // Ensure that any trailing data is written.
|
| _writeDone();
|
| - _connection._requestDone();
|
| + _connection._requestClosed();
|
| }
|
|
|
| void _streamSetNoPendingWriteHandler(callback()) {
|
| @@ -1774,10 +1812,6 @@ class _HttpClientResponse
|
| return _inputStream;
|
| }
|
|
|
| - void _onRequestStart(String method, String uri, String version) {
|
| - // TODO(sgjesse): Error handling
|
| - }
|
| -
|
| void _onResponseStart(int statusCode, String reasonPhrase, String version) {
|
| _statusCode = statusCode;
|
| _reasonPhrase = reasonPhrase;
|
| @@ -1906,7 +1940,6 @@ class _HttpClientResponse
|
| }
|
|
|
| void _onDataEnd() {
|
| - _connection._responseDone();
|
| if (_inputStream != null) {
|
| _inputStream._closeReceived();
|
| } else {
|
| @@ -1946,64 +1979,54 @@ class _HttpClientResponse
|
|
|
| class _HttpClientConnection
|
| extends _HttpConnectionBase implements HttpClientConnection {
|
| - static const int NONE = 0;
|
| - static const int REQUEST_DONE = 1;
|
| - static const int RESPONSE_DONE = 2;
|
| - static const int ALL_DONE = REQUEST_DONE | RESPONSE_DONE;
|
|
|
| - _HttpClientConnection(_HttpClient this._client);
|
| + _HttpClientConnection(_HttpClient this._client) {
|
| + _httpParser = new _HttpParser.responseParser();
|
| + }
|
|
|
| void _connectionEstablished(_SocketConnection socketConn) {
|
| super._connectionEstablished(socketConn._socket);
|
| _socketConn = socketConn;
|
| // Register HTTP parser callbacks.
|
| - _httpParser.requestStart =
|
| - (method, uri, version) => _onRequestStart(method, uri, version);
|
| - _httpParser.responseStart =
|
| - (statusCode, reasonPhrase, version) =>
|
| - _onResponseStart(statusCode, reasonPhrase, version);
|
| - _httpParser.headerReceived =
|
| - (name, value) => _onHeaderReceived(name, value);
|
| - _httpParser.headersComplete = () => _onHeadersComplete();
|
| - _httpParser.dataReceived = (data) => _onDataReceived(data);
|
| - _httpParser.dataEnd = (closed) => _onDataEnd(closed);
|
| - _httpParser.error = (e) => _onError(e);
|
| + _httpParser.responseStart = _onResponseStart;
|
| + _httpParser.headerReceived = _onHeaderReceived;
|
| + _httpParser.headersComplete = _onHeadersComplete;
|
| + _httpParser.dataReceived = _onDataReceived;
|
| + _httpParser.dataEnd = _onDataEnd;
|
| + _httpParser.error = _onError;
|
| + _httpParser.closed = _onClosed;
|
| + _httpParser.requestStart = (method, uri, version) { assert(false); };
|
| + _state = _HttpConnectionBase.ACTIVE;
|
| }
|
|
|
| - bool get _isRequestDone => (_state & REQUEST_DONE) == REQUEST_DONE;
|
| - bool get _isResponseDone => (_state & RESPONSE_DONE) == RESPONSE_DONE;
|
| - bool get _isAllDone => (_state & ALL_DONE) == ALL_DONE;
|
| -
|
| void _checkSocketDone() {
|
| if (_isAllDone) {
|
| - if (!_closing) {
|
| + // If we are done writing the response, and either the server
|
| + // has closed or the connection is not persistent, we must
|
| + // close.
|
| + if (_isReadClosed || !_response.persistentConnection) {
|
| + this.onClosed = () {
|
| + _client._closedSocketConnection(_socketConn);
|
| + };
|
| + _client._closeQueue.add(this);
|
| + } else {
|
| _client._returnSocketConnection(_socketConn);
|
| - }
|
| - _socket = null;
|
| - _socketConn = null;
|
| - assert(_pendingRedirect == null || _pendingRetry == null);
|
| - if (_pendingRedirect != null) {
|
| - _doRedirect(_pendingRedirect);
|
| - _pendingRedirect = null;
|
| - } else if (_pendingRetry != null) {
|
| - _doRetry(_pendingRetry);
|
| - _pendingRetry = null;
|
| + _socket = null;
|
| + _socketConn = null;
|
| + assert(_pendingRedirect == null || _pendingRetry == null);
|
| + if (_pendingRedirect != null) {
|
| + _doRedirect(_pendingRedirect);
|
| + _pendingRedirect = null;
|
| + } else if (_pendingRetry != null) {
|
| + _doRetry(_pendingRetry);
|
| + _pendingRetry = null;
|
| + }
|
| }
|
| }
|
| }
|
|
|
| - void _requestDone() {
|
| - _state |= REQUEST_DONE;
|
| - _checkSocketDone();
|
| - }
|
| -
|
| - void _responseDone() {
|
| - if (_closing) {
|
| - if (_socket != null) {
|
| - _socket.close();
|
| - }
|
| - }
|
| - _state |= RESPONSE_DONE;
|
| + void _requestClosed() {
|
| + _state |= _HttpConnectionBase.REQUEST_DONE;
|
| _checkSocketDone();
|
| }
|
|
|
| @@ -2020,32 +2043,24 @@ class _HttpClientConnection
|
| return _detachSocket();
|
| }
|
|
|
| - void _onConnectionClosed(e) {
|
| + void _onClosed() {
|
| + _state |= _HttpConnectionBase.READ_CLOSED;
|
| + }
|
| +
|
| + void _onError(e) {
|
| // Socket is closed either due to an error or due to normal socket close.
|
| - if (e != null) {
|
| - if (_onErrorCallback != null) {
|
| - _onErrorCallback(e);
|
| - } else {
|
| - throw e;
|
| - }
|
| - }
|
| - _closing = true;
|
| - if (e != null) {
|
| - // Propagate the error to the streams.
|
| - if (_response != null && _response._streamErrorHandler != null) {
|
| - _response._streamErrorHandler(e);
|
| - }
|
| - _responseDone();
|
| + if (_onErrorCallback != null) {
|
| + _onErrorCallback(e);
|
| } else {
|
| - // If there was no socket error the socket was closed
|
| - // normally. Indicate closing to the HTTP Parser as there might
|
| - // still be an HTTP error.
|
| - _httpParser.connectionClosed();
|
| + throw e;
|
| + }
|
| + // Propagate the error to the streams.
|
| + if (_response != null && _response._streamErrorHandler != null) {
|
| + _response._streamErrorHandler(e);
|
| + }
|
| + if (_socketConn != null) {
|
| + _client._closeSocketConnection(_socketConn);
|
| }
|
| - }
|
| -
|
| - void _onRequestStart(String method, String uri, String version) {
|
| - // TODO(sgjesse): Error handling.
|
| }
|
|
|
| void _onResponseStart(int statusCode, String reasonPhrase, String version) {
|
| @@ -2065,8 +2080,9 @@ class _HttpClientConnection
|
| }
|
|
|
| void _onDataEnd(bool close) {
|
| - if (close) _closing = true;
|
| _response._onDataEnd();
|
| + _state |= _HttpConnectionBase.RESPONSE_DONE;
|
| + _checkSocketDone();
|
| }
|
|
|
| void set onRequest(void handler(HttpClientRequest request)) {
|
| @@ -2087,7 +2103,7 @@ class _HttpClientConnection
|
| _response = null;
|
|
|
| // Retry the URL using the same connection instance.
|
| - _state = NONE;
|
| + _state = _HttpConnectionBase.IDLE;
|
| _client._openUrl(retry.method, retry.location, this);
|
| }
|
|
|
| @@ -2131,8 +2147,6 @@ class _HttpClientConnection
|
| }
|
| }
|
|
|
| - int _state = NONE;
|
| -
|
| List<RedirectInfo> get redirects => _redirects;
|
|
|
| Function _onRequest;
|
| @@ -2172,6 +2186,13 @@ class _SocketConnection {
|
| _returnTime = new Date.now();
|
| }
|
|
|
| + void _close() {
|
| + _socket.onData = null;
|
| + _socket.onClosed = null;
|
| + _socket.onError = null;
|
| + _socket.close();
|
| + }
|
| +
|
| Duration _idleTime(Date now) => now.difference(_returnTime);
|
|
|
| int get hashCode => _socket.hashCode;
|
| @@ -2241,6 +2262,7 @@ class _HttpClient implements HttpClient {
|
|
|
| _HttpClient() : _openSockets = new Map(),
|
| _activeSockets = new Set(),
|
| + _closeQueue = new _CloseQueue(),
|
| credentials = new List<_Credentials>(),
|
| _shutdown = false;
|
|
|
| @@ -2299,15 +2321,16 @@ class _HttpClient implements HttpClient {
|
| set findProxy(String f(Uri uri)) => _findProxy = f;
|
|
|
| void shutdown() {
|
| - _openSockets.forEach((String key, Queue<_SocketConnection> connections) {
|
| - while (!connections.isEmpty) {
|
| - _SocketConnection socketConn = connections.removeFirst();
|
| - socketConn._socket.close();
|
| - }
|
| - });
|
| - _activeSockets.forEach((_SocketConnection socketConn) {
|
| - socketConn._socket.close();
|
| - });
|
| + _closeQueue.shutdown();
|
| + _openSockets.forEach((String key, Queue<_SocketConnection> connections) {
|
| + while (!connections.isEmpty) {
|
| + _SocketConnection socketConn = connections.removeFirst();
|
| + socketConn._socket.close();
|
| + }
|
| + });
|
| + _activeSockets.forEach((_SocketConnection socketConn) {
|
| + socketConn._socket.close();
|
| + });
|
| if (_evictionTimer != null) _cancelEvictionTimer();
|
| _shutdown = true;
|
| }
|
| @@ -2503,6 +2526,15 @@ class _HttpClient implements HttpClient {
|
| sockets.addFirst(socketConn);
|
| }
|
|
|
| + void _closeSocketConnection(_SocketConnection socketConn) {
|
| + socketConn._close();
|
| + _activeSockets.remove(socketConn);
|
| + }
|
| +
|
| + void _closedSocketConnection(_SocketConnection socketConn) {
|
| + _activeSockets.remove(socketConn);
|
| + }
|
| +
|
| _Credentials _findCredentials(Uri url, [_AuthenticationScheme scheme]) {
|
| // Look for credentials.
|
| _Credentials cr =
|
| @@ -2527,6 +2559,7 @@ class _HttpClient implements HttpClient {
|
| Function _onOpen;
|
| Map<String, Queue<_SocketConnection>> _openSockets;
|
| Set<_SocketConnection> _activeSockets;
|
| + _CloseQueue _closeQueue;
|
| List<_Credentials> credentials;
|
| Timer _evictionTimer;
|
| Function _findProxy;
|
|
|