OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 class _HttpHeaders implements HttpHeaders { | 5 class _HttpHeaders implements HttpHeaders { |
6 _HttpHeaders() : _headers = new Map<String, List<String>>(); | 6 _HttpHeaders() : _headers = new Map<String, List<String>>(); |
7 | 7 |
8 List<String> operator[](String name) { | 8 List<String> operator[](String name) { |
9 name = name.toLowerCase(); | 9 name = name.toLowerCase(); |
10 return _headers[name]; | 10 return _headers[name]; |
(...skipping 1130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1141 class _HttpClientResponse | 1141 class _HttpClientResponse |
1142 extends _HttpRequestResponseBase implements HttpClientResponse { | 1142 extends _HttpRequestResponseBase implements HttpClientResponse { |
1143 _HttpClientResponse(_HttpClientConnection connection) | 1143 _HttpClientResponse(_HttpClientConnection connection) |
1144 : super(connection) { | 1144 : super(connection) { |
1145 _connection = connection; | 1145 _connection = connection; |
1146 } | 1146 } |
1147 | 1147 |
1148 int get statusCode() => _statusCode; | 1148 int get statusCode() => _statusCode; |
1149 String get reasonPhrase() => _reasonPhrase; | 1149 String get reasonPhrase() => _reasonPhrase; |
1150 | 1150 |
| 1151 bool get isRedirect() { |
| 1152 return statusCode == HttpStatus.MOVED_PERMANENTLY || |
| 1153 statusCode == HttpStatus.FOUND || |
| 1154 statusCode == HttpStatus.SEE_OTHER || |
| 1155 statusCode == HttpStatus.TEMPORARY_REDIRECT; |
| 1156 } |
| 1157 |
1151 InputStream get inputStream() { | 1158 InputStream get inputStream() { |
1152 if (_inputStream == null) { | 1159 if (_inputStream == null) { |
1153 _inputStream = new _HttpInputStream(this); | 1160 _inputStream = new _HttpInputStream(this); |
1154 } | 1161 } |
1155 return _inputStream; | 1162 return _inputStream; |
1156 } | 1163 } |
1157 | 1164 |
1158 void _onRequestStart(String method, String uri, String version) { | 1165 void _onRequestStart(String method, String uri, String version) { |
1159 // TODO(sgjesse): Error handling | 1166 // TODO(sgjesse): Error handling |
1160 } | 1167 } |
1161 | 1168 |
1162 void _onResponseStart(int statusCode, String reasonPhrase, String version) { | 1169 void _onResponseStart(int statusCode, String reasonPhrase, String version) { |
1163 _statusCode = statusCode; | 1170 _statusCode = statusCode; |
1164 _reasonPhrase = reasonPhrase; | 1171 _reasonPhrase = reasonPhrase; |
1165 } | 1172 } |
1166 | 1173 |
1167 void _onHeaderReceived(String name, String value) { | 1174 void _onHeaderReceived(String name, String value) { |
1168 _headers.add(name, value); | 1175 _headers.add(name, value); |
1169 } | 1176 } |
1170 | 1177 |
1171 void _onHeadersComplete() { | 1178 void _onHeadersComplete() { |
1172 _headers._mutable = false; | 1179 _headers._mutable = false; |
1173 _buffer = new _BufferList(); | 1180 _buffer = new _BufferList(); |
1174 if (_connection._onResponse != null) { | 1181 if (isRedirect && _connection.followRedirects) { |
| 1182 if (_connection._redirects == null || |
| 1183 _connection._redirects.length < _connection.maxRedirects) { |
| 1184 // Check the location header. |
| 1185 List<String> location = headers[HttpHeaders.LOCATION]; |
| 1186 if (location == null || location.length > 1) { |
| 1187 throw new RedirectException("Invalid redirect", |
| 1188 _connection._redirects); |
| 1189 } |
| 1190 // Check for redirect loop |
| 1191 if (_connection._redirects != null) { |
| 1192 Uri redirectUrl = new Uri.fromString(location[0]); |
| 1193 for (int i = 0; i < _connection._redirects.length; i++) { |
| 1194 if (_connection._redirects[i].location.toString() == |
| 1195 redirectUrl.toString()) { |
| 1196 throw new RedirectLoop(_connection._redirects); |
| 1197 } |
| 1198 } |
| 1199 } |
| 1200 // Drain body and redirect. |
| 1201 inputStream.onData = inputStream.read; |
| 1202 inputStream.onClosed = _connection.redirect; |
| 1203 } else { |
| 1204 throw new RedirectLimitExceeded(_connection._redirects); |
| 1205 } |
| 1206 } else if (_connection._onResponse != null) { |
1175 _connection._onResponse(this); | 1207 _connection._onResponse(this); |
1176 } | 1208 } |
1177 } | 1209 } |
1178 | 1210 |
1179 void _onDataReceived(List<int> data) { | 1211 void _onDataReceived(List<int> data) { |
1180 _buffer.add(data); | 1212 _buffer.add(data); |
1181 if (_inputStream != null) _inputStream._dataReceived(); | 1213 if (_inputStream != null) _inputStream._dataReceived(); |
1182 } | 1214 } |
1183 | 1215 |
1184 void _onDataEnd() { | 1216 void _onDataEnd() { |
| 1217 _connection._responseDone(); |
1185 if (_inputStream != null) _inputStream._closeReceived(); | 1218 if (_inputStream != null) _inputStream._closeReceived(); |
1186 _connection._responseDone(); | |
1187 } | 1219 } |
1188 | 1220 |
1189 // Delegate functions for the HttpInputStream implementation. | 1221 // Delegate functions for the HttpInputStream implementation. |
1190 int _streamAvailable() { | 1222 int _streamAvailable() { |
1191 return _buffer.length; | 1223 return _buffer.length; |
1192 } | 1224 } |
1193 | 1225 |
1194 List<int> _streamRead(int bytesToRead) { | 1226 List<int> _streamRead(int bytesToRead) { |
1195 return _buffer.readBytes(bytesToRead); | 1227 return _buffer.readBytes(bytesToRead); |
1196 } | 1228 } |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1315 } | 1347 } |
1316 | 1348 |
1317 void set onResponse(void handler(HttpClientResponse response)) { | 1349 void set onResponse(void handler(HttpClientResponse response)) { |
1318 _onResponse = handler; | 1350 _onResponse = handler; |
1319 } | 1351 } |
1320 | 1352 |
1321 void set onError(void callback(e)) { | 1353 void set onError(void callback(e)) { |
1322 _onErrorCallback = callback; | 1354 _onErrorCallback = callback; |
1323 } | 1355 } |
1324 | 1356 |
| 1357 void redirect([String method, Uri url]) { |
| 1358 if (_socketConn != null) { |
| 1359 throw new HttpException("Cannot redirect with body data pending"); |
| 1360 } |
| 1361 if (method == null) method = _method; |
| 1362 if (url == null) { |
| 1363 url = new Uri.fromString(_response.headers.value(HttpHeaders.LOCATION)); |
| 1364 } |
| 1365 if (_redirects == null) { |
| 1366 _redirects = new List<_RedirectInfo>(); |
| 1367 } |
| 1368 _redirects.add(new _RedirectInfo(_response.statusCode, method, url)); |
| 1369 _request = null; |
| 1370 _response = null; |
| 1371 // Open redirect URL using the same connection instance. |
| 1372 _client._openUrl(method, url, this); |
| 1373 } |
| 1374 |
| 1375 List<RedirectInfo> get redirects() => _redirects; |
| 1376 |
1325 Function _onRequest; | 1377 Function _onRequest; |
1326 Function _onResponse; | 1378 Function _onResponse; |
1327 Function _onErrorCallback; | 1379 Function _onErrorCallback; |
1328 | 1380 |
1329 _HttpClient _client; | 1381 _HttpClient _client; |
1330 _SocketConnection _socketConn; | 1382 _SocketConnection _socketConn; |
1331 HttpClientRequest _request; | 1383 HttpClientRequest _request; |
1332 HttpClientResponse _response; | 1384 HttpClientResponse _response; |
1333 String _method; | 1385 String _method; |
1334 | 1386 |
| 1387 // Redirect handling |
| 1388 bool followRedirects = true; |
| 1389 int maxRedirects = 5; |
| 1390 List<_RedirectInfo> _redirects; |
| 1391 |
1335 // Callbacks. | 1392 // Callbacks. |
1336 var requestReceived; | 1393 var requestReceived; |
1337 } | 1394 } |
1338 | 1395 |
1339 | 1396 |
1340 // Class for holding keep-alive sockets in the cache for the HTTP | 1397 // Class for holding keep-alive sockets in the cache for the HTTP |
1341 // client together with the connection information. | 1398 // client together with the connection information. |
1342 class _SocketConnection { | 1399 class _SocketConnection { |
1343 _SocketConnection(String this._host, | 1400 _SocketConnection(String this._host, |
1344 int this._port, | 1401 int this._port, |
(...skipping 19 matching lines...) Expand all Loading... |
1364 | 1421 |
1365 class _HttpClient implements HttpClient { | 1422 class _HttpClient implements HttpClient { |
1366 static final int DEFAULT_EVICTION_TIMEOUT = 60000; | 1423 static final int DEFAULT_EVICTION_TIMEOUT = 60000; |
1367 | 1424 |
1368 _HttpClient() : _openSockets = new Map(), | 1425 _HttpClient() : _openSockets = new Map(), |
1369 _activeSockets = new Set(), | 1426 _activeSockets = new Set(), |
1370 _shutdown = false; | 1427 _shutdown = false; |
1371 | 1428 |
1372 HttpClientConnection open( | 1429 HttpClientConnection open( |
1373 String method, String host, int port, String path) { | 1430 String method, String host, int port, String path) { |
| 1431 _open(method, host, port, path); |
| 1432 } |
| 1433 |
| 1434 HttpClientConnection _open(String method, |
| 1435 String host, |
| 1436 int port, |
| 1437 String path, |
| 1438 [_HttpClientConnection connection]) { |
1374 if (_shutdown) throw new HttpException("HttpClient shutdown"); | 1439 if (_shutdown) throw new HttpException("HttpClient shutdown"); |
1375 if (method == null || host == null || port == null || path == null) { | 1440 if (method == null || host == null || port == null || path == null) { |
1376 throw new IllegalArgumentException(null); | 1441 throw new IllegalArgumentException(null); |
1377 } | 1442 } |
1378 return _prepareHttpClientConnection(host, port, method, path); | 1443 return _prepareHttpClientConnection(host, port, method, path, connection); |
1379 } | 1444 } |
1380 | 1445 |
1381 HttpClientConnection openUrl(String method, Uri url) { | 1446 HttpClientConnection openUrl(String method, Uri url) { |
| 1447 _openUrl(method, url); |
| 1448 } |
| 1449 |
| 1450 HttpClientConnection _openUrl(String method, |
| 1451 Uri url, |
| 1452 [_HttpClientConnection connection]) { |
1382 if (url.scheme != "http") { | 1453 if (url.scheme != "http") { |
1383 throw new HttpException("Unsupported URL scheme ${url.scheme}"); | 1454 throw new HttpException("Unsupported URL scheme ${url.scheme}"); |
1384 } | 1455 } |
1385 if (url.userInfo != "") { | 1456 if (url.userInfo != "") { |
1386 throw new HttpException("Unsupported user info ${url.userInfo}"); | 1457 throw new HttpException("Unsupported user info ${url.userInfo}"); |
1387 } | 1458 } |
1388 int port = url.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : url.port; | 1459 int port = url.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : url.port; |
1389 String path; | 1460 String path; |
1390 if (url.query != "") { | 1461 if (url.query != "") { |
1391 if (url.fragment != "") { | 1462 if (url.fragment != "") { |
1392 path = "${url.path}?${url.query}#${url.fragment}"; | 1463 path = "${url.path}?${url.query}#${url.fragment}"; |
1393 } else { | 1464 } else { |
1394 path = "${url.path}?${url.query}"; | 1465 path = "${url.path}?${url.query}"; |
1395 } | 1466 } |
1396 } else { | 1467 } else { |
1397 path = url.path; | 1468 path = url.path; |
1398 } | 1469 } |
1399 return open(method, url.domain, port, path); | 1470 return _open(method, url.domain, port, path, connection); |
1400 } | 1471 } |
1401 | 1472 |
1402 HttpClientConnection get(String host, int port, String path) { | 1473 HttpClientConnection get(String host, int port, String path) { |
1403 return open("GET", host, port, path); | 1474 return _open("GET", host, port, path); |
1404 } | 1475 } |
1405 | 1476 |
1406 HttpClientConnection getUrl(Uri url) => openUrl("GET", url); | 1477 HttpClientConnection getUrl(Uri url) => _openUrl("GET", url); |
1407 | 1478 |
1408 HttpClientConnection post(String host, int port, String path) { | 1479 HttpClientConnection post(String host, int port, String path) { |
1409 return open("POST", host, port, path); | 1480 return _open("POST", host, port, path); |
1410 } | 1481 } |
1411 | 1482 |
1412 HttpClientConnection postUrl(Uri url) => openUrl("POST", url); | 1483 HttpClientConnection postUrl(Uri url) => _openUrl("POST", url); |
1413 | 1484 |
1414 void shutdown() { | 1485 void shutdown() { |
1415 _openSockets.forEach((String key, Queue<_SocketConnection> connections) { | 1486 _openSockets.forEach((String key, Queue<_SocketConnection> connections) { |
1416 while (!connections.isEmpty()) { | 1487 while (!connections.isEmpty()) { |
1417 _SocketConnection socketConn = connections.removeFirst(); | 1488 _SocketConnection socketConn = connections.removeFirst(); |
1418 socketConn._socket.close(); | 1489 socketConn._socket.close(); |
1419 } | 1490 } |
1420 }); | 1491 }); |
1421 _activeSockets.forEach((_SocketConnection socketConn) { | 1492 _activeSockets.forEach((_SocketConnection socketConn) { |
1422 socketConn._socket.close(); | 1493 socketConn._socket.close(); |
1423 }); | 1494 }); |
1424 if (_evictionTimer != null) { | 1495 if (_evictionTimer != null) { |
1425 _evictionTimer.cancel(); | 1496 _evictionTimer.cancel(); |
1426 } | 1497 } |
1427 _shutdown = true; | 1498 _shutdown = true; |
1428 } | 1499 } |
1429 | 1500 |
1430 String _connectionKey(String host, int port) { | 1501 String _connectionKey(String host, int port) { |
1431 return "$host:$port"; | 1502 return "$host:$port"; |
1432 } | 1503 } |
1433 | 1504 |
1434 HttpClientConnection _prepareHttpClientConnection( | 1505 HttpClientConnection _prepareHttpClientConnection( |
1435 String host, int port, String method, String path) { | 1506 String host, |
| 1507 int port, |
| 1508 String method, |
| 1509 String path, |
| 1510 [_HttpClientConnection connection]) { |
1436 | 1511 |
1437 void _connectionOpened(_SocketConnection socketConn, | 1512 void _connectionOpened(_SocketConnection socketConn, |
1438 _HttpClientConnection connection) { | 1513 _HttpClientConnection connection) { |
1439 connection._connectionEstablished(socketConn); | 1514 connection._connectionEstablished(socketConn); |
1440 HttpClientRequest request = connection.open(method, path); | 1515 HttpClientRequest request = connection.open(method, path); |
1441 request.headers.host = host; | 1516 request.headers.host = host; |
1442 request.headers.port = port; | 1517 request.headers.port = port; |
1443 if (connection._onRequest != null) { | 1518 if (connection._onRequest != null) { |
1444 connection._onRequest(request); | 1519 connection._onRequest(request); |
1445 } else { | 1520 } else { |
1446 request.outputStream.close(); | 1521 request.outputStream.close(); |
1447 } | 1522 } |
1448 } | 1523 } |
1449 | 1524 |
1450 _HttpClientConnection connection = new _HttpClientConnection(this); | 1525 // Create a new connection if we are not re-using an existing one. |
| 1526 if (connection == null) { |
| 1527 connection = new _HttpClientConnection(this); |
| 1528 } |
1451 | 1529 |
1452 // If there are active connections for this key get the first one | 1530 // If there are active connections for this key get the first one |
1453 // otherwise create a new one. | 1531 // otherwise create a new one. |
1454 Queue socketConnections = _openSockets[_connectionKey(host, port)]; | 1532 Queue socketConnections = _openSockets[_connectionKey(host, port)]; |
1455 if (socketConnections == null || socketConnections.isEmpty()) { | 1533 if (socketConnections == null || socketConnections.isEmpty()) { |
1456 Socket socket = new Socket(host, port); | 1534 Socket socket = new Socket(host, port); |
1457 // Until the connection is established handle connection errors | 1535 // Until the connection is established handle connection errors |
1458 // here as the HttpClientConnection object is not yet associated | 1536 // here as the HttpClientConnection object is not yet associated |
1459 // with the socket. | 1537 // with the socket. |
1460 socket.onError = (e) { | 1538 socket.onError = (e) { |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1543 } | 1621 } |
1544 | 1622 |
1545 | 1623 |
1546 class _DetachedSocket implements DetachedSocket { | 1624 class _DetachedSocket implements DetachedSocket { |
1547 _DetachedSocket(this._socket, this._unparsedData); | 1625 _DetachedSocket(this._socket, this._unparsedData); |
1548 Socket get socket() => _socket; | 1626 Socket get socket() => _socket; |
1549 List<int> get unparsedData() => _unparsedData; | 1627 List<int> get unparsedData() => _unparsedData; |
1550 Socket _socket; | 1628 Socket _socket; |
1551 List<int> _unparsedData; | 1629 List<int> _unparsedData; |
1552 } | 1630 } |
| 1631 |
| 1632 |
| 1633 class _RedirectInfo implements RedirectInfo { |
| 1634 const _RedirectInfo(int this.statusCode, |
| 1635 String this.method, |
| 1636 Uri this.location); |
| 1637 final int statusCode; |
| 1638 final String method; |
| 1639 final Uri location; |
| 1640 } |
OLD | NEW |