http_host_validator.dart 6.25 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'base/io.dart';
import 'base/platform.dart';
import 'doctor_validator.dart';
import 'features.dart';

// Overridable environment variables
const String kEnvPubHostedUrl = 'PUB_HOSTED_URL';
const String kEnvCloudUrl = 'FLUTTER_STORAGE_BASE_URL';
const String kDoctorHostTimeout = 'FLUTTER_DOCTOR_HOST_TIMEOUT';

/// Common Flutter HTTP hosts.
const String kPubDevHttpHost = 'https://pub.dev/';
const String kgCloudHttpHost = 'https://cloud.google.com/';

/// MacOS specific required HTTP hosts.
const List<String> macOSRequiredHttpHosts = <String>[
  'https://cocoapods.org/',
];

26 27 28 29 30 31 32 33 34
/// Android specific required HTTP hosts.
List<String> androidRequiredHttpHosts(Platform platform) {
  return <String>[
    // If kEnvCloudUrl is set, it will be used as the maven host
    if (!platform.environment.containsKey(kEnvCloudUrl))
      'https://maven.google.com/',
  ];
}

35 36
// Validator that checks all provided hosts are reachable and responsive
class HttpHostValidator extends DoctorValidator {
37 38 39 40 41 42 43 44
  HttpHostValidator({
    required Platform platform,
    required FeatureFlags featureFlags,
    required HttpClient httpClient,
  }) : _platform = platform,
      _featureFlags = featureFlags,
      _httpClient = httpClient,
      super('HTTP Host Availability');
45 46 47 48 49 50

  final Platform _platform;
  final FeatureFlags _featureFlags;
  final HttpClient _httpClient;

  @override
51
  String get slowWarning => 'HTTP Host availability check is taking a long time...';
52 53 54

  List<String> get _requiredHosts => <String>[
    if (_featureFlags.isMacOSEnabled) ...macOSRequiredHttpHosts,
55
    if (_featureFlags.isAndroidEnabled) ...androidRequiredHttpHosts(_platform),
56 57 58 59
    _platform.environment[kEnvPubHostedUrl] ?? kPubDevHttpHost,
    _platform.environment[kEnvCloudUrl] ?? kgCloudHttpHost,
  ];

60
  /// Make a head request to the HTTP host for checking availability
61
  Future<_HostValidationResult> _checkHostAvailability(String host) async {
62
    late final int timeout;
63
    try {
64
      timeout = int.parse(_platform.environment[kDoctorHostTimeout] ?? '10');
65 66
      final HttpClientRequest req = await _httpClient.headUrl(Uri.parse(host));
      await req.close().timeout(Duration(seconds: timeout));
67
      // HTTP host is available if no exception happened
68 69
      return _HostValidationResult.success(host);
    } on TimeoutException {
70
      return _HostValidationResult.fail(host, 'Failed to connect to host in $timeout second${timeout == 1 ? '': 's'}');
71
    } on SocketException catch (e) {
72
      return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}');
73
    } on HttpException catch (e) {
74
      return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}');
75 76
    } on HandshakeException catch (e) {
      return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}');
77
    } on OSError catch (e) {
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
      return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}');
    } on FormatException catch (e) {
      if (e.message.contains('Invalid radix-10 number')) {
        return _HostValidationResult.fail(host, 'The value of $kDoctorHostTimeout(${_platform.environment[kDoctorHostTimeout]}) is not a valid duration in seconds');
      } else if (e.message.contains('Invalid empty scheme')){
        // Check if the invalid host is kEnvPubHostedUrl, else it must be kEnvCloudUrl
        final String? pubHostedUrl = _platform.environment[kEnvPubHostedUrl];
        if (pubHostedUrl != null && host == pubHostedUrl) {
          return _HostValidationResult.fail(host, 'The value of $kEnvPubHostedUrl(${_platform.environment[kEnvPubHostedUrl]}) could not be parsed as a valid url');
        }
        return _HostValidationResult.fail(host, 'The value of $kEnvCloudUrl(${_platform.environment[kEnvCloudUrl]}) could not be parsed as a valid url');
      }
      return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}');
    } on ArgumentError catch (e) {
      final String exceptionMessage = e.message.toString();
      if (exceptionMessage.contains('No host specified')) {
        // Check if the invalid host is kEnvPubHostedUrl, else it must be kEnvCloudUrl
        final String? pubHostedUrl = _platform.environment[kEnvPubHostedUrl];
        if (pubHostedUrl != null && host == pubHostedUrl) {
          return _HostValidationResult.fail(host, 'The value of $kEnvPubHostedUrl(${_platform.environment[kEnvPubHostedUrl]}) is not a valid host');
        }
        return _HostValidationResult.fail(host, 'The value of $kEnvCloudUrl(${_platform.environment[kEnvCloudUrl]}) is not a valid host');
      }
      return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: $exceptionMessage');
102 103 104 105 106 107
    }
  }

  @override
  Future<ValidationResult> validate() async {
    final List<ValidationMessage> messages = <ValidationMessage>[];
108 109
    final Iterable<Future<_HostValidationResult>> availabilityResultFutures = _requiredHosts.map(_checkHostAvailability);
    final List<_HostValidationResult> availabilityResults = await Future.wait(availabilityResultFutures);
110

111
    if (availabilityResults.every((_HostValidationResult result) => result.available)) {
112 113
      return ValidationResult(
          ValidationType.installed,
114 115
          messages..add(const ValidationMessage('All required HTTP hosts are available')),
      );
116 117
    }

118
    availabilityResults.removeWhere((_HostValidationResult result) => result.available);
119 120

    for (final _HostValidationResult result in availabilityResults) {
121
      messages.add(ValidationMessage.error('HTTP host "${result.host}" is not reachable. Reason: ${result.failResultInfo}'));
122 123 124
    }

    return ValidationResult(
125 126 127 128 129
      availabilityResults.length == _requiredHosts.length
        ? ValidationType.notAvailable
        : ValidationType.partial,
      messages,
    );
130 131 132 133 134
  }
}

class _HostValidationResult {
  _HostValidationResult.success(this.host)
135 136
    : failResultInfo = '',
      available = true;
137

138
  _HostValidationResult.fail(this.host, this.failResultInfo) : available = false;
139 140 141 142 143

  final String failResultInfo;
  final String host;
  final bool available;
}