Index: pkg/dev_compiler/web/web_command.dart |
diff --git a/pkg/dev_compiler/web/web_command.dart b/pkg/dev_compiler/web/web_command.dart |
index 1df3255ec61bf74ce53e0bccdf0b22b16f40c8b0..d5eef15cc48a182485e73890387a55d5daf54080 100644 |
--- a/pkg/dev_compiler/web/web_command.dart |
+++ b/pkg/dev_compiler/web/web_command.dart |
@@ -7,6 +7,7 @@ library dev_compiler.web.web_command; |
import 'dart:async'; |
import 'dart:convert'; |
import 'dart:html' show HttpRequest; |
+import 'dart:typed_data'; |
import 'package:analyzer/dart/element/element.dart' |
show |
@@ -37,6 +38,21 @@ typedef void MessageHandler(Object message); |
@JS() |
@anonymous |
+class JSIterator<V> {} |
+ |
+@JS('Map') |
+class JSMap<K, V> { |
+ external V get(K v); |
+ external set(K k, V v); |
+ external JSIterator<K> keys(); |
+ external JSIterator<V> values(); |
+} |
+ |
+@JS('Array.from') |
+external List<V> iteratorToList<V>(JSIterator<V> iterator); |
+ |
+@JS() |
+@anonymous |
class CompileResult { |
external factory CompileResult( |
{String code, List<String> errors, bool isValid}); |
@@ -45,6 +61,51 @@ class CompileResult { |
typedef CompileModule(String imports, String body, String libraryName, |
String existingLibrary, String fileName); |
+/// Version of HttpRequest.request that retries on 502 errors. |
+Future<HttpRequest> httpRequestRetry502Error(String url, |
+ {String responseType, String mimeType}) { |
+ var completer = new Completer<HttpRequest>(); |
+ |
+ var xhr = new HttpRequest(); |
+ xhr.open('GET', url, async: true); |
+ |
+ if (responseType != null) { |
+ xhr.responseType = responseType; |
+ } |
+ |
+ if (mimeType != null) { |
+ xhr.overrideMimeType(mimeType); |
+ } |
+ |
+ xhr.onLoad.listen((e) { |
+ var accepted = xhr.status >= 200 && xhr.status < 300; |
+ var fileUri = xhr.status == 0; // file:// URIs have status of 0. |
+ var notModified = xhr.status == 304; |
+ // Redirect status is specified up to 307, but others have been used in |
+ // practice. Notably Google Drive uses 308 Resume Incomplete for |
+ // resumable uploads, and it's also been used as a redirect. The |
+ // redirect case will be handled by the browser before it gets to us, |
+ // so if we see it we should pass it through to the user. |
+ var unknownRedirect = xhr.status > 307 && xhr.status < 400; |
+ |
+ if (accepted || fileUri || notModified || unknownRedirect) { |
+ completer.complete(xhr); |
+ } else { |
+ if (xhr.status == 502) { |
+ print("Retrying due to 502 error for $url"); |
+ return HttpRequest.request(url, |
+ responseType: responseType, mimeType: mimeType); |
+ } |
+ completer.completeError(e); |
+ } |
+ }); |
+ |
+ xhr.onError.listen(completer.completeError); |
+ |
+ xhr.send(); |
+ return completer.future; |
+} |
+ |
/// The command for invoking the modular compiler. |
class WebCompileCommand extends Command { |
get name => 'compile'; |
@@ -63,35 +124,43 @@ class WebCompileCommand extends Command { |
return requestSummaries; |
} |
- void requestSummaries(String summaryRoot, String sdkUrl, |
- List<String> summaryUrls, Function onCompileReady, Function onError) { |
- HttpRequest |
- .request(sdkUrl, |
- responseType: "arraybuffer", mimeType: "application/octet-stream") |
- .then((sdkRequest) { |
- var sdkBytes = sdkRequest.response.asUint8List(); |
- |
- // Map summary URLs to HttpRequests. |
- var summaryRequests = summaryUrls.map((summary) => new Future(() => |
- HttpRequest.request(summaryRoot + summary, |
- responseType: "arraybuffer", |
- mimeType: "application/octet-stream"))); |
- |
- Future.wait(summaryRequests).then((summaryResponses) { |
- // Map summary responses to summary bytes. |
- var summaryBytes = <List<int>>[]; |
- for (var response in summaryResponses) { |
- summaryBytes.add(response.response.asUint8List()); |
- } |
+ Future<Null> requestSummaries(String sdkUrl, JSMap<String, String> summaryMap, |
+ Function onCompileReady, Function onError) async { |
+ var sdkRequest; |
+ try { |
+ sdkRequest = await httpRequestRetry502Error(sdkUrl, |
+ responseType: "arraybuffer", mimeType: "application/octet-stream"); |
+ } catch (error) { |
+ onError('Dart sdk summaries failed to load: $error. url: $sdkUrl'); |
+ return null; |
+ } |
- onCompileReady(setUpCompile(sdkBytes, summaryBytes, summaryUrls)); |
- }).catchError((error) => onError('Summaries failed to load: $error')); |
- }).catchError((error) => |
- onError('Dart sdk summaries failed to load: $error. url: $sdkUrl')); |
+ var sdkBytes = (sdkRequest.response as ByteBuffer).asUint8List(); |
+ |
+ // Map summary URLs to HttpRequests. |
+ var summaryRequests = iteratorToList(summaryMap.values()) |
+ .map((String summaryUrl) => httpRequestRetry502Error(summaryUrl, |
+ responseType: "arraybuffer", mimeType: "application/octet-stream")) |
+ .toList(); |
+ var summaryResponses; |
+ try { |
+ summaryResponses = await Future.wait(summaryRequests); |
+ } catch (error) { |
+ print('Summaries failed to load: $error'); |
+ onError('Summaries failed to load: $error'); |
+ return null; |
+ } |
+ // Map summary responses to summary bytes. |
+ List<List<int>> summaryBytes = summaryResponses |
+ .map((response) => (response.response as ByteBuffer).asUint8List()) |
+ .toList(); |
+ print("Summaries loaded"); |
+ onCompileReady(setUpCompile( |
+ sdkBytes, summaryBytes, iteratorToList(summaryMap.keys()))); |
} |
List<Function> setUpCompile(List<int> sdkBytes, List<List<int>> summaryBytes, |
- List<String> summaryUrls) { |
+ List<String> moduleIds) { |
var dartSdkSummaryPath = '/dart-sdk/lib/_internal/web_sdk.sum'; |
var resourceProvider = new MemoryResourceProvider() |
@@ -106,9 +175,17 @@ class WebCompileCommand extends Command { |
resourceProvider: resourceProvider, recordDependencyInfo: true); |
for (var i = 0; i < summaryBytes.length; i++) { |
var bytes = summaryBytes[i]; |
- var url = '/' + summaryUrls[i]; |
- var summaryBundle = new PackageBundle.fromBuffer(bytes); |
- summaryDataStore.addBundle(url, summaryBundle); |
+ // Packages with no dart source files will have empty summaries which is fine. |
+ if (bytes.length == 0) continue; |
+ var url = '/${moduleIds[i]}.api.ds'; |
+ try { |
+ var summaryBundle = new PackageBundle.fromBuffer(bytes); |
+ summaryDataStore.addBundle(url, summaryBundle); |
+ } catch (e) { |
+ print( |
+ "XXX failed to load summary for $i, ${moduleIds[i]}, len = ${summaryBytes[i].length}"); |
+ continue; |
+ } |
} |
var summaryResolver = |
new InSummaryUriResolver(resourceProvider, summaryDataStore); |