Version 2.10.0-27.0.dev

Merge commit 'efe7676067dc620882994a52fd4e45ec90efc50b' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04aa7bd..4aa79ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 ### Core libraries
 
+#### `dart:io`
+
+*   Adds `Abort` method to class `HttpClientRequest`, which allows users
+    to cancel outgoing HTTP requests and stop following IO operations.
+
 #### `dart:typed_data`
 
 *   Class `BytesBuilder` is moved from `dart:io` to `dart:typed_data`.
diff --git a/pkg/analyzer/lib/file_system/file_system.dart b/pkg/analyzer/lib/file_system/file_system.dart
index 6c8735b..c4dd4c2 100644
--- a/pkg/analyzer/lib/file_system/file_system.dart
+++ b/pkg/analyzer/lib/file_system/file_system.dart
@@ -131,6 +131,9 @@
   /// Return the full path to this resource.
   String get path;
 
+  /// Return the [ResourceProvider] that owns this resource.
+  ResourceProvider get provider;
+
   /// Return a short version of the name that can be displayed to the user to
   /// denote this resource.
   String get shortName;
diff --git a/pkg/analyzer/lib/file_system/memory_file_system.dart b/pkg/analyzer/lib/file_system/memory_file_system.dart
index 9baa63a..091943f 100644
--- a/pkg/analyzer/lib/file_system/memory_file_system.dart
+++ b/pkg/analyzer/lib/file_system/memory_file_system.dart
@@ -306,7 +306,7 @@
 
   @override
   int get modificationStamp {
-    int stamp = _provider._pathToTimestamp[path];
+    int stamp = provider._pathToTimestamp[path];
     if (stamp == null) {
       throw FileSystemException(path, "File does not exist");
     }
@@ -370,7 +370,7 @@
       : super(provider, path);
 
   @override
-  bool get exists => _provider._pathToResource[path] is _MemoryFile;
+  bool get exists => provider._pathToResource[path] is _MemoryFile;
 
   @override
   int get lengthSync {
@@ -379,7 +379,7 @@
 
   @override
   int get modificationStamp {
-    int stamp = _provider._pathToTimestamp[path];
+    int stamp = provider._pathToTimestamp[path];
     if (stamp == null) {
       throw FileSystemException(path, 'File "$path" does not exist.');
     }
@@ -396,13 +396,13 @@
 
   @override
   Source createSource([Uri uri]) {
-    uri ??= _provider.pathContext.toUri(path);
+    uri ??= provider.pathContext.toUri(path);
     return FileSource(this, uri);
   }
 
   @override
   void delete() {
-    _provider.deleteFile(path);
+    provider.deleteFile(path);
   }
 
   @override
@@ -412,7 +412,7 @@
 
   @override
   Uint8List readAsBytesSync() {
-    Uint8List content = _provider._pathToBytes[path];
+    Uint8List content = provider._pathToBytes[path];
     if (content == null) {
       throw FileSystemException(path, 'File "$path" does not exist.');
     }
@@ -421,7 +421,7 @@
 
   @override
   String readAsStringSync() {
-    Uint8List content = _provider._pathToBytes[path];
+    Uint8List content = provider._pathToBytes[path];
     if (content == null) {
       throw FileSystemException(path, 'File "$path" does not exist.');
     }
@@ -430,7 +430,7 @@
 
   @override
   File renameSync(String newPath) {
-    return _provider._renameFileSync(this, newPath);
+    return provider._renameFileSync(this, newPath);
   }
 
   @override
@@ -438,12 +438,12 @@
 
   @override
   void writeAsBytesSync(List<int> bytes) {
-    _provider._setFileContent(this, bytes);
+    provider._setFileContent(this, bytes);
   }
 
   @override
   void writeAsStringSync(String content) {
-    _provider._setFileContent(this, utf8.encode(content));
+    provider._setFileContent(this, utf8.encode(content));
   }
 }
 
@@ -453,19 +453,19 @@
       : super(provider, path);
 
   @override
-  bool get exists => _provider._pathToResource[path] is _MemoryFolder;
+  bool get exists => provider._pathToResource[path] is _MemoryFolder;
 
   @override
   String canonicalizePath(String relPath) {
-    relPath = _provider.pathContext.normalize(relPath);
-    String childPath = _provider.pathContext.join(path, relPath);
-    childPath = _provider.pathContext.normalize(childPath);
+    relPath = provider.pathContext.normalize(relPath);
+    String childPath = provider.pathContext.join(path, relPath);
+    childPath = provider.pathContext.normalize(childPath);
     return childPath;
   }
 
   @override
   bool contains(String path) {
-    return _provider.pathContext.isWithin(this.path, path);
+    return provider.pathContext.isWithin(this.path, path);
   }
 
   @override
@@ -480,39 +480,39 @@
 
   @override
   void create() {
-    _provider.newFolder(path);
+    provider.newFolder(path);
   }
 
   @override
   void delete() {
-    _provider.deleteFolder(path);
+    provider.deleteFolder(path);
   }
 
   @override
   Resource getChild(String relPath) {
     String childPath = canonicalizePath(relPath);
-    return _provider._pathToResource[childPath] ??
-        _MemoryFile(_provider, childPath);
+    return provider._pathToResource[childPath] ??
+        _MemoryFile(provider, childPath);
   }
 
   @override
   _MemoryFile getChildAssumingFile(String relPath) {
     String childPath = canonicalizePath(relPath);
-    _MemoryResource resource = _provider._pathToResource[childPath];
+    _MemoryResource resource = provider._pathToResource[childPath];
     if (resource is _MemoryFile) {
       return resource;
     }
-    return _MemoryFile(_provider, childPath);
+    return _MemoryFile(provider, childPath);
   }
 
   @override
   _MemoryFolder getChildAssumingFolder(String relPath) {
     String childPath = canonicalizePath(relPath);
-    _MemoryResource resource = _provider._pathToResource[childPath];
+    _MemoryResource resource = provider._pathToResource[childPath];
     if (resource is _MemoryFolder) {
       return resource;
     }
-    return _MemoryFolder(_provider, childPath);
+    return _MemoryFolder(provider, childPath);
   }
 
   @override
@@ -521,8 +521,8 @@
       throw FileSystemException(path, 'Folder does not exist.');
     }
     List<Resource> children = <Resource>[];
-    _provider._pathToResource.forEach((resourcePath, resource) {
-      if (_provider.pathContext.dirname(resourcePath) == path) {
+    provider._pathToResource.forEach((resourcePath, resource) {
+      if (provider.pathContext.dirname(resourcePath) == path) {
         children.add(resource);
       }
     });
@@ -541,28 +541,30 @@
   Folder resolveSymbolicLinksSync() => this;
 
   @override
-  Uri toUri() => _provider.pathContext.toUri(path + '/');
+  Uri toUri() => provider.pathContext.toUri(path + '/');
 }
 
 /// An in-memory implementation of [Resource].
 abstract class _MemoryResource implements Resource {
-  final MemoryResourceProvider _provider;
+  @override
+  final MemoryResourceProvider provider;
+
   @override
   final String path;
 
-  _MemoryResource(this._provider, this.path);
+  _MemoryResource(this.provider, this.path);
 
   Stream<WatchEvent> get changes {
     StreamController<WatchEvent> streamController =
         StreamController<WatchEvent>();
-    if (!_provider._pathToWatchers.containsKey(path)) {
-      _provider._pathToWatchers[path] = <StreamController<WatchEvent>>[];
+    if (!provider._pathToWatchers.containsKey(path)) {
+      provider._pathToWatchers[path] = <StreamController<WatchEvent>>[];
     }
-    _provider._pathToWatchers[path].add(streamController);
+    provider._pathToWatchers[path].add(streamController);
     streamController.done.then((_) {
-      _provider._pathToWatchers[path].remove(streamController);
-      if (_provider._pathToWatchers[path].isEmpty) {
-        _provider._pathToWatchers.remove(path);
+      provider._pathToWatchers[path].remove(streamController);
+      if (provider._pathToWatchers[path].isEmpty) {
+        provider._pathToWatchers.remove(path);
       }
     });
     return streamController.stream;
@@ -573,15 +575,15 @@
 
   @override
   Folder get parent {
-    String parentPath = _provider.pathContext.dirname(path);
+    String parentPath = provider.pathContext.dirname(path);
     if (parentPath == path) {
       return null;
     }
-    return _provider.getFolder(parentPath);
+    return provider.getFolder(parentPath);
   }
 
   @override
-  String get shortName => _provider.pathContext.basename(path);
+  String get shortName => provider.pathContext.basename(path);
 
   @override
   bool operator ==(Object other) {
@@ -595,5 +597,5 @@
   String toString() => path;
 
   @override
-  Uri toUri() => _provider.pathContext.toUri(path);
+  Uri toUri() => provider.pathContext.toUri(path);
 }
diff --git a/pkg/analyzer/lib/file_system/overlay_file_system.dart b/pkg/analyzer/lib/file_system/overlay_file_system.dart
index 2b12574..af97ba7 100644
--- a/pkg/analyzer/lib/file_system/overlay_file_system.dart
+++ b/pkg/analyzer/lib/file_system/overlay_file_system.dart
@@ -144,11 +144,11 @@
   Stream<WatchEvent> get changes => _file.changes;
 
   @override
-  bool get exists => _provider.hasOverlay(path) || _resource.exists;
+  bool get exists => provider.hasOverlay(path) || _resource.exists;
 
   @override
   int get lengthSync {
-    String content = _provider._getOverlayContent(path);
+    String content = provider._getOverlayContent(path);
     if (content != null) {
       return content.length;
     }
@@ -157,7 +157,7 @@
 
   @override
   int get modificationStamp {
-    int stamp = _provider._getOverlayModificationStamp(path);
+    int stamp = provider._getOverlayModificationStamp(path);
     if (stamp != null) {
       return stamp;
     }
@@ -170,25 +170,25 @@
 
   @override
   File copyTo(Folder parentFolder) {
-    String newPath = _provider.pathContext.join(parentFolder.path, shortName);
-    _provider._copyOverlay(path, newPath);
+    String newPath = provider.pathContext.join(parentFolder.path, shortName);
+    provider._copyOverlay(path, newPath);
     if (_file.exists) {
       if (parentFolder is _OverlayFolder) {
-        return _OverlayFile(_provider, _file.copyTo(parentFolder._folder));
+        return _OverlayFile(provider, _file.copyTo(parentFolder._folder));
       }
-      return _OverlayFile(_provider, _file.copyTo(parentFolder));
+      return _OverlayFile(provider, _file.copyTo(parentFolder));
     } else {
-      return _OverlayFile(_provider, _provider.baseProvider.getFile(newPath));
+      return _OverlayFile(provider, provider.baseProvider.getFile(newPath));
     }
   }
 
   @override
   Source createSource([Uri uri]) =>
-      FileSource(this, uri ?? _provider.pathContext.toUri(path));
+      FileSource(this, uri ?? provider.pathContext.toUri(path));
 
   @override
   void delete() {
-    bool hadOverlay = _provider.removeOverlay(path);
+    bool hadOverlay = provider.removeOverlay(path);
     if (_resource.exists) {
       _resource.delete();
     } else if (!hadOverlay) {
@@ -198,7 +198,7 @@
 
   @override
   Uint8List readAsBytesSync() {
-    String content = _provider._getOverlayContent(path);
+    String content = provider._getOverlayContent(path);
     if (content != null) {
       return utf8.encode(content) as Uint8List;
     }
@@ -207,7 +207,7 @@
 
   @override
   String readAsStringSync() {
-    String content = _provider._getOverlayContent(path);
+    String content = provider._getOverlayContent(path);
     if (content != null) {
       return content;
     }
@@ -217,13 +217,13 @@
   @override
   File renameSync(String newPath) {
     File newFile = _file.renameSync(newPath);
-    if (_provider.hasOverlay(path)) {
-      _provider.setOverlay(newPath,
-          content: _provider._getOverlayContent(path),
-          modificationStamp: _provider._getOverlayModificationStamp(path));
-      _provider.removeOverlay(path);
+    if (provider.hasOverlay(path)) {
+      provider.setOverlay(newPath,
+          content: provider._getOverlayContent(path),
+          modificationStamp: provider._getOverlayModificationStamp(path));
+      provider.removeOverlay(path);
     }
-    return _OverlayFile(_provider, newFile);
+    return _OverlayFile(provider, newFile);
   }
 
   @override
@@ -233,7 +233,7 @@
 
   @override
   void writeAsStringSync(String content) {
-    if (_provider.hasOverlay(path)) {
+    if (provider.hasOverlay(path)) {
       throw FileSystemException(path, 'Cannot write a file with an overlay');
     }
     _file.writeAsStringSync(content);
@@ -252,7 +252,7 @@
   Stream<WatchEvent> get changes => _folder.changes;
 
   @override
-  bool get exists => _provider._hasOverlayIn(path) || _resource.exists;
+  bool get exists => provider._hasOverlayIn(path) || _resource.exists;
 
   /// Return the folder from the base resource provider that corresponds to this
   /// folder.
@@ -260,7 +260,7 @@
 
   @override
   String canonicalizePath(String relPath) {
-    pathos.Context context = _provider.pathContext;
+    pathos.Context context = provider.pathContext;
     relPath = context.normalize(relPath);
     String childPath = context.join(path, relPath);
     childPath = context.normalize(childPath);
@@ -287,37 +287,37 @@
 
   @override
   Resource getChild(String relPath) =>
-      _OverlayResource._from(_provider, _folder.getChild(relPath));
+      _OverlayResource._from(provider, _folder.getChild(relPath));
 
   @override
   File getChildAssumingFile(String relPath) =>
-      _OverlayFile(_provider, _folder.getChildAssumingFile(relPath));
+      _OverlayFile(provider, _folder.getChildAssumingFile(relPath));
 
   @override
   Folder getChildAssumingFolder(String relPath) =>
-      _OverlayFolder(_provider, _folder.getChildAssumingFolder(relPath));
+      _OverlayFolder(provider, _folder.getChildAssumingFolder(relPath));
 
   @override
   List<Resource> getChildren() {
     Map<String, Resource> children = {};
     try {
       for (final child in _folder.getChildren()) {
-        children[child.path] = _OverlayResource._from(_provider, child);
+        children[child.path] = _OverlayResource._from(provider, child);
       }
     } on FileSystemException {
       // We don't want to throw if we're a folder that only exists in the
       // overlay and not on disk.
     }
 
-    for (String overlayPath in _provider._overlaysInFolder(path)) {
-      pathos.Context context = _provider.pathContext;
+    for (String overlayPath in provider._overlaysInFolder(path)) {
+      pathos.Context context = provider.pathContext;
       if (context.dirname(overlayPath) == path) {
-        children.putIfAbsent(overlayPath, () => _provider.getFile(overlayPath));
+        children.putIfAbsent(overlayPath, () => provider.getFile(overlayPath));
       } else {
         String relativePath = context.relative(overlayPath, from: path);
         String folderName = context.split(relativePath)[0];
         String folderPath = context.join(path, folderName);
-        children.putIfAbsent(folderPath, () => _provider.getFolder(folderPath));
+        children.putIfAbsent(folderPath, () => provider.getFolder(folderPath));
       }
     }
     return children.values.toList();
@@ -326,17 +326,17 @@
 
 /// The base class for resources from an [OverlayResourceProvider].
 abstract class _OverlayResource implements Resource {
-  /// The resource provider associated with this resource.
-  final OverlayResourceProvider _provider;
+  @override
+  final OverlayResourceProvider provider;
 
   /// The resource from the provider's base provider that corresponds to this
   /// resource.
   final Resource _resource;
 
   /// Initialize a newly created instance of a resource to have the given
-  /// [_provider] and to represent the [_resource] from the provider's base
+  /// [provider] and to represent the [_resource] from the provider's base
   /// resource provider.
-  _OverlayResource(this._provider, this._resource);
+  _OverlayResource(this.provider, this._resource);
 
   /// Return an instance of the subclass of this class corresponding to the
   /// given [resource] that is associated with the given [provider].
@@ -359,7 +359,7 @@
     if (parent == null) {
       return null;
     }
-    return _OverlayFolder(_provider, parent);
+    return _OverlayFolder(provider, parent);
   }
 
   @override
@@ -388,7 +388,7 @@
 
   @override
   Resource resolveSymbolicLinksSync() =>
-      _OverlayResource._from(_provider, _resource.resolveSymbolicLinksSync());
+      _OverlayResource._from(provider, _resource.resolveSymbolicLinksSync());
 
   @override
   Uri toUri() => _resource.toUri();
diff --git a/pkg/analyzer/lib/file_system/physical_file_system.dart b/pkg/analyzer/lib/file_system/physical_file_system.dart
index 2e5f628..f335f05 100644
--- a/pkg/analyzer/lib/file_system/physical_file_system.dart
+++ b/pkg/analyzer/lib/file_system/physical_file_system.dart
@@ -375,6 +375,9 @@
   Context get pathContext => io.Platform.isWindows ? windows : posix;
 
   @override
+  ResourceProvider get provider => PhysicalResourceProvider.INSTANCE;
+
+  @override
   String get shortName => pathContext.basename(path);
 
   @override
diff --git a/sdk/lib/_http/http.dart b/sdk/lib/_http/http.dart
index 6db8524..7fc8b40 100644
--- a/sdk/lib/_http/http.dart
+++ b/sdk/lib/_http/http.dart
@@ -2015,6 +2015,34 @@
   ///
   /// Returns `null` if the socket is not available.
   HttpConnectionInfo? get connectionInfo;
+
+  /// Aborts the client connection.
+  ///
+  /// If the connection has not yet completed, the request is aborted and the
+  /// [done] future (also returned by [close]) is completed with the provided
+  /// [exception] and [stackTrace].
+  /// If [exception] is omitted, it defaults to an [HttpException], and if
+  /// [stackTrace] is omitted, it defaults to [StackTrace.empty].
+  ///
+  /// If the [done] future has already completed, aborting has no effect.
+  ///
+  /// Using the [IOSink] methods (e.g., [write] and [add]) has no effect after
+  /// the request has been aborted
+  ///
+  /// ```dart
+  /// HttpClientRequst request = ...
+  /// request.write();
+  /// Timer(Duration(seconds: 1), () {
+  ///   request.abort();
+  /// });
+  /// request.close().then((response) {
+  ///   // If response comes back before abort, this callback will be called.
+  /// }, onError: (e) {
+  ///   // If abort() called before response is available, onError will fire.
+  /// });
+  /// ```
+  @Since("2.10")
+  void abort([Object? exception, StackTrace? stackTrace]);
 }
 
 /**
diff --git a/sdk/lib/_http/http_impl.dart b/sdk/lib/_http/http_impl.dart
index bc43046..97087ed 100644
--- a/sdk/lib/_http/http_impl.dart
+++ b/sdk/lib/_http/http_impl.dart
@@ -1078,6 +1078,8 @@
 
   List<RedirectInfo> _responseRedirects = [];
 
+  bool _aborted = false;
+
   _HttpClientRequest(_HttpOutgoing outgoing, Uri uri, this.method, this._proxy,
       this._httpClient, this._httpClientConnection, this._timeline)
       : uri = uri,
@@ -1141,7 +1143,10 @@
           .then((list) => list[0]);
 
   Future<HttpClientResponse> close() {
-    super.close();
+    if (!_aborted) {
+      // It will send out the request.
+      super.close();
+    }
     return done;
   }
 
@@ -1161,6 +1166,9 @@
       _httpClientConnection.connectionInfo;
 
   void _onIncoming(_HttpIncoming incoming) {
+    if (_aborted) {
+      return;
+    }
     var response = new _HttpClientResponse(incoming, this, _httpClient);
     Future<HttpClientResponse> future;
     if (followRedirects && response.isRedirect) {
@@ -1183,12 +1191,21 @@
     } else {
       future = new Future<HttpClientResponse>.value(response);
     }
-    future.then((v) => _responseCompleter.complete(v),
-        onError: _responseCompleter.completeError);
+    future.then((v) {
+      if (!_responseCompleter.isCompleted) {
+        _responseCompleter.complete(v);
+      }
+    }, onError: (e, s) {
+      if (!_responseCompleter.isCompleted) {
+        _responseCompleter.completeError(e, s);
+      }
+    });
   }
 
   void _onError(error, StackTrace stackTrace) {
-    _responseCompleter.completeError(error, stackTrace);
+    if (!_responseCompleter.isCompleted) {
+      _responseCompleter.completeError(error, stackTrace);
+    }
   }
 
   // Generate the request URI based on the method and proxy.
@@ -1221,7 +1238,21 @@
     }
   }
 
+  void add(List<int> data) {
+    if (data.length == 0 || _aborted) return;
+    super.add(data);
+  }
+
+  void write(Object? obj) {
+    if (_aborted) return;
+    super.write(obj);
+  }
+
   void _writeHeader() {
+    if (_aborted) {
+      _outgoing.setHeader(Uint8List(0), 0);
+      return;
+    }
     BytesBuilder buffer = new _CopyingBytesBuilder(_OUTGOING_BUFFER_SIZE);
 
     // Write the request method.
@@ -1254,6 +1285,15 @@
     Uint8List headerBytes = buffer.takeBytes();
     _outgoing.setHeader(headerBytes, headerBytes.length);
   }
+
+  void abort([Object? exception, StackTrace? stackTrace]) {
+    _aborted = true;
+    if (!_responseCompleter.isCompleted) {
+      exception ??= HttpException("Request has been aborted");
+      _responseCompleter.completeError(exception, stackTrace);
+      _httpClientConnection.destroy();
+    }
+  }
 }
 
 // Used by _HttpOutgoing as a target of a chunked converter for gzip
diff --git a/tests/standalone/io/http_client_connect_test.dart b/tests/standalone/io/http_client_connect_test.dart
index b82812b..4eb0fff 100644
--- a/tests/standalone/io/http_client_connect_test.dart
+++ b/tests/standalone/io/http_client_connect_test.dart
@@ -308,7 +308,127 @@
   }
 }
 
-void main() {
+Future<void> testHttpAbort() async {
+  // Test that abort() is called after request is sent.
+  asyncStart();
+  final completer = Completer<void>();
+  final server = await HttpServer.bind("127.0.0.1", 0);
+  server.listen((request) {
+    completer.complete();
+    request.response.close();
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  request.headers.add(HttpHeaders.contentLengthHeader, "8");
+  request.write('somedata');
+  completer.future.then((_) {
+    request.abort();
+    asyncStart();
+    Future.delayed(Duration(milliseconds: 500), () {
+      server.close();
+      asyncEnd();
+    });
+  });
+  request.close().then((response) {
+    Expect.fail('abort() prevents a response being returned');
+  }, onError: (e) {
+    Expect.type<HttpException>(e);
+    Expect.isTrue(e.toString().contains('abort'));
+    asyncEnd();
+  });
+}
+
+Future<void> testHttpAbortBeforeWrite() async {
+  // Test that abort() is called before write(). No message should be sent from
+  // HttpClientRequest.
+  asyncStart();
+  final completer = Completer<Socket>();
+  final server = await ServerSocket.bind("127.0.0.1", 0);
+  server.listen((s) async {
+    s.listen((data) {
+      Expect.fail('No message should be received');
+    });
+    await Future.delayed(Duration(milliseconds: 500));
+    completer.complete(s);
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  request.headers.add(HttpHeaders.contentLengthHeader, "8");
+  // This HttpException will go to onError callback.
+  request.abort(HttpException('Error'));
+  asyncStart();
+  request.write('somedata');
+  completer.future.then((socket) {
+    socket.destroy();
+    server.close();
+    asyncEnd();
+  });
+  request.close().then((response) {
+    Expect.fail('abort() prevents a response being returned');
+  }, onError: (e) {
+    Expect.type<HttpException>(e);
+    asyncEnd();
+  });
+}
+
+Future<void> testHttpAbortBeforeClose() async {
+  // Test that abort() is called after write(). Some messages added prior to
+  // abort() are sent.
+  final completer = new Completer<void>();
+  asyncStart();
+  final server = await ServerSocket.bind("127.0.0.1", 0);
+  server.listen((s) {
+    StringBuffer buffer = StringBuffer();
+    s.listen((data) {
+      buffer.write(utf8.decode(data));
+      if (buffer.toString().contains("content-length: 8")) {
+        completer.complete();
+        s.destroy();
+        server.close();
+      }
+    });
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  // Add an additional header field for server to verify.
+  request.headers.add(HttpHeaders.contentLengthHeader, "8");
+  request.write('somedata');
+  await completer.future;
+  final string = 'abort message';
+  request.abort(string);
+  request.close().then((response) {
+    Expect.fail('abort() prevents a response being returned');
+  }, onError: (e) {
+    Expect.type<String>(e);
+    Expect.equals(string, e);
+    asyncEnd();
+  });
+}
+
+Future<void> testHttpAbortAfterClose() async {
+  // Test that abort() is called after response is received. It should not
+  // affect HttpClientResponse.
+  asyncStart();
+  final value = 'someRandomData';
+  final server = await HttpServer.bind("127.0.0.1", 0);
+  server.listen((request) {
+    request.response.write(value);
+    request.response.close();
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  request.close().then((response) {
+    request.abort();
+    response.listen((data) {
+      Expect.equals(utf8.decode(data), value);
+    }, onDone: () {
+      asyncEnd();
+      server.close();
+    });
+  });
+}
+
+void main() async {
   testGetEmptyRequest();
   testGetDataRequest();
   testGetInvalidHost();
@@ -324,4 +444,8 @@
   testMaxConnectionsPerHost(5, 10);
   testMaxConnectionsPerHost(10, 50);
   testMaxConnectionsWithFailure();
+  await testHttpAbort();
+  await testHttpAbortBeforeWrite();
+  await testHttpAbortBeforeClose();
+  await testHttpAbortAfterClose();
 }
diff --git a/tests/standalone/io/internet_address_test.dart b/tests/standalone/io/internet_address_test.dart
index 6671b92..306affc 100644
--- a/tests/standalone/io/internet_address_test.dart
+++ b/tests/standalone/io/internet_address_test.dart
@@ -210,9 +210,13 @@
 }
 
 void testInvalidScopedId() {
-  Expect.throws<ArgumentError>(() => InternetAddress('::1%invalid'), (error) {
-    return error.toString().contains('scope ID');
-  });
+  // macOS will not throw an error.
+  try {
+    InternetAddress('::1%invalid');
+  } catch (e) {
+    Expect.type<ArgumentError>(e);
+    Expect.isTrue(e.toString().contains('scope ID'));
+  }
 }
 
 void main() {
diff --git a/tests/standalone_2/io/http_client_connect_test.dart b/tests/standalone_2/io/http_client_connect_test.dart
index 6de515b..46380f1 100644
--- a/tests/standalone_2/io/http_client_connect_test.dart
+++ b/tests/standalone_2/io/http_client_connect_test.dart
@@ -306,7 +306,127 @@
   }
 }
 
-void main() {
+Future<void> testHttpAbort() async {
+  // Test that abort() is called after request is sent.
+  asyncStart();
+  final completer = Completer<void>();
+  final server = await HttpServer.bind("127.0.0.1", 0);
+  server.listen((request) {
+    completer.complete();
+    request.response.close();
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  request.headers.add(HttpHeaders.contentLengthHeader, "8");
+  request.write('somedata');
+  completer.future.then((_) {
+    request.abort();
+    asyncStart();
+    Future.delayed(Duration(milliseconds: 500), () {
+      server.close();
+      asyncEnd();
+    });
+  });
+  request.close().then((response) {
+    Expect.fail('abort() prevents a response being returned');
+  }, onError: (e) {
+    Expect.type<HttpException>(e);
+    Expect.isTrue(e.toString().contains('abort'));
+    asyncEnd();
+  });
+}
+
+Future<void> testHttpAbortBeforeWrite() async {
+  // Test that abort() is called before write(). No message should be sent from
+  // HttpClientRequest.
+  asyncStart();
+  final completer = Completer<Socket>();
+  final server = await ServerSocket.bind("127.0.0.1", 0);
+  server.listen((s) async {
+    s.listen((data) {
+      Expect.fail('No message should be received');
+    });
+    await Future.delayed(Duration(milliseconds: 500));
+    completer.complete(s);
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  request.headers.add(HttpHeaders.contentLengthHeader, "8");
+  // This HttpException will go to onError callback.
+  request.abort(HttpException('Error'));
+  asyncStart();
+  request.write('somedata');
+  completer.future.then((socket) {
+    socket.destroy();
+    server.close();
+    asyncEnd();
+  });
+  request.close().then((response) {
+    Expect.fail('abort() prevents a response being returned');
+  }, onError: (e) {
+    Expect.type<HttpException>(e);
+    asyncEnd();
+  });
+}
+
+Future<void> testHttpAbortBeforeClose() async {
+  // Test that abort() is called after write(). Some messages added prior to
+  // abort() are sent.
+  final completer = new Completer<void>();
+  asyncStart();
+  final server = await ServerSocket.bind("127.0.0.1", 0);
+  server.listen((s) {
+    StringBuffer buffer = StringBuffer();
+    s.listen((data) {
+      buffer.write(utf8.decode(data));
+      if (buffer.toString().contains("content-length: 8")) {
+        completer.complete();
+        s.destroy();
+        server.close();
+      }
+    });
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  // Add an additional header field for server to verify.
+  request.headers.add(HttpHeaders.contentLengthHeader, "8");
+  request.write('somedata');
+  await completer.future;
+  final string = 'abort message';
+  request.abort(string);
+  request.close().then((response) {
+    Expect.fail('abort() prevents a response being returned');
+  }, onError: (e) {
+    Expect.type<String>(e);
+    Expect.equals(string, e);
+    asyncEnd();
+  });
+}
+
+Future<void> testHttpAbortAfterClose() async {
+  // Test that abort() is called after response is received. It should not
+  // affect HttpClientResponse.
+  asyncStart();
+  final value = 'someRandomData';
+  final server = await HttpServer.bind("127.0.0.1", 0);
+  server.listen((request) {
+    request.response.write(value);
+    request.response.close();
+  });
+
+  final request = await HttpClient().get("127.0.0.1", server.port, "/");
+  request.close().then((response) {
+    request.abort();
+    response.listen((data) {
+      Expect.equals(utf8.decode(data), value);
+    }, onDone: () {
+      asyncEnd();
+      server.close();
+    });
+  });
+}
+
+void main() async {
   testGetEmptyRequest();
   testGetDataRequest();
   testGetInvalidHost();
@@ -322,4 +442,8 @@
   testMaxConnectionsPerHost(5, 10);
   testMaxConnectionsPerHost(10, 50);
   testMaxConnectionsWithFailure();
+  await testHttpAbort();
+  await testHttpAbortBeforeWrite();
+  await testHttpAbortBeforeClose();
+  await testHttpAbortAfterClose();
 }
diff --git a/tests/standalone_2/io/internet_address_test.dart b/tests/standalone_2/io/internet_address_test.dart
index 47fc491..e7af9cd 100644
--- a/tests/standalone_2/io/internet_address_test.dart
+++ b/tests/standalone_2/io/internet_address_test.dart
@@ -209,9 +209,13 @@
 }
 
 void testInvalidScopedId() {
-  Expect.throws<ArgumentError>(() => InternetAddress('::1%invalid'), (error) {
-    return error.toString().contains('scope ID');
-  });
+  // macOS will not throw an error.
+  try {
+    InternetAddress('::1%invalid');
+  } catch (e) {
+    Expect.type<ArgumentError>(e);
+    Expect.isTrue(e.toString().contains('scope ID'));
+  }
 }
 
 void main() {
diff --git a/tools/VERSION b/tools/VERSION
index 03dfa4a..eafca47 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 26
+PRERELEASE 27
 PRERELEASE_PATCH 0
\ No newline at end of file