OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of dart.io; | 5 part of dart.io; |
6 | 6 |
7 const int _OUTGOING_BUFFER_SIZE = 8 * 1024; | 7 const int _OUTGOING_BUFFER_SIZE = 8 * 1024; |
8 | 8 |
9 class _HttpIncoming extends Stream<List<int>> { | 9 class _HttpIncoming extends Stream<List<int>> { |
10 final int _transferLength; | 10 final int _transferLength; |
(...skipping 1484 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1495 } | 1495 } |
1496 | 1496 |
1497 class _ConnnectionInfo { | 1497 class _ConnnectionInfo { |
1498 final _HttpClientConnection connection; | 1498 final _HttpClientConnection connection; |
1499 final _Proxy proxy; | 1499 final _Proxy proxy; |
1500 | 1500 |
1501 _ConnnectionInfo(this.connection, this.proxy); | 1501 _ConnnectionInfo(this.connection, this.proxy); |
1502 } | 1502 } |
1503 | 1503 |
1504 | 1504 |
1505 class _ConnectionTarget { | |
1506 // Unique key for this connection target. | |
1507 final String key; | |
1508 final Set<_HttpClientConnection> _idle = new HashSet(); | |
1509 final Set<_HttpClientConnection> _active = new HashSet(); | |
1510 | |
1511 _ConnectionTarget(this.key); | |
1512 | |
1513 bool get isEmpty => _idle.isEmpty && _active.isEmpty; | |
1514 | |
1515 bool get hasIdle => _idle.isNotEmpty; | |
1516 | |
1517 bool get hasActive => _active.isNotEmpty; | |
1518 | |
1519 _HttpClientConnection takeIdle() { | |
1520 assert(hasIdle); | |
1521 _HttpClientConnection connection = _idle.first; | |
1522 _idle.remove(connection); | |
1523 connection.stopTimer(); | |
1524 _active.add(connection); | |
1525 return connection; | |
1526 } | |
1527 | |
1528 void addNewActive(_HttpClientConnection connection) { | |
1529 _active.add(connection); | |
1530 } | |
1531 | |
1532 void returnConnection(_HttpClientConnection connection) { | |
1533 assert(_active.contains(connection)); | |
1534 _active.remove(connection); | |
1535 _idle.add(connection); | |
1536 } | |
1537 | |
1538 void connectionClosed(_HttpClientConnection connection) { | |
1539 assert(!_active.contains(connection) || !_idle.contains(connection)); | |
1540 _active.remove(connection); | |
1541 _idle.remove(connection); | |
1542 } | |
1543 | |
1544 void close(bool force) { | |
1545 for (var c in _idle.toList()) { | |
1546 c.close(); | |
1547 } | |
1548 if (force) { | |
1549 for (var c in _active.toList()) { | |
1550 c.destroy(); | |
1551 } | |
1552 } | |
1553 } | |
1554 } | |
1555 | |
1556 | |
1505 class _HttpClient implements HttpClient { | 1557 class _HttpClient implements HttpClient { |
1506 bool _closing = false; | 1558 bool _closing = false; |
1507 final Map<String, Set<_HttpClientConnection>> _idleConnections | 1559 final Map<String, _ConnectionTarget> _connectionTargets |
1508 = new HashMap<String, Set<_HttpClientConnection>>(); | 1560 = new HashMap<String, _ConnectionTarget>(); |
1509 final Map<String, Set<_HttpClientConnection>> _activeConnections | |
1510 = new HashMap<String, Set<_HttpClientConnection>>(); | |
1511 final List<_Credentials> _credentials = []; | 1561 final List<_Credentials> _credentials = []; |
1512 final List<_ProxyCredentials> _proxyCredentials = []; | 1562 final List<_ProxyCredentials> _proxyCredentials = []; |
1513 Function _authenticate; | 1563 Function _authenticate; |
1514 Function _authenticateProxy; | 1564 Function _authenticateProxy; |
1515 Function _findProxy = HttpClient.findProxyFromEnvironment; | 1565 Function _findProxy = HttpClient.findProxyFromEnvironment; |
1516 Duration _idleTimeout = const Duration(seconds: 15); | 1566 Duration _idleTimeout = const Duration(seconds: 15); |
1517 Function _badCertificateCallback; | 1567 Function _badCertificateCallback; |
1518 | 1568 |
1519 Timer _noActiveTimer; | 1569 Timer _noActiveTimer; |
1520 | 1570 |
1521 Duration get idleTimeout => _idleTimeout; | 1571 Duration get idleTimeout => _idleTimeout; |
1522 | 1572 |
1523 String userAgent = _getHttpVersion(); | 1573 String userAgent = _getHttpVersion(); |
1524 | 1574 |
1525 void set idleTimeout(Duration timeout) { | 1575 void set idleTimeout(Duration timeout) { |
1526 _idleTimeout = timeout; | 1576 _idleTimeout = timeout; |
1527 _idleConnections.values.forEach( | 1577 for (var c in _connectionTargets.values) { |
1528 (l) => l.forEach((c) { | 1578 for (var idle in c.idle) { |
1529 // Reset timer. This is fine, as it's not happening often. | 1579 // Reset timer. This is fine, as it's not happening often. |
1530 c.stopTimer(); | 1580 idle.stopTimer(); |
1531 c.startTimer(); | 1581 idle.startTimer(); |
1532 })); | 1582 } |
1583 } | |
1533 } | 1584 } |
1534 | 1585 |
1535 set badCertificateCallback(bool callback(X509Certificate cert, | 1586 set badCertificateCallback(bool callback(X509Certificate cert, |
1536 String host, | 1587 String host, |
1537 int port)) { | 1588 int port)) { |
1538 _badCertificateCallback = callback; | 1589 _badCertificateCallback = callback; |
1539 } | 1590 } |
1540 | 1591 |
1541 | 1592 |
1542 Future<HttpClientRequest> open(String method, | 1593 Future<HttpClientRequest> open(String method, |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1578 | 1629 |
1579 Future<HttpClientRequest> headUrl(Uri url) => _openUrl("head", url); | 1630 Future<HttpClientRequest> headUrl(Uri url) => _openUrl("head", url); |
1580 | 1631 |
1581 Future<HttpClientRequest> patch(String host, int port, String path) | 1632 Future<HttpClientRequest> patch(String host, int port, String path) |
1582 => open("patch", host, port, path); | 1633 => open("patch", host, port, path); |
1583 | 1634 |
1584 Future<HttpClientRequest> patchUrl(Uri url) => _openUrl("patch", url); | 1635 Future<HttpClientRequest> patchUrl(Uri url) => _openUrl("patch", url); |
1585 | 1636 |
1586 void close({bool force: false}) { | 1637 void close({bool force: false}) { |
1587 _closing = true; | 1638 _closing = true; |
1588 // Create flattened copy of _idleConnections, as 'destory' will manipulate | 1639 _connectionTargets.values.toList().forEach((c) => c.close(force)); |
1589 // it. | 1640 assert(!_connectionTargets.values.any((s) => s.hasIdle)); |
1590 var idle = _idleConnections.values.fold( | 1641 assert(!force || _connectionTargets.isEmpty); |
1591 [], | |
1592 (l, e) { | |
1593 l.addAll(e); | |
1594 return l; | |
1595 }); | |
1596 idle.forEach((e) { | |
1597 e.close(); | |
1598 }); | |
1599 assert(_idleConnections.isEmpty); | |
1600 if (force) { | |
1601 for (var connection in | |
1602 _activeConnections.values.expand((s) => s).toList()) { | |
1603 connection.destroy(); | |
1604 } | |
1605 assert(_activeConnections.isEmpty); | |
1606 _activeConnections.clear(); | |
1607 } | |
1608 } | 1642 } |
1609 | 1643 |
1610 set authenticate(Future<bool> f(Uri url, String scheme, String realm)) { | 1644 set authenticate(Future<bool> f(Uri url, String scheme, String realm)) { |
1611 _authenticate = f; | 1645 _authenticate = f; |
1612 } | 1646 } |
1613 | 1647 |
1614 void addCredentials(Uri url, String realm, HttpClientCredentials cr) => | 1648 void addCredentials(Uri url, String realm, HttpClientCredentials cr) => |
1615 _credentials.add(new _SiteCredentials(url, realm, cr)); | 1649 _credentials.add(new _SiteCredentials(url, realm, cr)); |
1616 | 1650 |
1617 set authenticateProxy( | 1651 set authenticateProxy( |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1696 } | 1730 } |
1697 } | 1731 } |
1698 return request | 1732 return request |
1699 ..headers.chunkedTransferEncoding = false | 1733 ..headers.chunkedTransferEncoding = false |
1700 ..contentLength = 0; | 1734 ..contentLength = 0; |
1701 }); | 1735 }); |
1702 } | 1736 } |
1703 | 1737 |
1704 // Return a live connection to the idle pool. | 1738 // Return a live connection to the idle pool. |
1705 void _returnConnection(_HttpClientConnection connection) { | 1739 void _returnConnection(_HttpClientConnection connection) { |
1706 var key = connection.key; | 1740 _connectionTargets[connection.key].returnConnection(connection); |
1707 _activeConnections[key].remove(connection); | |
1708 if (_activeConnections[key].isEmpty) { | |
1709 _activeConnections.remove(key); | |
1710 } | |
1711 if (_closing) { | |
1712 connection.close(); | |
1713 return; | |
1714 } | |
1715 _idleConnections | |
1716 .putIfAbsent(key, () => new HashSet()) | |
1717 .add(connection); | |
1718 connection.startTimer(); | 1741 connection.startTimer(); |
1719 _updateTimers(); | 1742 _updateTimers(); |
1720 } | 1743 } |
1721 | 1744 |
1722 // Remove a closed connnection from the active set. | 1745 // Remove a closed connnection from the active set. |
1723 void _connectionClosed(_HttpClientConnection connection) { | 1746 void _connectionClosed(_HttpClientConnection connection) { |
1724 connection.stopTimer(); | 1747 connection.stopTimer(); |
1725 var key = connection.key; | 1748 var connectionTarget = _connectionTargets[connection.key]; |
1726 if (_activeConnections.containsKey(key)) { | 1749 if (connectionTarget != null) { |
1727 _activeConnections[key].remove(connection); | 1750 connectionTarget.connectionClosed(connection); |
1728 if (_activeConnections[key].isEmpty) { | 1751 if (connectionTarget.isEmpty) { |
1729 _activeConnections.remove(key); | 1752 _connectionTargets.remove(connection.key); |
1730 } | 1753 } |
1754 _updateTimers(); | |
1731 } | 1755 } |
1732 if (_idleConnections.containsKey(key)) { | |
1733 _idleConnections[key].remove(connection); | |
1734 if (_idleConnections[key].isEmpty) { | |
1735 _idleConnections.remove(key); | |
1736 } | |
1737 } | |
1738 _updateTimers(); | |
1739 } | 1756 } |
1740 | 1757 |
1741 void _updateTimers() { | 1758 void _updateTimers() { |
1742 if (_activeConnections.isEmpty) { | 1759 bool hasActive = _connectionTargets.values.any((t) => t.hasActive); |
Søren Gjesse
2014/05/23 09:49:11
We could consider abstracting the _connectionTarge
| |
1743 if (!_idleConnections.isEmpty && _noActiveTimer == null) { | 1760 if (!hasActive) { |
1761 bool hasIdle = _connectionTargets.values.any((t) => t.hasIdle); | |
1762 if (hasIdle && _noActiveTimer == null) { | |
1744 _noActiveTimer = new Timer(const Duration(milliseconds: 100), () { | 1763 _noActiveTimer = new Timer(const Duration(milliseconds: 100), () { |
1745 _noActiveTimer = null; | 1764 _noActiveTimer = null; |
1746 if (_activeConnections.isEmpty) { | 1765 bool hasActive = |
1766 _connectionTargets.values.any((t) => t.hasActive); | |
1767 if (!hasActive) { | |
1747 close(); | 1768 close(); |
1748 _closing = false; | 1769 _closing = false; |
1749 } | 1770 } |
1750 }); | 1771 }); |
1751 } | 1772 } |
1752 } else if (_noActiveTimer != null) { | 1773 } else if (_noActiveTimer != null) { |
1753 _noActiveTimer.cancel(); | 1774 _noActiveTimer.cancel(); |
1754 _noActiveTimer = null; | 1775 _noActiveTimer = null; |
1755 } | 1776 } |
1756 } | 1777 } |
1757 | 1778 |
1758 // Get a new _HttpClientConnection, either from the idle pool or created from | 1779 // Get a new _HttpClientConnection, either from the idle pool or created from |
1759 // a new Socket. | 1780 // a new Socket. |
1760 Future<_ConnnectionInfo> _getConnection(String uriHost, | 1781 Future<_ConnnectionInfo> _getConnection(String uriHost, |
1761 int uriPort, | 1782 int uriPort, |
1762 _ProxyConfiguration proxyConf, | 1783 _ProxyConfiguration proxyConf, |
1763 bool isSecure) { | 1784 bool isSecure) { |
1764 Iterator<_Proxy> proxies = proxyConf.proxies.iterator; | 1785 Iterator<_Proxy> proxies = proxyConf.proxies.iterator; |
1765 | 1786 |
1766 Future<_ConnnectionInfo> connect(error) { | 1787 Future<_ConnnectionInfo> connect(error) { |
1767 if (!proxies.moveNext()) return new Future.error(error); | 1788 if (!proxies.moveNext()) return new Future.error(error); |
1768 _Proxy proxy = proxies.current; | 1789 _Proxy proxy = proxies.current; |
1769 String host = proxy.isDirect ? uriHost: proxy.host; | 1790 String host = proxy.isDirect ? uriHost: proxy.host; |
1770 int port = proxy.isDirect ? uriPort: proxy.port; | 1791 int port = proxy.isDirect ? uriPort: proxy.port; |
1771 String key = _HttpClientConnection.makeKey(isSecure, host, port); | 1792 String key = _HttpClientConnection.makeKey(isSecure, host, port); |
1772 if (_idleConnections.containsKey(key)) { | 1793 var connectionTarget = _connectionTargets[key]; |
1773 var connection = _idleConnections[key].first; | 1794 if (connectionTarget != null && connectionTarget.hasIdle) { |
1774 _idleConnections[key].remove(connection); | 1795 var connection = connectionTarget.takeIdle(); |
1775 if (_idleConnections[key].isEmpty) { | |
1776 _idleConnections.remove(key); | |
1777 } | |
1778 connection.stopTimer(); | |
1779 _activeConnections | |
1780 .putIfAbsent(key, () => new HashSet()) | |
1781 .add(connection); | |
1782 _updateTimers(); | 1796 _updateTimers(); |
1783 return new Future.value(new _ConnnectionInfo(connection, proxy)); | 1797 return new Future.value(new _ConnnectionInfo(connection, proxy)); |
1784 } | 1798 } |
1785 var currentBadCertificateCallback = _badCertificateCallback; | 1799 var currentBadCertificateCallback = _badCertificateCallback; |
1786 bool callback(X509Certificate certificate) => | 1800 bool callback(X509Certificate certificate) => |
1787 currentBadCertificateCallback == null ? false : | 1801 currentBadCertificateCallback == null ? false : |
1788 currentBadCertificateCallback(certificate, uriHost, uriPort); | 1802 currentBadCertificateCallback(certificate, uriHost, uriPort); |
1789 Future socketFuture = (isSecure && proxy.isDirect | 1803 Future socketFuture = (isSecure && proxy.isDirect |
1790 ? SecureSocket.connect(host, | 1804 ? SecureSocket.connect(host, |
1791 port, | 1805 port, |
1792 sendClientCertificate: true, | 1806 sendClientCertificate: true, |
1793 onBadCertificate: callback) | 1807 onBadCertificate: callback) |
1794 : Socket.connect(host, port)); | 1808 : Socket.connect(host, port)); |
1795 return socketFuture.then((socket) { | 1809 return socketFuture.then((socket) { |
1796 socket.setOption(SocketOption.TCP_NODELAY, true); | 1810 socket.setOption(SocketOption.TCP_NODELAY, true); |
1797 var connection = new _HttpClientConnection(key, socket, this); | 1811 var connection = new _HttpClientConnection(key, socket, this); |
1798 if (isSecure && !proxy.isDirect) { | 1812 if (isSecure && !proxy.isDirect) { |
1799 connection._dispose = true; | 1813 connection._dispose = true; |
1800 return connection.createProxyTunnel( | 1814 return connection.createProxyTunnel( |
1801 uriHost, uriPort, proxy, callback) | 1815 uriHost, uriPort, proxy, callback) |
1802 .then((tunnel) { | 1816 .then((tunnel) { |
1803 _activeConnections | 1817 var connectionTarget = _connectionTargets |
1804 .putIfAbsent(tunnel.key, () => new HashSet()) | 1818 .putIfAbsent(tunnel.key, |
1805 .add(tunnel); | 1819 () => new _ConnectionTarget(tunnel.key)); |
1820 connectionTarget.addNewActive(tunnel); | |
1806 return new _ConnnectionInfo(tunnel, proxy); | 1821 return new _ConnnectionInfo(tunnel, proxy); |
1807 }); | 1822 }); |
1808 } else { | 1823 } else { |
1809 _activeConnections | 1824 var connectionTarget = _connectionTargets |
1810 .putIfAbsent(key, () => new HashSet()) | 1825 .putIfAbsent(key, () => new _ConnectionTarget(key)); |
1811 .add(connection); | 1826 connectionTarget.addNewActive(connection); |
1812 return new _ConnnectionInfo(connection, proxy); | 1827 return new _ConnnectionInfo(connection, proxy); |
1813 } | 1828 } |
1814 }, onError: (error) { | 1829 }, onError: (error) { |
1815 // Continue with next proxy. | 1830 // Continue with next proxy. |
1816 return connect(error); | 1831 return connect(error); |
1817 }); | 1832 }); |
1818 } | 1833 } |
1819 return connect(new HttpException("No proxies given")); | 1834 return connect(new HttpException("No proxies given")); |
1820 } | 1835 } |
1821 | 1836 |
(...skipping 843 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2665 const _RedirectInfo(this.statusCode, this.method, this.location); | 2680 const _RedirectInfo(this.statusCode, this.method, this.location); |
2666 } | 2681 } |
2667 | 2682 |
2668 String _getHttpVersion() { | 2683 String _getHttpVersion() { |
2669 var version = Platform.version; | 2684 var version = Platform.version; |
2670 // Only include major and minor version numbers. | 2685 // Only include major and minor version numbers. |
2671 int index = version.indexOf('.', version.indexOf('.') + 1); | 2686 int index = version.indexOf('.', version.indexOf('.') + 1); |
2672 version = version.substring(0, index); | 2687 version = version.substring(0, index); |
2673 return 'Dart/$version (dart:io)'; | 2688 return 'Dart/$version (dart:io)'; |
2674 } | 2689 } |
OLD | NEW |