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

Side by Side Diff: sdk/lib/_internal/pub/lib/src/http.dart

Issue 1165473002: Start pulling pub from its own repo. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Code review changes Created 5 years, 6 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
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Helpers for dealing with HTTP.
6 library pub.http;
7
8 import 'dart:async';
9 import 'dart:convert';
10 import 'dart:io';
11
12 import 'package:http/http.dart' as http;
13 import 'package:http_throttle/http_throttle.dart';
14 import 'package:stack_trace/stack_trace.dart';
15
16 import 'io.dart';
17 import 'log.dart' as log;
18 import 'oauth2.dart' as oauth2;
19 import 'sdk.dart' as sdk;
20 import 'utils.dart';
21
22 // TODO(nweiz): make this configurable
23 /// The amount of time in milliseconds to allow HTTP requests before assuming
24 /// they've failed.
25 final HTTP_TIMEOUT = 30 * 1000;
26
27 /// Headers and field names that should be censored in the log output.
28 final _CENSORED_FIELDS = const ['refresh_token', 'authorization'];
29
30 /// Headers required for pub.dartlang.org API requests.
31 ///
32 /// The Accept header tells pub.dartlang.org which version of the API we're
33 /// expecting, so it can either serve that version or give us a 406 error if
34 /// it's not supported.
35 final PUB_API_HEADERS = const {'Accept': 'application/vnd.pub.v2+json'};
36
37 /// An HTTP client that transforms 40* errors and socket exceptions into more
38 /// user-friendly error messages.
39 ///
40 /// This also adds a 30-second timeout to every request. This can be configured
41 /// on a per-request basis by setting the 'Pub-Request-Timeout' header to the
42 /// desired number of milliseconds, or to "None" to disable the timeout.
43 class _PubHttpClient extends http.BaseClient {
44 final _requestStopwatches = new Map<http.BaseRequest, Stopwatch>();
45
46 http.Client _inner;
47
48 _PubHttpClient([http.Client inner])
49 : this._inner = inner == null ? new http.Client() : inner;
50
51 Future<http.StreamedResponse> send(http.BaseRequest request) {
52 _requestStopwatches[request] = new Stopwatch()..start();
53 request.headers[HttpHeaders.USER_AGENT] = "Dart pub ${sdk.version}";
54 _logRequest(request);
55
56 var timeoutLength = HTTP_TIMEOUT;
57 var timeoutString = request.headers.remove('Pub-Request-Timeout');
58 if (timeoutString == 'None') {
59 timeoutLength = null;
60 } else if (timeoutString != null) {
61 timeoutLength = int.parse(timeoutString);
62 }
63
64 var future = _inner.send(request).then((streamedResponse) {
65 _logResponse(streamedResponse);
66
67 var status = streamedResponse.statusCode;
68 // 401 responses should be handled by the OAuth2 client. It's very
69 // unlikely that they'll be returned by non-OAuth2 requests. We also want
70 // to pass along 400 responses from the token endpoint.
71 var tokenRequest = urisEqual(
72 streamedResponse.request.url, oauth2.tokenEndpoint);
73 if (status < 400 || status == 401 || (status == 400 && tokenRequest)) {
74 return streamedResponse;
75 }
76
77 if (status == 406 &&
78 request.headers['Accept'] == PUB_API_HEADERS['Accept']) {
79 fail("Pub ${sdk.version} is incompatible with the current version of "
80 "${request.url.host}.\n"
81 "Upgrade pub to the latest version and try again.");
82 }
83
84 if (status == 500 &&
85 (request.url.host == "pub.dartlang.org" ||
86 request.url.host == "storage.googleapis.com")) {
87 var message = "HTTP error 500: Internal Server Error at "
88 "${request.url}.";
89
90 if (request.url.host == "pub.dartlang.org" ||
91 request.url.host == "storage.googleapis.com") {
92 message += "\nThis is likely a transient error. Please try again "
93 "later.";
94 }
95
96 fail(message);
97 }
98
99 return http.Response.fromStream(streamedResponse).then((response) {
100 throw new PubHttpException(response);
101 });
102 }).catchError((error, stackTrace) {
103 // Work around issue 23008.
104 if (stackTrace == null) stackTrace = new Chain.current();
105
106 if (error is SocketException &&
107 error.osError != null) {
108 if (error.osError.errorCode == 8 ||
109 error.osError.errorCode == -2 ||
110 error.osError.errorCode == -5 ||
111 error.osError.errorCode == 11001 ||
112 error.osError.errorCode == 11004) {
113 fail('Could not resolve URL "${request.url.origin}".',
114 error, stackTrace);
115 } else if (error.osError.errorCode == -12276) {
116 fail('Unable to validate SSL certificate for '
117 '"${request.url.origin}".',
118 error, stackTrace);
119 }
120 }
121 throw error;
122 });
123
124 if (timeoutLength == null) return future;
125 return timeout(future, timeoutLength, request.url,
126 'fetching URL "${request.url}"');
127 }
128
129 /// Logs the fact that [request] was sent, and information about it.
130 void _logRequest(http.BaseRequest request) {
131 var requestLog = new StringBuffer();
132 requestLog.writeln("HTTP ${request.method} ${request.url}");
133 request.headers.forEach((name, value) =>
134 requestLog.writeln(_logField(name, value)));
135
136 if (request.method == 'POST') {
137 var contentTypeString = request.headers[HttpHeaders.CONTENT_TYPE];
138 if (contentTypeString == null) contentTypeString = '';
139 var contentType = ContentType.parse(contentTypeString);
140 if (request is http.MultipartRequest) {
141 requestLog.writeln();
142 requestLog.writeln("Body fields:");
143 request.fields.forEach((name, value) =>
144 requestLog.writeln(_logField(name, value)));
145
146 // TODO(nweiz): make MultipartRequest.files readable, and log them?
147 } else if (request is http.Request) {
148 if (contentType.value == 'application/x-www-form-urlencoded') {
149 requestLog.writeln();
150 requestLog.writeln("Body fields:");
151 request.bodyFields.forEach((name, value) =>
152 requestLog.writeln(_logField(name, value)));
153 } else if (contentType.value == 'text/plain' ||
154 contentType.value == 'application/json') {
155 requestLog.write(request.body);
156 }
157 }
158 }
159
160 log.fine(requestLog.toString().trim());
161 }
162
163 /// Logs the fact that [response] was received, and information about it.
164 void _logResponse(http.StreamedResponse response) {
165 // TODO(nweiz): Fork the response stream and log the response body. Be
166 // careful not to log OAuth2 private data, though.
167
168 var responseLog = new StringBuffer();
169 var request = response.request;
170 var stopwatch = _requestStopwatches.remove(request)..stop();
171 responseLog.writeln("HTTP response ${response.statusCode} "
172 "${response.reasonPhrase} for ${request.method} ${request.url}");
173 responseLog.writeln("took ${stopwatch.elapsed}");
174 response.headers.forEach((name, value) =>
175 responseLog.writeln(_logField(name, value)));
176
177 log.fine(responseLog.toString().trim());
178 }
179
180 /// Returns a log-formatted string for the HTTP field or header with the given
181 /// [name] and [value].
182 String _logField(String name, String value) {
183 if (_CENSORED_FIELDS.contains(name.toLowerCase())) {
184 return "$name: <censored>";
185 } else {
186 return "$name: $value";
187 }
188 }
189 }
190
191 /// The [_PubHttpClient] wrapped by [httpClient].
192 final _pubClient = new _PubHttpClient();
193
194 /// The HTTP client to use for all HTTP requests.
195 final httpClient = new ThrottleClient(16, _pubClient);
196
197 /// The underlying HTTP client wrapped by [httpClient].
198 http.Client get innerHttpClient => _pubClient._inner;
199 set innerHttpClient(http.Client client) => _pubClient._inner = client;
200
201 /// Handles a successful JSON-formatted response from pub.dartlang.org.
202 ///
203 /// These responses are expected to be of the form `{"success": {"message":
204 /// "some message"}}`. If the format is correct, the message will be printed;
205 /// otherwise an error will be raised.
206 void handleJsonSuccess(http.Response response) {
207 var parsed = parseJsonResponse(response);
208 if (parsed['success'] is! Map ||
209 !parsed['success'].containsKey('message') ||
210 parsed['success']['message'] is! String) {
211 invalidServerResponse(response);
212 }
213 log.message(parsed['success']['message']);
214 }
215
216 /// Handles an unsuccessful JSON-formatted response from pub.dartlang.org.
217 ///
218 /// These responses are expected to be of the form `{"error": {"message": "some
219 /// message"}}`. If the format is correct, the message will be raised as an
220 /// error; otherwise an [invalidServerResponse] error will be raised.
221 void handleJsonError(http.Response response) {
222 var errorMap = parseJsonResponse(response);
223 if (errorMap['error'] is! Map ||
224 !errorMap['error'].containsKey('message') ||
225 errorMap['error']['message'] is! String) {
226 invalidServerResponse(response);
227 }
228 fail(errorMap['error']['message']);
229 }
230
231 /// Parses a response body, assuming it's JSON-formatted.
232 ///
233 /// Throws a user-friendly error if the response body is invalid JSON, or if
234 /// it's not a map.
235 Map parseJsonResponse(http.Response response) {
236 var value;
237 try {
238 value = JSON.decode(response.body);
239 } on FormatException {
240 invalidServerResponse(response);
241 }
242 if (value is! Map) invalidServerResponse(response);
243 return value;
244 }
245
246 /// Throws an error describing an invalid response from the server.
247 void invalidServerResponse(http.Response response) =>
248 fail('Invalid server response:\n${response.body}');
249
250 /// Exception thrown when an HTTP operation fails.
251 class PubHttpException implements Exception {
252 final http.Response response;
253
254 const PubHttpException(this.response);
255
256 String toString() => 'HTTP error ${response.statusCode}: '
257 '${response.reasonPhrase}';
258 }
OLDNEW
« no previous file with comments | « sdk/lib/_internal/pub/lib/src/global_packages.dart ('k') | sdk/lib/_internal/pub/lib/src/io.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698