Skip to content

Delegate implementation of dart:io's networking bits... #39104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dnfield opened this issue Oct 25, 2019 · 19 comments
Open

Delegate implementation of dart:io's networking bits... #39104

dnfield opened this issue Oct 25, 2019 · 19 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. customer-google3 library-io

Comments

@dnfield
Copy link
Contributor

dnfield commented Oct 25, 2019

...or perhaps provide a new library that does so.

Context: Flutter users are concerned about the added size of including a networking stack that they may not want to use (e.g. pre-existing mobile apps may be expected to use some other networking library flutter/flutter#40220) or that may not be taking full advantage of the OS capabilities (e.g. backgrounding download tasks flutter/flutter#32161, or respecting system proxy settings flutter/flutter#20376).

This also causes some headaches with Flutter for web, since the dart:io network implementation is not very compatible with AJAX/XMLHttpRequest.

A key part of this request is to have a way to buid the Dart VM without including the network bits of dart:io, e.g. all of the socket*.cc and BoringSSL, at least in product mode (where we wouldn't need the observatory anymore).

However, it seems like it would be ideal to be able to just fully delegate the nework calls to some other library in this mode (whether in product mode or not) so that existing code using e.g. HttpClient would not break but could effectively delegate to some other networking library specific to a platform.

It may also work just to have some new dart:net library or somesuch with the same API surface as the existing socket and HTTP implementation, but that delegates.

/cc @zanderso @xster @bkonyi @jonahwilliams who all probably have good thoughts on this.

@lrhn
Copy link
Member

lrhn commented Oct 25, 2019

Migrating a part of dart:io to, say, dart:net and exporting that from dart:io is definitely a possibility.

That won't solve the problem because you still can't use dart:io without having dart:net available.

We could, perhaps, split dart:io into a number of individual packages:

  • net (maybe even net_client/net_server)
  • files/filesystem
  • process
  • platform

and export all of them from dart:io, but encourage users to migrate to the individual libraries for "greater portability".

Before doing anything rash, we might want to consider for each of these whether there is a better API that we'd want to expose, and then have dart:io delegate to the better API instead of just exporting it. If the better API is one that can also be supported on the web, then that might be an advantage.
The dart:io library design is showing its age. It's not consistent with current design guides and I'm sure we can do it better today. If we keep dart:io for backwards compatibility, then existing code should keep working.

@xster
Copy link
Contributor

xster commented Oct 25, 2019

also cc @Hixie (I'm not super sure who's driving that effort).

Weren't there customers wanting to supply their own cronet implementation to back Dart networking as well? We should align since they likely have the same technical solution.

@devoncarew devoncarew added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io labels Oct 25, 2019
@devoncarew
Copy link
Member

cc @vsm @leafpetersen as there was some discussion about this at a recent planning meeting.

@zanderso
Copy link
Member

I'd be supportive of this change. As for better APIs, the Flutter tool mainly uses package:file and package:platform instead of directly using those parts of dart:io due to the ease of injecting different implementations. If those APIs (and similar ones for network, processes, etc.) were shifted to dart: then you'd get similar benefits at the embedder/platform level rather than just at the app level.

As an alternative approach, which I'm not necessarily endorsing, an embedder (like the engine) could set HttpOverrides.global and/or IOOverrides.global during Isolate setup before main() runs to jam in platform-specific implementations, possibly implemented in embedder-specific, private dart: libraries, like dart:_flutter_cronet, etc.. But I'd really prefer a solution whose benefits go beyond the Flutter engine.

@dnfield
Copy link
Contributor Author

dnfield commented Oct 25, 2019

@zanderso another issue with doing it in an embedder specific way is that apps still need to package in BoringSSL and the rest of the Dart network static on the C++ side, which last I checked was ~.3MB. That becomes a sticking point for some of our customers. I'm hoping that whatever solution is created would not only improve the Dart API surface but also improve options around binary size for people who want to bring their own.

@zanderso
Copy link
Member

@dnfield Yeah, there are really two parts to this issue:

  1. Using a different implementation of dart:io APIs (or whatever the new APIs are)
  2. Removing the unwanted implementations.

Since the engine build is currently taking the dart:io implementation off-the-shelf, there's no way to accomplish (2) without the Flutter engine doing something embedder specific. The embedder specific thing that the engine does could be supplying its own implementation of a new dart:net library, or it could be supplying an IOOverrides and not using the standalone VM's *_patch.dart files and not linking in the things that they refer to.

@jonahwilliams
Copy link
Contributor

If I'm understanding Option 2 in the context of a slimmed down Flutter, that would mean something like entirely removing HttpClient/HttpServer along with the associated native bits?

That has a lot of unfortunate side effects. In general, it would make it very difficult to prevent accidental breakages if a downstream dependency of the a SDK. Consider the follow case:

  1. Flutter does not use class A from dart:io.
  2. Customized Flutter/dart SDK is forked, removes class A to save code size.
  3. Flutter begins using class A
  4. Customized Flutter/Dart SDK is updated and breaks due to framework usage of A.

The above scenario is only preventable if Flutter essentially freezes the usage of dart core libraries. That would not be conducive to taking advantage of updates to dart core libraries.

@dnfield
Copy link
Contributor Author

dnfield commented Oct 25, 2019

The goal would be to have some other implementation in use, e.g. NSURLConnection on uOS

@jonahwilliams
Copy link
Contributor

That sounds like option 1 though, the interfaces stay but the implementation changes

@zanderso
Copy link
Member

If I'm understanding Option 2 in the context of a slimmed down Flutter, that would mean something like entirely removing HttpClient/HttpServer along with the associated native bits?

I was assuming it meant backing e.g. the dart:io HttpClient/HttpServer with a different implementation, possibly in a new dart:net library, where the new implementation in dart:net would have further capabilities like @dnfield mentions in the issue description. So the goal for (1) in #39104 (comment) is to add new implementations, and the goal for (2) is to remove the redundant native bits (like BoringSSL). For both, the Dart interfaces remain, and are unchanged.

@mraleph
Copy link
Member

mraleph commented Nov 7, 2019

/cc @a-siva

@dnfield
Copy link
Contributor Author

dnfield commented Feb 10, 2020

I'm going to work on a design doc for this.

@zoeyfan
Copy link

zoeyfan commented Feb 10, 2020

@zanderso
Copy link
Member

/cc @mehmetf who is looking at some changes to the HttpClient, and might be interested in this.

@mraleph
Copy link
Member

mraleph commented Feb 11, 2020

@zoeyfan that label is just about the size of compiled Dart code - so it does not fit this particular issue which is more about the size of the runtime system.

@mehmetf
Copy link
Contributor

mehmetf commented Aug 23, 2020

There has been numerous discussions around this internally. I have asked help from vsm to help organize this work.

Flutter's embedding of Dart on a given platform should match the core native characteristics of the platform. Doing this via plugins makes little sense to me. It should be directly provided by Flutter. In here, "core" means the categories that Lasse enumerated above (network, process, file).

Recent examples where we had to create inferior solutions:

  • We ended up implementing cronet support as a plugin because I was told QUIC support should be a package. I still don't believe this is the best integration model for something that low level (nasty bidirectional streams holding two copies of data). If we cannot have cronet in Dart (unlikely), we should handle it via platform specific implementation of dart:net APIs.
  • Flutter has no way of elevating process priority on Android when the app is doing something important. This is done on Android by creating a foreground service; a concept entirely invisible to Dart SDK. We, again, created a plugin for this but there might be a better solution such as making it a platform specific implementation of some dart:process API.
  • We want to use the state-of-the-art encryption solution by Google https://github.com/google/tink. Ideally this should be a platform specific implementation of dart:crypto. Again, we ended up creating a plugin which is not suitable for high-throughput data channels. We cannot use FFI directly because we don't want Android apps to suffer a size hit. We want to look into a FFI/JNI solution.

@a-siva
Copy link
Contributor

a-siva commented Oct 23, 2020

Before doing anything rash, we might want to consider for each of these whether there is a better API that we'd want to expose, and then have dart:io delegate to the better API instead of just exporting it. If the better API is one that can also be supported on the web, then that might be an advantage.
The dart:io library design is showing its age. It's not consistent with current design guides and I'm sure we can do it better today.

@lrhn could you point us to a document which talks about the current design guidelines and how the dart:io API can be made better.

@lrhn
Copy link
Member

lrhn commented Oct 26, 2020

I don't believe there is any such document, it's just API design choices that are either internally inconsistent, or that are not following patterns that have become de-facto standard for writing Dart code.

For example, the IO library has several classes which extend Stream, which is almost never done elsewhere ("favor composition over inheritance" is probably the most common general rule violated by dart:io).
An HttpRequest is a steam sink that you can write headers into, rather than having a headers sink.
Similarly, the IO sinks have every possible write themselves, both bytes and strings, instead of just being byte sinks and providing an easy ways to wrap them into string sinks with a specific encoding.

There is the write and writeSync distinction with file I/O, which is not something I have a good solution for, but it's not great API design.
The File class (and other file system entities) seems backwards - you create a File object first, before checking whether the path is a file at all. The File class doesn't represent a file, it really represents a path which may or may not point to a file (or a directory, or nothing). But the Directory class, which also represents a path, just with other intended operations, does not help you with actual path operations. You have to do File(path.join(dir.path, "filename")) instead of just, say, dir.file("filename").
So, the classes are inconsistent in whether they represent an abstract path or an on-disk entity, which makes them do both things half-heartedly. It's more of a grab-bag of functionality related to what you want the path to represent.

It's not bad, just not very consistent with almost all other Dart code.

@brianquinlan
Copy link
Contributor

cupertino_http is a new experimental Flutter plugin that provides access to Apple's Foundation URL Loading System - which supports HTTP/3, honors iOS VPN/Proxy settings, etc.

cupertino_http has the same interface as package:http Client so it is easy to use in a cross-platform way. For example:

late Client client;
if (Platform.isIOS) {
  final config = URLSessionConfiguration.ephemeralSessionConfiguration()
    # Do whatever configuration you want.
    ..allowsCellularAccess = false
    ..allowsConstrainedNetworkAccess = false
    ..allowsExpensiveNetworkAccess = false;
  client = CupertinoClient.fromSessionConfiguration(config);
} else {
  client = IOClient(); // Uses an HTTP client based on dart:io
}

final response = await client.get(Uri.https(
    'www.googleapis.com',
    '/books/v1/volumes',
    {'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));

I would really appreciate it if you can try cupertino_http out and see if it fixes some of the problems listed above for you for iOS.

Comments or bugs in the cupertino_http issue tracker would be appreciated!

@lrhn lrhn added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. and removed area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. labels Feb 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. customer-google3 library-io
Projects
None yet
Development

No branches or pull requests