| Index: utils/pub/http.dart
|
| diff --git a/utils/pub/http.dart b/utils/pub/http.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9190a416ca65159ba86d16dc621f6fad3435bb12
|
| --- /dev/null
|
| +++ b/utils/pub/http.dart
|
| @@ -0,0 +1,138 @@
|
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +/// Helpers for dealing with HTTP.
|
| +library http;
|
| +
|
| +import 'dart:io';
|
| +import 'dart:json';
|
| +
|
| +// TODO(nweiz): Make this import better.
|
| +import '../../pkg/http/lib/http.dart' as http;
|
| +import 'curl_client.dart';
|
| +import 'io.dart';
|
| +import 'log.dart' as log;
|
| +
|
| +// TODO(nweiz): make this configurable
|
| +/**
|
| + * The amount of time in milliseconds to allow HTTP requests before assuming
|
| + * they've failed.
|
| + */
|
| +final HTTP_TIMEOUT = 30 * 1000;
|
| +
|
| +/// An HTTP client that transforms 40* errors and socket exceptions into more
|
| +/// user-friendly error messages.
|
| +class PubHttpClient extends http.BaseClient {
|
| + final http.Client _inner;
|
| +
|
| + PubHttpClient([http.Client inner])
|
| + : _inner = inner == null ? new http.Client() : inner;
|
| +
|
| + Future<http.StreamedResponse> send(http.BaseRequest request) {
|
| + // TODO(rnystrom): Log request body when it's available and plaintext, but
|
| + // not when it contains OAuth2 credentials.
|
| +
|
| + // TODO(nweiz): remove this when issue 4061 is fixed.
|
| + var stackTrace;
|
| + try {
|
| + throw null;
|
| + } catch (_, localStackTrace) {
|
| + stackTrace = localStackTrace;
|
| + }
|
| +
|
| + // TODO(nweiz): Ideally the timeout would extend to reading from the
|
| + // response input stream, but until issue 3657 is fixed that's not feasible.
|
| + return timeout(_inner.send(request).chain((streamedResponse) {
|
| + log.fine("Got response ${streamedResponse.statusCode} "
|
| + "${streamedResponse.reasonPhrase}.");
|
| +
|
| + var status = streamedResponse.statusCode;
|
| + // 401 responses should be handled by the OAuth2 client. It's very
|
| + // unlikely that they'll be returned by non-OAuth2 requests.
|
| + if (status < 400 || status == 401) {
|
| + return new Future.immediate(streamedResponse);
|
| + }
|
| +
|
| + return http.Response.fromStream(streamedResponse).transform((response) {
|
| + throw new PubHttpException(response);
|
| + });
|
| + }).transformException((e) {
|
| + if (e is SocketIOException &&
|
| + e.osError != null &&
|
| + (e.osError.errorCode == 8 ||
|
| + e.osError.errorCode == -2 ||
|
| + e.osError.errorCode == -5 ||
|
| + e.osError.errorCode == 11004)) {
|
| + throw 'Could not resolve URL "${request.url.origin}".';
|
| + }
|
| + throw e;
|
| + }), HTTP_TIMEOUT, 'fetching URL "${request.url}"');
|
| + }
|
| +}
|
| +
|
| +/// The HTTP client to use for all HTTP requests.
|
| +final httpClient = new PubHttpClient();
|
| +
|
| +final curlClient = new PubHttpClient(new CurlClient());
|
| +
|
| +/// Handles a successful JSON-formatted response from pub.dartlang.org.
|
| +///
|
| +/// These responses are expected to be of the form `{"success": {"message":
|
| +/// "some message"}}`. If the format is correct, the message will be printed;
|
| +/// otherwise an error will be raised.
|
| +void handleJsonSuccess(http.Response response) {
|
| + var parsed = parseJsonResponse(response);
|
| + if (parsed['success'] is! Map ||
|
| + !parsed['success'].containsKey('message') ||
|
| + parsed['success']['message'] is! String) {
|
| + invalidServerResponse(response);
|
| + }
|
| + log.message(parsed['success']['message']);
|
| +}
|
| +
|
| +/// Handles an unsuccessful JSON-formatted response from pub.dartlang.org.
|
| +///
|
| +/// These responses are expected to be of the form `{"error": {"message": "some
|
| +/// message"}}`. If the format is correct, the message will be raised as an
|
| +/// error; otherwise an [invalidServerResponse] error will be raised.
|
| +void handleJsonError(http.Response response) {
|
| + var errorMap = parseJsonResponse(response);
|
| + if (errorMap['error'] is! Map ||
|
| + !errorMap['error'].containsKey('message') ||
|
| + errorMap['error']['message'] is! String) {
|
| + invalidServerResponse(response);
|
| + }
|
| + throw errorMap['error']['message'];
|
| +}
|
| +
|
| +/// Parses a response body, assuming it's JSON-formatted. Throws a user-friendly
|
| +/// error if the response body is invalid JSON, or if it's not a map.
|
| +Map parseJsonResponse(http.Response response) {
|
| + var value;
|
| + try {
|
| + value = JSON.parse(response.body);
|
| + } catch (e) {
|
| + // TODO(nweiz): narrow this catch clause once issue 6775 is fixed.
|
| + invalidServerResponse(response);
|
| + }
|
| + if (value is! Map) invalidServerResponse(response);
|
| + return value;
|
| +}
|
| +
|
| +/// Throws an error describing an invalid response from the server.
|
| +void invalidServerResponse(http.Response response) {
|
| + throw 'Invalid server response:\n${response.body}';
|
| +}
|
| +
|
| +/**
|
| + * Exception thrown when an HTTP operation fails.
|
| + */
|
| +class PubHttpException implements Exception {
|
| + final http.Response response;
|
| +
|
| + const PubHttpException(this.response);
|
| +
|
| + String toString() => 'HTTP error ${response.statusCode}: '
|
| + '${response.reasonPhrase}';
|
| +}
|
|
|