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; |
+} |