| Index: runtime/bin/http_impl.dart
|
| diff --git a/runtime/bin/http_impl.dart b/runtime/bin/http_impl.dart
|
| index 822fbd590aead346f0fe25f7a7ca2118bf0965c6..ffe4563615fc3f88ff4147d34a447c44f1642841 100644
|
| --- a/runtime/bin/http_impl.dart
|
| +++ b/runtime/bin/http_impl.dart
|
| @@ -1148,6 +1148,13 @@ class _HttpClientResponse
|
| int get statusCode() => _statusCode;
|
| String get reasonPhrase() => _reasonPhrase;
|
|
|
| + bool get isRedirect() {
|
| + return statusCode == HttpStatus.MOVED_PERMANENTLY ||
|
| + statusCode == HttpStatus.FOUND ||
|
| + statusCode == HttpStatus.SEE_OTHER ||
|
| + statusCode == HttpStatus.TEMPORARY_REDIRECT;
|
| + }
|
| +
|
| InputStream get inputStream() {
|
| if (_inputStream == null) {
|
| _inputStream = new _HttpInputStream(this);
|
| @@ -1171,7 +1178,32 @@ class _HttpClientResponse
|
| void _onHeadersComplete() {
|
| _headers._mutable = false;
|
| _buffer = new _BufferList();
|
| - if (_connection._onResponse != null) {
|
| + if (isRedirect && _connection.followRedirects) {
|
| + if (_connection._redirects == null ||
|
| + _connection._redirects.length < _connection.maxRedirects) {
|
| + // Check the location header.
|
| + List<String> location = headers[HttpHeaders.LOCATION];
|
| + if (location == null || location.length > 1) {
|
| + throw new RedirectException("Invalid redirect",
|
| + _connection._redirects);
|
| + }
|
| + // Check for redirect loop
|
| + if (_connection._redirects != null) {
|
| + Uri redirectUrl = new Uri.fromString(location[0]);
|
| + for (int i = 0; i < _connection._redirects.length; i++) {
|
| + if (_connection._redirects[i].location.toString() ==
|
| + redirectUrl.toString()) {
|
| + throw new RedirectLoop(_connection._redirects);
|
| + }
|
| + }
|
| + }
|
| + // Drain body and redirect.
|
| + inputStream.onData = inputStream.read;
|
| + inputStream.onClosed = _connection.redirect;
|
| + } else {
|
| + throw new RedirectLimitExceeded(_connection._redirects);
|
| + }
|
| + } else if (_connection._onResponse != null) {
|
| _connection._onResponse(this);
|
| }
|
| }
|
| @@ -1182,8 +1214,8 @@ class _HttpClientResponse
|
| }
|
|
|
| void _onDataEnd() {
|
| - if (_inputStream != null) _inputStream._closeReceived();
|
| _connection._responseDone();
|
| + if (_inputStream != null) _inputStream._closeReceived();
|
| }
|
|
|
| // Delegate functions for the HttpInputStream implementation.
|
| @@ -1322,6 +1354,26 @@ class _HttpClientConnection
|
| _onErrorCallback = callback;
|
| }
|
|
|
| + void redirect([String method, Uri url]) {
|
| + if (_socketConn != null) {
|
| + throw new HttpException("Cannot redirect with body data pending");
|
| + }
|
| + if (method == null) method = _method;
|
| + if (url == null) {
|
| + url = new Uri.fromString(_response.headers.value(HttpHeaders.LOCATION));
|
| + }
|
| + if (_redirects == null) {
|
| + _redirects = new List<_RedirectInfo>();
|
| + }
|
| + _redirects.add(new _RedirectInfo(_response.statusCode, method, url));
|
| + _request = null;
|
| + _response = null;
|
| + // Open redirect URL using the same connection instance.
|
| + _client._openUrl(method, url, this);
|
| + }
|
| +
|
| + List<RedirectInfo> get redirects() => _redirects;
|
| +
|
| Function _onRequest;
|
| Function _onResponse;
|
| Function _onErrorCallback;
|
| @@ -1332,6 +1384,11 @@ class _HttpClientConnection
|
| HttpClientResponse _response;
|
| String _method;
|
|
|
| + // Redirect handling
|
| + bool followRedirects = true;
|
| + int maxRedirects = 5;
|
| + List<_RedirectInfo> _redirects;
|
| +
|
| // Callbacks.
|
| var requestReceived;
|
| }
|
| @@ -1371,14 +1428,28 @@ class _HttpClient implements HttpClient {
|
|
|
| HttpClientConnection open(
|
| String method, String host, int port, String path) {
|
| + _open(method, host, port, path);
|
| + }
|
| +
|
| + HttpClientConnection _open(String method,
|
| + String host,
|
| + int port,
|
| + String path,
|
| + [_HttpClientConnection connection]) {
|
| if (_shutdown) throw new HttpException("HttpClient shutdown");
|
| if (method == null || host == null || port == null || path == null) {
|
| throw new IllegalArgumentException(null);
|
| }
|
| - return _prepareHttpClientConnection(host, port, method, path);
|
| + return _prepareHttpClientConnection(host, port, method, path, connection);
|
| }
|
|
|
| HttpClientConnection openUrl(String method, Uri url) {
|
| + _openUrl(method, url);
|
| + }
|
| +
|
| + HttpClientConnection _openUrl(String method,
|
| + Uri url,
|
| + [_HttpClientConnection connection]) {
|
| if (url.scheme != "http") {
|
| throw new HttpException("Unsupported URL scheme ${url.scheme}");
|
| }
|
| @@ -1396,20 +1467,20 @@ class _HttpClient implements HttpClient {
|
| } else {
|
| path = url.path;
|
| }
|
| - return open(method, url.domain, port, path);
|
| + return _open(method, url.domain, port, path, connection);
|
| }
|
|
|
| HttpClientConnection get(String host, int port, String path) {
|
| - return open("GET", host, port, path);
|
| + return _open("GET", host, port, path);
|
| }
|
|
|
| - HttpClientConnection getUrl(Uri url) => openUrl("GET", url);
|
| + HttpClientConnection getUrl(Uri url) => _openUrl("GET", url);
|
|
|
| HttpClientConnection post(String host, int port, String path) {
|
| - return open("POST", host, port, path);
|
| + return _open("POST", host, port, path);
|
| }
|
|
|
| - HttpClientConnection postUrl(Uri url) => openUrl("POST", url);
|
| + HttpClientConnection postUrl(Uri url) => _openUrl("POST", url);
|
|
|
| void shutdown() {
|
| _openSockets.forEach((String key, Queue<_SocketConnection> connections) {
|
| @@ -1432,7 +1503,11 @@ class _HttpClient implements HttpClient {
|
| }
|
|
|
| HttpClientConnection _prepareHttpClientConnection(
|
| - String host, int port, String method, String path) {
|
| + String host,
|
| + int port,
|
| + String method,
|
| + String path,
|
| + [_HttpClientConnection connection]) {
|
|
|
| void _connectionOpened(_SocketConnection socketConn,
|
| _HttpClientConnection connection) {
|
| @@ -1447,7 +1522,10 @@ class _HttpClient implements HttpClient {
|
| }
|
| }
|
|
|
| - _HttpClientConnection connection = new _HttpClientConnection(this);
|
| + // Create a new connection if we are not re-using an existing one.
|
| + if (connection == null) {
|
| + connection = new _HttpClientConnection(this);
|
| + }
|
|
|
| // If there are active connections for this key get the first one
|
| // otherwise create a new one.
|
| @@ -1550,3 +1628,13 @@ class _DetachedSocket implements DetachedSocket {
|
| Socket _socket;
|
| List<int> _unparsedData;
|
| }
|
| +
|
| +
|
| +class _RedirectInfo implements RedirectInfo {
|
| + const _RedirectInfo(int this.statusCode,
|
| + String this.method,
|
| + Uri this.location);
|
| + final int statusCode;
|
| + final String method;
|
| + final Uri location;
|
| +}
|
|
|