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

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: Remove half a TODO. 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
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 final List<RedirectInfo> redirects;
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,
130 List<RedirectInfo> this.redirects)
Søren Gjesse 2013/01/09 08:43:49 Do you need to pass this? Isn't it just _httpReque
Anders Johnsen 2013/01/09 10:03:00 Done.
120 : super(_incomingConnection); 131 : super(_incomingConnection);
121 132
122 int get statusCode => _incomingConnection.statusCode; 133 int get statusCode => _incomingConnection.statusCode;
123 String get reasonPhrase => _incomingConnection.reasonPhrase; 134 String get reasonPhrase => _incomingConnection.reasonPhrase;
124 135
136 bool get isRedirect {
137 if (_httpRequest.method == "GET" || _httpRequest.method == "HEAD") {
138 return statusCode == HttpStatus.MOVED_PERMANENTLY ||
139 statusCode == HttpStatus.FOUND ||
140 statusCode == HttpStatus.SEE_OTHER ||
141 statusCode == HttpStatus.TEMPORARY_REDIRECT;
142 } else if (_httpRequest.method == "POST") {
143 return statusCode == HttpStatus.SEE_OTHER;
144 }
145 return false;
146 }
147
148 /**
149 * Redirect this connection to a new URL. The default value for
150 * [method] is the method for the current request. The default value
151 * for [url] is the value of the [:HttpHeaders.LOCATION:] header of
152 * the current response. All body data must have been read from the
153 * current response before calling [redirect].
154 *
155 * All headers added to the request will be added to the redirection
156 * request(s). However, any body send with the request will not be
157 * part of the redirection request(s).
Søren Gjesse 2013/01/09 08:43:49 No need for this comment in the implementation fil
Anders Johnsen 2013/01/09 10:03:00 Done.
158 */
159 Future<HttpClientResponse> redirect([String method,
160 Uri url,
161 bool followLoops]) {
162 if (method == null) {
163 if (statusCode == HttpStatus.SEE_OTHER && _httpRequest.method == "POST") {
Søren Gjesse 2013/01/09 08:43:49 Add comment with reference to RFC 2616 section 10.
Anders Johnsen 2013/01/09 10:03:00 Done.
164 method = "GET";
165 } else {
166 method = _httpRequest.method;
167 }
168 }
169 if (url == null) {
170 if (!isRedirect) throw new StateError("Response is not redirecting");
Søren Gjesse 2013/01/09 08:43:49 "Response is not redirecting" => Response has no L
Søren Gjesse 2013/01/09 08:43:49 We should probably only check for the Location hea
Anders Johnsen 2013/01/09 10:03:00 Done.
Anders Johnsen 2013/01/09 10:03:00 Done.
171 url = new Uri.fromString(headers.value(HttpHeaders.LOCATION));
172 }
173 if (followLoops != true) {
174 for (var redirect in redirects) {
175 if (redirect.location == url) {
176 return new Future.immediateError(
177 new RedirectLoopException(redirects));
178 }
179 }
180 }
181 return _httpClient.openUrl(method, url)
182 .then((request) {
183 // Only follow redirects if initial request did.
184 request.followRedirects = _httpRequest.followRedirects;
185 // Allow one less redirect.
Søren Gjesse 2013/01/09 08:43:49 The "one less" in the comment is not reflected in
Anders Johnsen 2013/01/09 10:03:00 Done.
186 request.maxRedirects = _httpRequest.maxRedirects;
187 // Copy headers
Søren Gjesse 2013/01/09 08:43:49 Terminate comment with .
Anders Johnsen 2013/01/09 10:03:00 Done.
188 for (var header in _httpRequest.headers._headers.keys) {
189 if (header != HttpHeaders.HOST.toLowerCase()) {
190 request.headers.set(header, _httpRequest.headers[header]);
191 }
192 }
193 request.headers.contentLength = 0;
194 var redirects = [];
Søren Gjesse 2013/01/09 08:43:49 Just operate directly on request._responseRedirect
Anders Johnsen 2013/01/09 10:03:00 Done.
195 redirects.addAll(this.redirects);
196 redirects.add(new _RedirectInfo(statusCode, method, url));
197 request._setResponseRedirects(redirects);
198 return request.close();
199 });
200 }
201
125 StreamSubscription<List<int>> listen(void onData(List<int> event), 202 StreamSubscription<List<int>> listen(void onData(List<int> event),
126 {void onError(AsyncError error), 203 {void onError(AsyncError error),
127 void onDone(), 204 void onDone(),
128 bool unsubscribeOnError}) { 205 bool unsubscribeOnError}) {
129 return _incomingConnection.listen( 206 return _incomingConnection.listen(
130 onData, 207 onData,
131 onError: onError, 208 onError: onError,
132 onDone: onDone, 209 onDone: onDone,
133 unsubscribeOnError: unsubscribeOnError); 210 unsubscribeOnError: unsubscribeOnError);
134 } 211 }
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 300
224 // Write headers. 301 // Write headers.
225 headers._write(this); 302 headers._write(this);
226 writeCRLF(); 303 writeCRLF();
227 } 304 }
228 } 305 }
229 306
230 307
231 class _HttpClientRequest extends _HttpOutgoing<HttpClientRequest> 308 class _HttpClientRequest extends _HttpOutgoing<HttpClientRequest>
232 implements HttpClientRequest { 309 implements HttpClientRequest {
233 _HttpClientRequest(Uri this.uri, 310 final String method;
311 final Uri uri;
312 final List<Cookie> cookies = new List<Cookie>();
313
314 // The HttpClient this request belongs to.
315 final _HttpClient _httpClient;
316
317 final Completer<HttpClientResponse> _responseCompleter
318 = new Completer<HttpClientResponse>();
319
320
321 // TODO(ajohnsen): Get default value from client?
322 bool _followRedirects = true;
323
324 int _maxRedirects = 5;
325
326 List<RedirectInfo> _responseRedirects = [];
327
328 _HttpClientRequest(_HttpOutgoingConnection outgoing,
329 Uri this.uri,
234 String this.method, 330 String this.method,
235 _HttpOutgoingConnection outgoing) 331 _HttpClient this._httpClient)
236 : super("1.1", outgoing) { 332 : super("1.1", outgoing) {
237 // GET and HEAD have 'content-length: 0' by default. 333 // GET and HEAD have 'content-length: 0' by default.
238 if (method == "GET" || method == "HEAD") { 334 if (method == "GET" || method == "HEAD") {
239 contentLength = 0; 335 contentLength = 0;
240 } 336 }
241 } 337 }
242 338
339 Future<HttpClientResponse> get response => _responseCompleter.future;
340
243 Future<HttpClientResponse> close() { 341 Future<HttpClientResponse> close() {
244 super.close(); 342 super.close();
245 return response; 343 return response;
246 } 344 }
247 345
346 int get maxRedirects => _maxRedirects;
347 void set maxRedirects(int maxRedirects) {
348 if (_headersWritten) throw new StateError("Request already sent");
349 _maxRedirects = maxRedirects;
350 }
351
352 bool get followRedirects => _followRedirects;
353 void set followRedirects(bool followRedirects) {
354 if (_headersWritten) throw new StateError("Request already sent");
355 _followRedirects = followRedirects;
356 }
357
248 void _onIncoming(_HttpIncomingConnection incoming) { 358 void _onIncoming(_HttpIncomingConnection incoming) {
249 // TODO(ajohnsen): Handle redirect and auth. 359 // TODO(ajohnsen): Handle auth.
250 _responseCompleter.complete(new _HttpClientResponse(incoming)); 360 var response = new _HttpClientResponse(incoming,
361 this,
362 _httpClient,
363 _responseRedirects);
364
365 Future<HttpClientResponse> future;
366
367 if (followRedirects &&
368 response.isRedirect) {
369 if (response.redirects.length < maxRedirects) {
370 // Redirect
371 future = response.redirect();
372 } else {
373 // End with exception, too many redirects.
374 future = new Future.immediateError(
375 new RedirectLimitExceededException(response.redirects));
376 }
377 } else {
378 future = new Future<HttpClientResponse>.immediate(response);
379 }
380
381 future.then(
382 _responseCompleter.complete,
383 onError: (e) {
384 _responseCompleter.completeError(e.error, e.stackTrace);
385 });
251 } 386 }
252 387
253 void _onError(AsyncError error) { 388 void _onError(AsyncError error) {
254 _responseCompleter.completeError(error.error, error.stackTrace); 389 _responseCompleter.completeError(error.error, error.stackTrace);
255 } 390 }
256 391
257 void _writeHeader() { 392 void _writeHeader() {
258 writeSP() => add([_CharCode.SP]); 393 writeSP() => add([_CharCode.SP]);
259 writeCRLF() => add([_CharCode.CR, _CharCode.LF]); 394 writeCRLF() => add([_CharCode.CR, _CharCode.LF]);
260 395
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 headers.add("cookie", sb.toString()); 427 headers.add("cookie", sb.toString());
293 } 428 }
294 429
295 headers._finalize(); 430 headers._finalize();
296 431
297 // Write headers. 432 // Write headers.
298 headers._write(this); 433 headers._write(this);
299 writeCRLF(); 434 writeCRLF();
300 } 435 }
301 436
302 Future<HttpClientResponse> get response => _responseCompleter.future; 437 void _setResponseRedirects(List<RedirectInfo> redirects) {
Søren Gjesse 2013/01/09 08:43:49 Why do you need this method?
Anders Johnsen 2013/01/09 10:03:00 Done.
303 438 _responseRedirects = redirects;
304 final String method; 439 }
305 final Uri uri;
306 final List<Cookie> cookies = new List<Cookie>();
307 final Completer<HttpClientResponse> _responseCompleter
308 = new Completer<HttpClientResponse>();
309 } 440 }
310 441
311 442
312 // Transformer that transforms data to HTTP Chunked Encoding. 443 // Transformer that transforms data to HTTP Chunked Encoding.
313 class _ChunkedTransformer extends StreamController<List<int>> 444 class _ChunkedTransformer extends StreamController<List<int>>
314 implements StreamTransformer<List<int>, List<int>> { 445 implements StreamTransformer<List<int>, List<int>> {
315 _ChunkedTransformer() : super.singleSubscription(); 446 _ChunkedTransformer() : super.singleSubscription();
316 447
317 Stream<List<int>> bind(Stream<List<int>> stream) { 448 Stream<List<int>> bind(Stream<List<int>> stream) {
318 var subscription = stream.listen( 449 var subscription = stream.listen(
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
453 class _HttpClient implements HttpClient { 584 class _HttpClient implements HttpClient {
454 // TODO(ajohnsen): Use eviction timeout. 585 // TODO(ajohnsen): Use eviction timeout.
455 static const int DEFAULT_EVICTION_TIMEOUT = 60000; 586 static const int DEFAULT_EVICTION_TIMEOUT = 60000;
456 587
457 Future<HttpClientRequest> open(String method, 588 Future<HttpClientRequest> open(String method,
458 String host, 589 String host,
459 int port, 590 int port,
460 String path) { 591 String path) {
461 // TODO(sgjesse): The path set here can contain both query and 592 // TODO(sgjesse): The path set here can contain both query and
462 // fragment. They should be cracked and set correctly. 593 // fragment. They should be cracked and set correctly.
463 return _open(method, new Uri.fromComponents( 594 return _openUrl(method, new Uri.fromComponents(
464 scheme: "http", domain: host, port: port, path: path)); 595 scheme: "http", domain: host, port: port, path: path));
465 } 596 }
466 597
467 Future<HttpClientRequest> openUrl(String method, Uri url) { 598 Future<HttpClientRequest> openUrl(String method, Uri url) {
468 return _openUrl(method, url); 599 return _openUrl(method, url);
469 } 600 }
470 601
471 Future<HttpClientRequest> get(String host, 602 Future<HttpClientRequest> get(String host,
472 int port, 603 int port,
473 String path) { 604 String path) {
474 return open("get", host, port, path); 605 return open("get", host, port, path);
475 } 606 }
476 607
477 Future<HttpClientRequest> getUrl(Uri url) { 608 Future<HttpClientRequest> getUrl(Uri url) {
478 return _open("get", url); 609 return _openUrl("get", url);
479 } 610 }
480 611
481 Future<HttpClientRequest> post(String host, 612 Future<HttpClientRequest> post(String host,
482 int port, 613 int port,
483 String path) { 614 String path) {
484 return open("post", host, port, path); 615 return open("post", host, port, path);
485 } 616 }
486 617
487 Future<HttpClientRequest> postUrl(Uri url) { 618 Future<HttpClientRequest> postUrl(Uri url) {
488 return _open("post", url); 619 return _openUrl("post", url);
489 } 620 }
490 621
491 void close() { 622 void close() {
492 for (var queue in _idleConnections.values) { 623 for (var queue in _idleConnections.values) {
493 for (var connection in queue) { 624 for (var connection in queue) {
494 connection.destroy(); 625 connection.destroy();
495 } 626 }
496 } 627 }
497 _idleConnections.clear(); 628 _idleConnections.clear();
498 for (var connection in _activeConnections) { 629 for (var connection in _activeConnections) {
499 connection.destroy(); 630 connection.destroy();
500 } 631 }
501 _activeConnections.clear(); 632 _activeConnections.clear();
502 } 633 }
503 634
504 Future<HttpClientRequest> _open(String method, 635 Future<HttpClientRequest> _openUrl(String method,
505 Uri uri, 636 Uri uri,
506 [_HttpClientConnection connection]) { 637 [_HttpClientConnection connection]) {
507 if (method == null) { 638 if (method == null) {
508 throw new ArgumentError(method); 639 throw new ArgumentError(method);
509 } 640 }
510 // TODO(ajohnsen): Handle HTTPS. 641 // TODO(ajohnsen): Handle HTTPS.
511 if (uri.domain.isEmpty || uri.scheme != "http") { 642 if (uri.domain.isEmpty || uri.scheme != "http") {
512 throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri"); 643 throw new ArgumentError("Unsupported scheme '${uri.scheme}' in $uri");
513 } 644 }
514 645
515 // TODO(ajohnsen): Proxy? 646 // TODO(ajohnsen): Proxy?
516 Future future; 647 Future future;
648 int port = uri.port;
649 if (port == 0) port = HttpClient.DEFAULT_HTTP_PORT;
517 if (connection == null) { 650 if (connection == null) {
518 int port = uri.port;
519 if (port == 0) port = HttpClient.DEFAULT_HTTP_PORT;
520 future = _getConnection(uri.domain, port); 651 future = _getConnection(uri.domain, port);
521 } else { 652 } else {
522 future = new Future.immediate(connection); 653 future = new Future.immediate(connection);
523 } 654 }
524 655
525 return future.then((_HttpClientConnection connection) { 656 return future.then((_HttpClientConnection connection) {
526 onDone() { 657 onDone() {
527 // Called when the connection request/response sequence has ended. 658 // Called when the connection request/response sequence has ended.
528 _returnConnection(connection); 659 _returnConnection(connection);
529 } 660 }
530 // Create new internal outgoing connection. 661 // Create new internal outgoing connection.
531 var outgoing = new _HttpOutgoingConnection(); 662 var outgoing = new _HttpOutgoingConnection();
532 // Create new request object, wrapping the outgoing connection. 663 // Create new request object, wrapping the outgoing connection.
533 var request = new _HttpClientRequest( 664 var request = new _HttpClientRequest(outgoing,
534 uri, method.toUpperCase(), outgoing); 665 uri,
666 method.toUpperCase(),
667 this);
668 request.headers.host = uri.domain;
669 request.headers.port = port;
535 // Start sending the request (lazy, delayed until the user provides 670 // Start sending the request (lazy, delayed until the user provides
536 // data). 671 // data).
537 connection.sendRequest(outgoing, onDone) 672 connection.sendRequest(outgoing, onDone)
538 .then((incoming) { 673 .then((incoming) {
539 // The full request have been sent and a response is received 674 // The full request have been sent and a response is received
540 // containing status-code, headers and etc. 675 // containing status-code, headers and etc.
541 request._onIncoming(incoming); 676 request._onIncoming(incoming);
542 }) 677 })
543 .catchError((error) { 678 .catchError((error) {
544 // An error occoured before the http-header was parsed. This 679 // An error occoured before the http-header was parsed. This
(...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after
908 1043
909 1044
910 class _RedirectInfo implements RedirectInfo { 1045 class _RedirectInfo implements RedirectInfo {
911 const _RedirectInfo(int this.statusCode, 1046 const _RedirectInfo(int this.statusCode,
912 String this.method, 1047 String this.method,
913 Uri this.location); 1048 Uri this.location);
914 final int statusCode; 1049 final int statusCode;
915 final String method; 1050 final String method;
916 final Uri location; 1051 final Uri location;
917 } 1052 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698