| // 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/', |
| ]; |
| |
| /// 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/', |
| ]; |
| } |
| |
| // Validator that checks all provided hosts are reachable and responsive |
| class HttpHostValidator extends DoctorValidator { |
| HttpHostValidator({ |
| required Platform platform, |
| required FeatureFlags featureFlags, |
| required HttpClient httpClient, |
| }) : _platform = platform, |
| _featureFlags = featureFlags, |
| _httpClient = httpClient, |
| super('HTTP Host Availability'); |
| |
| final Platform _platform; |
| final FeatureFlags _featureFlags; |
| final HttpClient _httpClient; |
| |
| @override |
| String get slowWarning => 'HTTP Host availability check is taking a long time...'; |
| |
| List<String> get _requiredHosts => <String>[ |
| if (_featureFlags.isMacOSEnabled) ...macOSRequiredHttpHosts, |
| if (_featureFlags.isAndroidEnabled) ...androidRequiredHttpHosts(_platform), |
| _platform.environment[kEnvPubHostedUrl] ?? kPubDevHttpHost, |
| _platform.environment[kEnvCloudUrl] ?? kgCloudHttpHost, |
| ]; |
| |
| /// Make a head request to the HTTP host for checking availability |
| Future<_HostValidationResult> _checkHostAvailability(String host) async { |
| late final int timeout; |
| try { |
| timeout = int.parse(_platform.environment[kDoctorHostTimeout] ?? '10'); |
| final HttpClientRequest req = await _httpClient.headUrl(Uri.parse(host)); |
| await req.close().timeout(Duration(seconds: timeout)); |
| // HTTP host is available if no exception happened |
| return _HostValidationResult.success(host); |
| } on TimeoutException { |
| return _HostValidationResult.fail(host, 'Failed to connect to host in $timeout second${timeout == 1 ? '': 's'}'); |
| } on SocketException catch (e) { |
| return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); |
| } on HttpException catch (e) { |
| return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); |
| } on HandshakeException catch (e) { |
| return _HostValidationResult.fail(host, 'An error occurred while checking the HTTP host: ${e.message}'); |
| } on OSError catch (e) { |
| 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'); |
| } |
| } |
| |
| @override |
| Future<ValidationResult> validate() async { |
| final List<ValidationMessage> messages = <ValidationMessage>[]; |
| final Iterable<Future<_HostValidationResult>> availabilityResultFutures = _requiredHosts.map(_checkHostAvailability); |
| final List<_HostValidationResult> availabilityResults = await Future.wait(availabilityResultFutures); |
| |
| if (availabilityResults.every((_HostValidationResult result) => result.available)) { |
| return ValidationResult( |
| ValidationType.installed, |
| messages..add(const ValidationMessage('All required HTTP hosts are available')), |
| ); |
| } |
| |
| availabilityResults.removeWhere((_HostValidationResult result) => result.available); |
| |
| for (final _HostValidationResult result in availabilityResults) { |
| messages.add(ValidationMessage.error('HTTP host "${result.host}" is not reachable. Reason: ${result.failResultInfo}')); |
| } |
| |
| return ValidationResult( |
| availabilityResults.length == _requiredHosts.length |
| ? ValidationType.notAvailable |
| : ValidationType.partial, |
| messages, |
| ); |
| } |
| } |
| |
| class _HostValidationResult { |
| _HostValidationResult.success(this.host) |
| : failResultInfo = '', |
| available = true; |
| |
| _HostValidationResult.fail(this.host, this.failResultInfo) : available = false; |
| |
| final String failResultInfo; |
| final String host; |
| final bool available; |
| } |