Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(819)

Side by Side Diff: sdk/lib/io/http_impl.dart

Issue 11783034: Re-implement support for client redirects. (Closed) Base URL: https://dart.googlecode.com/svn/experimental/lib_v2_io/dart
Patch Set: Review fixes Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/io/http.dart ('k') | sdk/lib/io/http_parser.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 5
6 class _HttpIncomingConnection extends StreamController<List<int>> { 6 class _HttpIncomingConnection extends StreamController<List<int>> {
7 final Function _pause; 7 final Function _pause;
8 final Function _resume; 8 final Function _resume;
9 bool _ignore = false; 9 bool _ignore = false;
10 final SignalCompleter _dataCompleter = new SignalCompleter(); 10 final SignalCompleter _dataCompleter = new SignalCompleter();
11 final SignalCompleter _messageCompleter = new SignalCompleter(); 11 final SignalCompleter _messageCompleter = new SignalCompleter();
12 12
13 // Common properties. 13 // Common properties.
14 _HttpHeaders headers; 14 final _HttpHeaders headers;
15 int contentLength;
16 bool upgraded = false; 15 bool upgraded = false;
17 16
18 // ClientResponse properties. 17 // ClientResponse properties.
19 int statusCode; 18 int statusCode;
20 String reasonPhrase; 19 String reasonPhrase;
21 20
22 // Request properties. 21 // Request properties.
23 String method; 22 String method;
24 Uri uri; 23 Uri uri;
25 24
26 _HttpIncomingConnection(void this._pause(), 25 _HttpIncomingConnection(_HttpHeaders this.headers,
26 void this._pause(),
27 void this._resume()) 27 void this._resume())
28 : super.singleSubscription() { 28 : super.singleSubscription() {
29 _pause(); 29 _pause();
30 } 30 }
31 31
32 // Is completed once all data have been received. 32 // Is completed once all data have been received.
33 Signal get dataDone => _dataCompleter.signal; 33 Signal get dataDone => _dataCompleter.signal;
34 34
35 // Is completed once the message have been handled. 35 // Is completed once the message have been handled.
36 Signal get messageHandled => _messageCompleter.signal; 36 Signal get messageHandled => _messageCompleter.signal;
37 37
38 void onPauseStateChange() { 38 void onPauseStateChange() {
39 if (isPaused) { 39 if (isPaused) {
40 _pause(); 40 _pause();
41 } else { 41 } else {
42 _reasume(); 42 _resume();
43 } 43 }
44 } 44 }
45 45
46 void onSubscriptionStateChange() { 46 void onSubscriptionStateChange() {
47 if (hasSubscribers) { 47 if (hasSubscribers) {
48 _resume(); 48 _resume();
49 } else { 49 } else {
50 _ignore = true; 50 _ignore = true;
51 } 51 }
52 } 52 }
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 return _queryParameters; 109 return _queryParameters;
110 } 110 }
111 111
112 Uri get uri => _incomingConnection.uri; 112 Uri get uri => _incomingConnection.uri;
113 113
114 String get method => _incomingConnection.method; 114 String get method => _incomingConnection.method;
115 } 115 }
116 116
117 117
118 class _HttpClientResponse extends _HttpIncoming implements HttpClientResponse { 118 class _HttpClientResponse extends _HttpIncoming implements HttpClientResponse {
119 _HttpClientResponse(_HttpIncomingConnection _incomingConnection) 119 List<RedirectInfo> get redirects => _httpRequest._responseRedirects;
120
121 // The HttpClient this response belongs to.
122 final _HttpClient _httpClient;
123
124 // The HttpClientRequest of this response.
125 final _HttpClientRequest _httpRequest;
126
127 _HttpClientResponse(_HttpIncomingConnection _incomingConnection,
128 _HttpClientRequest this._httpRequest,
129 _HttpClient this._httpClient)
120 : super(_incomingConnection); 130 : super(_incomingConnection);
121 131
122 int get statusCode => _incomingConnection.statusCode; 132 int get statusCode => _incomingConnection.statusCode;
123 String get reasonPhrase => _incomingConnection.reasonPhrase; 133 String get reasonPhrase => _incomingConnection.reasonPhrase;
124 134
135 bool get isRedirect {
136 if (_httpRequest.method == "GET" || _httpRequest.method == "HEAD") {
137 return statusCode == HttpStatus.MOVED_PERMANENTLY ||
138 statusCode == HttpStatus.FOUND ||
139 statusCode == HttpStatus.SEE_OTHER ||
140 statusCode == HttpStatus.TEMPORARY_REDIRECT;
141 } else if (_httpRequest.method == "POST") {
142 return statusCode == HttpStatus.SEE_OTHER;
143 }
144 return false;
145 }
146
147 Future<HttpClientResponse> redirect([String method,
148 Uri url,
149 bool followLoops]) {
150 if (method == null) {
151 // Set method as defined by RFC 2616 section 10.3.4.
152 if (statusCode == HttpStatus.SEE_OTHER && _httpRequest.method == "POST") {
153 method = "GET";
154 } else {
155 method = _httpRequest.method;
156 }
157 }
158 if (url == null) {
159 String location = headers.value(HttpHeaders.LOCATION);
160 if (location == null) {
161 throw new StateError("Response has no Location header for redirect");
162 }
163 url = new Uri.fromString(location);
164 }
165 if (followLoops != true) {
166 for (var redirect in redirects) {
167 if (redirect.location == url) {
168 return new Future.immediateError(
169 new RedirectLoopException(redirects));
170 }
171 }
172 }
173 return _httpClient.openUrl(method, url)
174 .then((request) {
175 // Only follow redirects if initial request did.
176 request.followRedirects = _httpRequest.followRedirects;
177 // Allow the same number of redirects.
178 request.maxRedirects = _httpRequest.maxRedirects;
179 // Copy headers.
180 for (var header in _httpRequest.headers._headers.keys) {
181 if (header != HttpHeaders.HOST.toLowerCase()) {
182 request.headers.set(header, _httpRequest.headers[header]);
183 }
184 }
185 request.headers.contentLength = 0;
186 request._responseRedirects.addAll(this.redirects);
187 request._responseRedirects.add(new _RedirectInfo(statusCode,
188 method,
189 url));
190 return request.close();
191 });
192 }
193
125 StreamSubscription<List<int>> listen(void onData(List<int> event), 194 StreamSubscription<List<int>> listen(void onData(List<int> event),
126 {void onError(AsyncError error), 195 {void onError(AsyncError error),
127 void onDone(), 196 void onDone(),
128 bool unsubscribeOnError}) { 197 bool unsubscribeOnError}) {
129 return _incomingConnection.listen( 198 return _incomingConnection.listen(
130 onData, 199 onData,
131 onError: onError, 200 onError: onError,
132 onDone: onDone, 201 onDone: onDone,
133 unsubscribeOnError: unsubscribeOnError); 202 unsubscribeOnError: unsubscribeOnError);
134 } 203 }
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 292
224 // Write headers. 293 // Write headers.
225 headers._write(this); 294 headers._write(this);
226 writeCRLF(); 295 writeCRLF();
227 } 296 }
228 } 297 }
229 298
230 299
231 class _HttpClientRequest extends _HttpOutgoing<HttpClientRequest> 300 class _HttpClientRequest extends _HttpOutgoing<HttpClientRequest>
232 implements HttpClientRequest { 301 implements HttpClientRequest {
233 _HttpClientRequest(Uri this.uri, 302 final String method;
303 final Uri uri;
304 final List<Cookie> cookies = new List<Cookie>();
305
306 // The HttpClient this request belongs to.
307 final _HttpClient _httpClient;
308
309 final Completer<HttpClientResponse> _responseCompleter
310 = new Completer<HttpClientResponse>();
311
312
313 // TODO(ajohnsen): Get default value from client?
314 bool _followRedirects = true;
315
316 int _maxRedirects = 5;
317
318 List<RedirectInfo> _responseRedirects = [];
319
320 _HttpClientRequest(_HttpOutgoingConnection outgoing,
321 Uri this.uri,
234 String this.method, 322 String this.method,
235 _HttpOutgoingConnection outgoing) 323 _HttpClient this._httpClient)
236 : super("1.1", outgoing) { 324 : super("1.1", outgoing) {
237 // GET and HEAD have 'content-length: 0' by default. 325 // GET and HEAD have 'content-length: 0' by default.
238 if (method == "GET" || method == "HEAD") { 326 if (method == "GET" || method == "HEAD") {
239 contentLength = 0; 327 contentLength = 0;
240 } 328 }
241 } 329 }
242 330
331 Future<HttpClientResponse> get response => _responseCompleter.future;
332
243 Future<HttpClientResponse> close() { 333 Future<HttpClientResponse> close() {
244 super.close(); 334 super.close();
245 return response; 335 return response;
246 } 336 }
247 337
338 int get maxRedirects => _maxRedirects;
339 void set maxRedirects(int maxRedirects) {
340 if (_headersWritten) throw new StateError("Request already sent");
341 _maxRedirects = maxRedirects;
342 }
343
344 bool get followRedirects => _followRedirects;
345 void set followRedirects(bool followRedirects) {
346 if (_headersWritten) throw new StateError("Request already sent");
347 _followRedirects = followRedirects;
348 }
349
248 void _onIncoming(_HttpIncomingConnection incoming) { 350 void _onIncoming(_HttpIncomingConnection incoming) {
249 // TODO(ajohnsen): Handle redirect and auth. 351 // TODO(ajohnsen): Handle auth.
250 _responseCompleter.complete(new _HttpClientResponse(incoming)); 352 var response = new _HttpClientResponse(incoming,
353 this,
354 _httpClient);
355
356 Future<HttpClientResponse> future;
357
358 if (followRedirects &&
359 response.isRedirect) {
360 if (response.redirects.length < maxRedirects) {
361 // Redirect
362 future = response.redirect();
363 } else {
364 // End with exception, too many redirects.
365 future = new Future.immediateError(
366 new RedirectLimitExceededException(response.redirects));
367 }
368 } else {
369 future = new Future<HttpClientResponse>.immediate(response);
370 }
371
372 future.then(
373 _responseCompleter.complete,
374 onError: (e) {
375 _responseCompleter.completeError(e.error, e.stackTrace);
376 });
251 } 377 }
252 378
253 void _onError(AsyncError error) { 379 void _onError(AsyncError error) {
254 _responseCompleter.completeError(error.error, error.stackTrace); 380 _responseCompleter.completeError(error.error, error.stackTrace);
255 } 381 }
256 382
257 void _writeHeader() { 383 void _writeHeader() {
258 writeSP() => add([_CharCode.SP]); 384 writeSP() => add([_CharCode.SP]);
259 writeCRLF() => add([_CharCode.CR, _CharCode.LF]); 385 writeCRLF() => add([_CharCode.CR, _CharCode.LF]);
260 386
(...skipping 30 matching lines...) Expand all
291 } 417 }
292 headers.add("cookie", sb.toString()); 418 headers.add("cookie", sb.toString());
293 } 419 }
294 420
295 headers._finalize(); 421 headers._finalize();
296 422
297 // Write headers. 423 // Write headers.
298 headers._write(this); 424 headers._write(this);
299 writeCRLF(); 425 writeCRLF();
300 } 426 }
301
302 Future<HttpClientResponse> get response => _responseCompleter.future;
303
304 final String method;
305 final Uri uri;
306 final List<Cookie> cookies = new List<Cookie>();
307 final Completer<HttpClientResponse> _responseCompleter
308 = new Completer<HttpClientResponse>();
309 } 427 }
310 428
311 429
312 // Transformer that transforms data to HTTP Chunked Encoding. 430 // Transformer that transforms data to HTTP Chunked Encoding.
313 class _ChunkedTransformer extends StreamController<List<int>> 431 class _ChunkedTransformer extends StreamController<List<int>>
314 implements StreamTransformer<List<int>, List<int>> { 432 implements StreamTransformer<List<int>, List<int>> {
315 _ChunkedTransformer() : super.singleSubscription(); 433 _ChunkedTransformer() : super.singleSubscription();
316 434
317 Stream<List<int>> bind(Stream<List<int>> stream) { 435 Stream<List<int>> bind(Stream<List<int>> stream) {
318 var subscription = stream.listen( 436 var subscription = stream.listen(
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
453 class _HttpClient implements HttpClient { 571 class _HttpClient implements HttpClient {
454 // TODO(ajohnsen): Use eviction timeout. 572 // TODO(ajohnsen): Use eviction timeout.
455 static const int DEFAULT_EVICTION_TIMEOUT = 60000; 573 static const int DEFAULT_EVICTION_TIMEOUT = 60000;
456 574
457 Future<HttpClientRequest> open(String method, 575 Future<HttpClientRequest> open(String method,
458 String host, 576 String host,
459 int port, 577 int port,
460 String path) { 578 String path) {
461 // TODO(sgjesse): The path set here can contain both query and 579 // TODO(sgjesse): The path set here can contain both query and
462 // fragment. They should be cracked and set correctly. 580 // fragment. They should be cracked and set correctly.
463 return _open(method, new Uri.fromComponents( 581 return _openUrl(method, new Uri.fromComponents(
464 scheme: "http", domain: host, port: port, path: path)); 582 scheme: "http", domain: host, port: port, path: path));
465 } 583 }
466 584
467 Future<HttpClientRequest> openUrl(String method, Uri url) { 585 Future<HttpClientRequest> openUrl(String method, Uri url) {
468 return _openUrl(method, url); 586 return _openUrl(method, url);
469 } 587 }
470 588
471 Future<HttpClientRequest> get(String host, 589 Future<HttpClientRequest> get(String host,
472 int port, 590 int port,
473 String path) { 591 String path) {
474 return open("get", host, port, path); 592 return open("get", host, port, path);
475 } 593 }
476 594
477 Future<HttpClientRequest> getUrl(Uri url) { 595 Future<HttpClientRequest> getUrl(Uri url) {
478 return _open("get", url); 596 return _openUrl("get", url);
479 } 597 }
480 598
481 Future<HttpClientRequest> post(String host, 599 Future<HttpClientRequest> post(String host,
482 int port, 600 int port,
483 String path) { 601 String path) {
484 return open("post", host, port, path); 602 return open("post", host, port, path);
485 } 603 }
486 604
487 Future<HttpClientRequest> postUrl(Uri url) { 605 Future<HttpClientRequest> postUrl(Uri url) {
488 return _open("post", url); 606 return _openUrl("post", url);
489 } 607 }
490 608
491 void close() { 609 void close() {
492 for (var queue in _idleConnections.values) { 610 for (var queue in _idleConnections.values) {
493 for (var connection in queue) { 611 for (var connection in queue) {
494 connection.destroy(); 612 connection.destroy();
495 } 613 }
496 } 614 }
497 _idleConnections.clear(); 615 _idleConnections.clear();
498 for (var connection in _activeConnections) { 616 for (var connection in _activeConnections) {
499 connection.destroy(); 617 connection.destroy();
500 } 618 }
501 _activeConnections.clear(); 619 _activeConnections.clear();
502 } 620 }
503 621
504 Future<HttpClientRequest> _open(String method, 622 Future<HttpClientRequest> _openUrl(String method,
505 Uri uri, 623 Uri uri,
506 [_HttpClientConnection connection]) { 624 [_HttpClientConnection connection]) {
507 if (method == null) { 625 if (method == null) {
508 throw new ArgumentError(method); 626 throw new ArgumentError(method);
509 } 627 }
510 // TODO(ajohnsen): Handle HTTPS. 628 // TODO(ajohnsen): Handle HTTPS.
511 if (uri.domain.isEmpty || uri.scheme != "http") { 629 if (uri.domain.isEmpty || uri.scheme != "http") {
512 throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri"); 630 throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri");
513 } 631 }
514 632
515 // TODO(ajohnsen): Proxy? 633 // TODO(ajohnsen): Proxy?
516 Future future; 634 Future future;
635 int port = uri.port;
636 if (port == 0) port = HttpClient.DEFAULT_HTTP_PORT;
517 if (connection == null) { 637 if (connection == null) {
518 int port = uri.port;
519 if (port == 0) port = HttpClient.DEFAULT_HTTP_PORT;
520 future = _getConnection(uri.domain, port); 638 future = _getConnection(uri.domain, port);
521 } else { 639 } else {
522 future = new Future.immediate(connection); 640 future = new Future.immediate(connection);
523 } 641 }
524 642
525 return future.then((_HttpClientConnection connection) { 643 return future.then((_HttpClientConnection connection) {
526 onDone() { 644 onDone() {
527 // Called when the connection request/response sequence has ended. 645 // Called when the connection request/response sequence has ended.
528 _returnConnection(connection); 646 _returnConnection(connection);
529 } 647 }
530 // Create new internal outgoing connection. 648 // Create new internal outgoing connection.
531 var outgoing = new _HttpOutgoingConnection(); 649 var outgoing = new _HttpOutgoingConnection();
532 // Create new request object, wrapping the outgoing connection. 650 // Create new request object, wrapping the outgoing connection.
533 var request = new _HttpClientRequest( 651 var request = new _HttpClientRequest(outgoing,
534 uri, method.toUpperCase(), outgoing); 652 uri,
653 method.toUpperCase(),
654 this);
655 request.headers.host = uri.domain;
656 request.headers.port = port;
535 // Start sending the request (lazy, delayed until the user provides 657 // Start sending the request (lazy, delayed until the user provides
536 // data). 658 // data).
537 connection.sendRequest(outgoing, onDone) 659 connection.sendRequest(outgoing, onDone)
538 .then((incoming) { 660 .then((incoming) {
539 // The full request have been sent and a response is received 661 // The full request have been sent and a response is received
540 // containing status-code, headers and etc. 662 // containing status-code, headers and etc.
541 request._onIncoming(incoming); 663 request._onIncoming(incoming);
542 }) 664 })
543 .catchError((error) { 665 .catchError((error) {
544 // An error occoured before the http-header was parsed. This 666 // An error occoured before the http-header was parsed. This
(...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after
908 1030
909 1031
910 class _RedirectInfo implements RedirectInfo { 1032 class _RedirectInfo implements RedirectInfo {
911 const _RedirectInfo(int this.statusCode, 1033 const _RedirectInfo(int this.statusCode,
912 String this.method, 1034 String this.method,
913 Uri this.location); 1035 Uri this.location);
914 final int statusCode; 1036 final int statusCode;
915 final String method; 1037 final String method;
916 final Uri location; 1038 final Uri location;
917 } 1039 }
OLDNEW
« no previous file with comments | « sdk/lib/io/http.dart ('k') | sdk/lib/io/http_parser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698