1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import '../base/context.dart'; 8import '../globals.dart'; 9import 'common.dart'; 10import 'io.dart'; 11import 'platform.dart'; 12 13const int kNetworkProblemExitCode = 50; 14 15typedef HttpClientFactory = HttpClient Function(); 16 17/// Download a file from the given URL and return the bytes. 18Future<List<int>> fetchUrl(Uri url, {int maxAttempts}) async { 19 int attempts = 0; 20 int durationSeconds = 1; 21 while (true) { 22 attempts += 1; 23 final List<int> result = await _attempt(url); 24 if (result != null) 25 return result; 26 if (maxAttempts != null && attempts >= maxAttempts) { 27 printStatus('Download failed -- retry $attempts'); 28 return null; 29 } 30 printStatus('Download failed -- attempting retry $attempts in ' 31 '$durationSeconds second${ durationSeconds == 1 ? "" : "s"}...'); 32 await Future<void>.delayed(Duration(seconds: durationSeconds)); 33 if (durationSeconds < 64) 34 durationSeconds *= 2; 35 } 36} 37 38/// Check if the given URL points to a valid endpoint. 39Future<bool> doesRemoteFileExist(Uri url) async => 40 (await _attempt(url, onlyHeaders: true)) != null; 41 42Future<List<int>> _attempt(Uri url, { bool onlyHeaders = false }) async { 43 printTrace('Downloading: $url'); 44 HttpClient httpClient; 45 if (context.get<HttpClientFactory>() != null) { 46 httpClient = context.get<HttpClientFactory>()(); 47 } else { 48 httpClient = HttpClient(); 49 } 50 HttpClientRequest request; 51 try { 52 if (onlyHeaders) { 53 request = await httpClient.headUrl(url); 54 } else { 55 request = await httpClient.getUrl(url); 56 } 57 } on ArgumentError catch (error) { 58 final String overrideUrl = platform.environment['FLUTTER_STORAGE_BASE_URL']; 59 if (overrideUrl != null && url.toString().contains(overrideUrl)) { 60 printError(error.toString()); 61 throwToolExit( 62 'The value of FLUTTER_STORAGE_BASE_URL ($overrideUrl) could not be ' 63 'parsed as a valid url. Please see https://flutter.dev/community/china ' 64 'for an example of how to use it.\n' 65 'Full URL: $url', 66 exitCode: kNetworkProblemExitCode,); 67 } 68 printError(error.toString()); 69 rethrow; 70 } on HandshakeException catch (error) { 71 printTrace(error.toString()); 72 throwToolExit( 73 'Could not authenticate download server. You may be experiencing a man-in-the-middle attack,\n' 74 'your network may be compromised, or you may have malware installed on your computer.\n' 75 'URL: $url', 76 exitCode: kNetworkProblemExitCode, 77 ); 78 } on SocketException catch (error) { 79 printTrace('Download error: $error'); 80 return null; 81 } on HttpException catch (error) { 82 printTrace('Download error: $error'); 83 return null; 84 } 85 final HttpClientResponse response = await request.close(); 86 // If we're making a HEAD request, we're only checking to see if the URL is 87 // valid. 88 if (onlyHeaders) { 89 return (response.statusCode == 200) ? <int>[] : null; 90 } 91 if (response.statusCode != 200) { 92 if (response.statusCode > 0 && response.statusCode < 500) { 93 throwToolExit( 94 'Download failed.\n' 95 'URL: $url\n' 96 'Error: ${response.statusCode} ${response.reasonPhrase}', 97 exitCode: kNetworkProblemExitCode, 98 ); 99 } 100 // 5xx errors are server errors and we can try again 101 printTrace('Download error: ${response.statusCode} ${response.reasonPhrase}'); 102 return null; 103 } 104 printTrace('Received response from server, collecting bytes...'); 105 try { 106 final BytesBuilder responseBody = BytesBuilder(copy: false); 107 await response.forEach(responseBody.add); 108 return responseBody.takeBytes(); 109 } on IOException catch (error) { 110 printTrace('Download error: $error'); 111 return null; 112 } 113} 114