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

Side by Side Diff: utils/pub/http.dart

Issue 12428012: Properly handle errors from the OAuth2 token endpoint in pub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Minor changes Created 7 years, 9 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 | « utils/pub/command_lish.dart ('k') | utils/pub/oauth2.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 /// Helpers for dealing with HTTP. 5 /// Helpers for dealing with HTTP.
6 library pub.http; 6 library pub.http;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:io'; 9 import 'dart:io';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
11 11
12 // TODO(nweiz): Make this import better. 12 // TODO(nweiz): Make this import better.
13 import '../../pkg/http/lib/http.dart' as http; 13 import '../../pkg/http/lib/http.dart' as http;
14 import 'io.dart'; 14 import 'io.dart';
15 import 'log.dart' as log; 15 import 'log.dart' as log;
16 import 'oauth2.dart' as oauth2;
16 import 'utils.dart'; 17 import 'utils.dart';
17 18
18 // TODO(nweiz): make this configurable 19 // TODO(nweiz): make this configurable
19 /// The amount of time in milliseconds to allow HTTP requests before assuming 20 /// The amount of time in milliseconds to allow HTTP requests before assuming
20 /// they've failed. 21 /// they've failed.
21 final HTTP_TIMEOUT = 30 * 1000; 22 final HTTP_TIMEOUT = 30 * 1000;
22 23
24 /// Headers and field names that should be censored in the log output.
25 final _CENSORED_FIELDS = const ['refresh_token', 'authorization'];
26
23 /// An HTTP client that transforms 40* errors and socket exceptions into more 27 /// An HTTP client that transforms 40* errors and socket exceptions into more
24 /// user-friendly error messages. 28 /// user-friendly error messages.
25 class PubHttpClient extends http.BaseClient { 29 class PubHttpClient extends http.BaseClient {
26 http.Client inner; 30 http.Client inner;
27 31
28 PubHttpClient([http.Client inner]) 32 PubHttpClient([http.Client inner])
29 : this.inner = inner == null ? new http.Client() : inner; 33 : this.inner = inner == null ? new http.Client() : inner;
30 34
31 Future<http.StreamedResponse> send(http.BaseRequest request) { 35 Future<http.StreamedResponse> send(http.BaseRequest request) {
32 // TODO(rnystrom): Log request body when it's available and plaintext, but 36 _logRequest(request);
33 // not when it contains OAuth2 credentials.
34 37
35 // TODO(nweiz): remove this when issue 4061 is fixed. 38 // TODO(nweiz): remove this when issue 4061 is fixed.
36 var stackTrace; 39 var stackTrace;
37 try { 40 try {
38 throw null; 41 throw null;
39 } catch (_, localStackTrace) { 42 } catch (_, localStackTrace) {
40 stackTrace = localStackTrace; 43 stackTrace = localStackTrace;
41 } 44 }
42 45
43 // TODO(nweiz): Ideally the timeout would extend to reading from the 46 // TODO(nweiz): Ideally the timeout would extend to reading from the
44 // response input stream, but until issue 3657 is fixed that's not feasible. 47 // response input stream, but until issue 3657 is fixed that's not feasible.
45 return timeout(inner.send(request).then((streamedResponse) { 48 return timeout(inner.send(request).then((streamedResponse) {
46 log.fine("Got response ${streamedResponse.statusCode} " 49 _logResponse(streamedResponse);
47 "${streamedResponse.reasonPhrase}.");
48 50
49 var status = streamedResponse.statusCode; 51 var status = streamedResponse.statusCode;
50 // 401 responses should be handled by the OAuth2 client. It's very 52 // 401 responses should be handled by the OAuth2 client. It's very
51 // unlikely that they'll be returned by non-OAuth2 requests. 53 // unlikely that they'll be returned by non-OAuth2 requests. We also want
52 if (status < 400 || status == 401) return streamedResponse; 54 // to pass along 400 responses from the token endpoint.
55 var tokenRequest = streamedResponse.request.url == oauth2.tokenEndpoint;
56 if (status < 400 || status == 401 || (status == 400 && tokenRequest)) {
57 return streamedResponse;
58 }
53 59
54 return http.Response.fromStream(streamedResponse).then((response) { 60 return http.Response.fromStream(streamedResponse).then((response) {
55 throw new PubHttpException(response); 61 throw new PubHttpException(response);
56 }); 62 });
57 }).catchError((asyncError) { 63 }).catchError((asyncError) {
58 if (asyncError.error is SocketIOException && 64 if (asyncError.error is SocketIOException &&
59 asyncError.error.osError != null) { 65 asyncError.error.osError != null) {
60 if (asyncError.error.osError.errorCode == 8 || 66 if (asyncError.error.osError.errorCode == 8 ||
61 asyncError.error.osError.errorCode == -2 || 67 asyncError.error.osError.errorCode == -2 ||
62 asyncError.error.osError.errorCode == -5 || 68 asyncError.error.osError.errorCode == -5 ||
63 asyncError.error.osError.errorCode == 11001 || 69 asyncError.error.osError.errorCode == 11001 ||
64 asyncError.error.osError.errorCode == 11004) { 70 asyncError.error.osError.errorCode == 11004) {
65 throw 'Could not resolve URL "${request.url.origin}".'; 71 throw 'Could not resolve URL "${request.url.origin}".';
66 } else if (asyncError.error.osError.errorCode == -12276) { 72 } else if (asyncError.error.osError.errorCode == -12276) {
67 throw 'Unable to validate SSL certificate for ' 73 throw 'Unable to validate SSL certificate for '
68 '"${request.url.origin}".'; 74 '"${request.url.origin}".';
69 } 75 }
70 } 76 }
71 throw asyncError; 77 throw asyncError;
72 }), HTTP_TIMEOUT, 'fetching URL "${request.url}"'); 78 }), HTTP_TIMEOUT, 'fetching URL "${request.url}"');
73 } 79 }
80
81 /// Logs the fact that [request] was sent, and information about it.
82 void _logRequest(http.BaseRequest request) {
83 var requestLog = new StringBuffer();
84 requestLog.writeln("HTTP ${request.method} ${request.url}");
85 request.headers.forEach((name, value) =>
86 requestLog.writeln(_logField(name, value)));
87
88 if (request.method == 'POST') {
89 var contentTypeString = request.headers[HttpHeaders.CONTENT_TYPE];
90 if (contentTypeString == null) contentTypeString = '';
91 var contentType = new ContentType.fromString(contentTypeString);
92 if (contentType.value == 'application/x-www-form-urlencoded') {
93 requestLog.writeln('');
94 requestLog.writeln("Body fields:");
95 request.bodyFields.forEach((name, value) =>
96 requestLog.writeln(_logField(name, value)));
97 } else if (contentType.value == 'text/plain' ||
98 contentType.value == 'application/json') {
99 requestLog.write(request.body);
100 } else if (request is http.MultipartRequest) {
101 requestLog.writeln('');
102 requestLog.writeln("Body fields:");
103 request.fields.forEach((name, value) =>
104 requestLog.writeln(_logField(name, value)));
105
106 // TODO(nweiz): make MultipartRequest.files readable, and log them?
107 }
108 }
109
110 log.fine(requestLog.toString().trim());
111 }
112
113 /// Logs the fact that [response] was received, and information about it.
114 void _logResponse(http.StreamedResponse response) {
115 // TODO(nweiz): Fork the response stream and log the response body. Be
116 // careful not to log OAuth2 private data, though.
117
118 var responseLog = new StringBuffer();
119 var request = response.request;
120 responseLog.writeln("HTTP response ${response.statusCode} "
121 "${response.reasonPhrase} for ${request.method} ${request.url}");
122 response.headers.forEach((name, value) =>
123 responseLog.writeln(_logField(name, value)));
124
125 log.fine(responseLog.toString().trim());
126 }
127
128 /// Returns a log-formatted string for the HTTP field or header with the given
129 /// [name] and [value].
130 String _logField(String name, String value) {
131 if (_CENSORED_FIELDS.contains(name.toLowerCase())) {
132 return "$name: <censored>";
133 } else {
134 return "$name: $value";
135 }
136 }
74 } 137 }
75 138
76 /// The HTTP client to use for all HTTP requests. 139 /// The HTTP client to use for all HTTP requests.
77 final httpClient = new PubHttpClient(); 140 final httpClient = new PubHttpClient();
78 141
79 /// Handles a successful JSON-formatted response from pub.dartlang.org. 142 /// Handles a successful JSON-formatted response from pub.dartlang.org.
80 /// 143 ///
81 /// These responses are expected to be of the form `{"success": {"message": 144 /// These responses are expected to be of the form `{"success": {"message":
82 /// "some message"}}`. If the format is correct, the message will be printed; 145 /// "some message"}}`. If the format is correct, the message will be printed;
83 /// otherwise an error will be raised. 146 /// otherwise an error will be raised.
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
127 190
128 /// Exception thrown when an HTTP operation fails. 191 /// Exception thrown when an HTTP operation fails.
129 class PubHttpException implements Exception { 192 class PubHttpException implements Exception {
130 final http.Response response; 193 final http.Response response;
131 194
132 const PubHttpException(this.response); 195 const PubHttpException(this.response);
133 196
134 String toString() => 'HTTP error ${response.statusCode}: ' 197 String toString() => 'HTTP error ${response.statusCode}: '
135 '${response.reasonPhrase}'; 198 '${response.reasonPhrase}';
136 } 199 }
OLDNEW
« no previous file with comments | « utils/pub/command_lish.dart ('k') | utils/pub/oauth2.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698