Chromium Code Reviews| Index: sdk/lib/io/http_impl.dart |
| diff --git a/sdk/lib/io/http_impl.dart b/sdk/lib/io/http_impl.dart |
| index fb9f009ace0236378e64d1661e0646c9011c6c0c..6c488c6d3128ebe85efcf91076af3a3b3e2185c0 100644 |
| --- a/sdk/lib/io/http_impl.dart |
| +++ b/sdk/lib/io/http_impl.dart |
| @@ -885,14 +885,12 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse> |
| void _writeHeader() { |
| var buffer = new _BufferList(); |
| + |
| writeSP() => buffer.add(const [_CharCode.SP]); |
| + |
| writeCRLF() => buffer.add(const [_CharCode.CR, _CharCode.LF]); |
| - buffer.add(method.codeUnits); |
| - writeSP(); |
| - // Send the path for direct connections and the whole URL for |
| - // proxy connections. |
| - if (_proxy.isDirect) { |
| + void writePath() { |
| String path = uri.path; |
| if (path.length == 0) path = "/"; |
| if (uri.query != "") { |
| @@ -903,8 +901,29 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse> |
| } |
| } |
| buffer.add(path.codeUnits); |
| + } |
| + |
| + // Write the request method. |
| + buffer.add(method.codeUnits); |
| + writeSP(); |
| + // Write the request URI. |
| + if (_proxy.isDirect) { |
| + writePath(); |
| } else { |
| - buffer.add(uri.toString().codeUnits); |
| + 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) |
| + buffer.add(uri.domain.codeUnits); |
| + buffer.add(const [_CharCode.COLON]); |
| + buffer.add(uri.port.toString().codeUnits); |
| + } else { |
| + if (_httpClientConnection._proxyTunnel) { |
| + writePath(); |
| + } else { |
| + buffer.add(uri.toString().codeUnits); |
| + } |
| + } |
| } |
| writeSP(); |
| buffer.add(_Const.HTTP11); |
| @@ -1024,20 +1043,22 @@ class _HttpOutgoing implements StreamConsumer<List<int>> { |
| Future get done => _doneCompleter.future; |
| } |
| - |
| class _HttpClientConnection { |
| final String key; |
| final Socket _socket; |
| + final bool _proxyTunnel; |
| final _HttpParser _httpParser; |
| StreamSubscription _subscription; |
| final _HttpClient _httpClient; |
| + bool _dispose = false; |
| Completer<_HttpIncoming> _nextResponseCompleter; |
| Future _streamFuture; |
| _HttpClientConnection(String this.key, |
| Socket this._socket, |
| - _HttpClient this._httpClient) |
| + _HttpClient this._httpClient, |
| + [this._proxyTunnel = false]) |
| : _httpParser = new _HttpParser.responseParser() { |
| _socket.pipe(_httpParser); |
| @@ -1116,7 +1137,8 @@ class _HttpClientConnection { |
| _nextResponseCompleter.future |
| .then((incoming) { |
| incoming.dataDone.then((_) { |
| - if (incoming.headers.persistentConnection && |
| + if (!_dispose && |
| + incoming.headers.persistentConnection && |
| request.persistentConnection) { |
| // Return connection, now we are done. |
| _httpClient._returnConnection(this); |
| @@ -1167,7 +1189,41 @@ class _HttpClientConnection { |
| .then((_) => _socket.destroy()); |
| } |
| + Future<_HttpClientConnection> createProxyTunnel(host, port, proxy) { |
| + _HttpClientRequest request = |
| + send(new Uri.fromComponents(domain: 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( |
| + _encodeString("${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._httpRequest._httpClientConnection._socket; |
| + return SecureSocket.secure(socket, host: host); |
| + }) |
| + .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"; |
| + } |
| + |
| } |
| class _ConnnectionInfo { |
| @@ -1275,8 +1331,10 @@ class _HttpClient implements HttpClient { |
| if (method == null) { |
| throw new ArgumentError(method); |
| } |
| - if (uri.domain.isEmpty || (uri.scheme != "http" && uri.scheme != "https")) { |
| - throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri"); |
| + if (method != "CONNECT") { |
| + if (uri.domain.isEmpty || (uri.scheme != "http" && uri.scheme != "https")) { |
|
Mads Ager (google)
2013/04/29 10:05:01
long line
|
| + throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri"); |
| + } |
| } |
| bool isSecure = (uri.scheme == "https"); |
| @@ -1364,7 +1422,7 @@ class _HttpClient implements HttpClient { |
| _Proxy proxy = proxies.current; |
| String host = proxy.isDirect ? uriHost: proxy.host; |
| int port = proxy.isDirect ? uriPort: proxy.port; |
| - String key = isSecure ? "ssh:$host:$port" : "$host:$port"; |
| + String key = _HttpClientConnection.makeKey(isSecure, host, port); |
| if (_idleConnections.containsKey(key)) { |
| var connection = _idleConnections[key].first; |
| _idleConnections[key].remove(connection); |
| @@ -1382,8 +1440,17 @@ class _HttpClient implements HttpClient { |
| .then((socket) { |
| socket.setOption(SocketOption.TCP_NODELAY, true); |
| var connection = new _HttpClientConnection(key, socket, this); |
| - _activeConnections.add(connection); |
| - return new _ConnnectionInfo(connection, proxy); |
| + if (isSecure && !proxy.isDirect) { |
| + connection._dispose = true; |
| + return connection.createProxyTunnel(uriHost, uriPort, proxy) |
| + .then((tunnel) { |
| + _activeConnections.add(tunnel); |
| + return new _ConnnectionInfo(tunnel, proxy); |
| + }); |
| + } else { |
| + _activeConnections.add(connection); |
| + return new _ConnnectionInfo(connection, proxy); |
| + } |
| }, onError: (error) { |
| // Continue with next proxy. |
| return connect(error); |