-
Notifications
You must be signed in to change notification settings - Fork 358
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
Migrate to the fetch
API
#595
Comments
How about this since one year past? |
This would be a good feature request for us. Happy to take a look and see how well it maps. |
Example of using |
We need cross all platform |
Looks like we need to fallback on websockets. Flutter web's http (BrowserClient) has no streaming capability. EventSources have their own issues (no http headers, for starters). That's a huge minus. Fetch, QUIC, web-transport, http3 are still years away. websockets it is. |
what about web-rtc? |
Bump this thread. There is no XHR in Chrome manifest v3 service workers so |
Is there any chance of this happening any time soon, or what is the status? Are there any other reasons to keep using XHR now that IE is dead? |
I've come across the issue that XHR missing information about redirects.
|
Any updates on this? |
I came across this issue myself, because I needed a streamed response on web. Since an implementation did not exist, I implemented it myself: Implementationimport 'dart:async' show Future, StreamController, unawaited;
import 'dart:html' show window;
import 'dart:js_util' show callConstructor, getProperty, globalThis, jsify;
import 'dart:typed_data' show Uint8List;
import 'package:http/http.dart' as http;
import 'package:js/js.dart' as js;
import 'package:js_bindings/bindings/dom.dart';
import 'package:js_bindings/bindings/fetch.dart';
import 'package:js_bindings/bindings/streams.dart';
/// https://developer.chrome.com/articles/fetch-streaming-requests/
class StreamedBrowserClient extends http.BaseClient {
/// The currently active requests.
///
/// These are aborted if the client is closed.
final _abortControllers = <AbortController>{};
/// Whether to send credentials such as cookies or authorization headers for
/// cross-site requests.
///
/// Defaults to `false`.
bool withCredentials = false;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
var bytes = request.finalize();
final abortController = AbortController();
_abortControllers.add(abortController);
try {
// For some reason, just calling `ReadableStream()` didn't work in debug mode, but worked fine, when compiled??
final ReadableStream nativeStream =
callConstructor(getProperty(globalThis, "ReadableStream"), [
jsify({
"start": js.allowInterop((ReadableStreamDefaultController controller) {
return _futureToPromise(Future(() async {
await for (var chunk in bytes) {
controller.enqueue(Uint8List.fromList(chunk));
}
controller.close();
}));
})
}),
]);
final Response response = await window.fetch(
request.url.toString(),
{
if (request.method != "GET" && request.method != "HEAD") "body": nativeStream,
"method": request.method,
"headers": request.headers,
"credentials":
(withCredentials ? RequestCredentials.include : RequestCredentials.sameOrigin).value,
"signal": abortController.signal,
"duplex": "half",
},
);
final ReadableStreamDefaultReader reader = response.body!.getReader();
final controller = StreamController<List<int>>();
unawaited(Future(() async {
while (true) {
ReadableStreamReadResult result = await reader.read();
if (result.done) {
controller.close();
_abortControllers.remove(abortController);
break;
}
controller.add(result.value);
}
}));
return http.StreamedResponse(controller.stream, response.status);
} catch (e) {
_abortControllers.remove(abortController);
throw http.ClientException(e.toString(), request.url);
}
}
/// Closes the client.
///
/// This terminates all active requests.
@override
void close() {
super.close();
for (final controller in _abortControllers) {
controller.abort();
}
}
}
Promise<T> _futureToPromise<T>(Future<T> future) {
return Promise(js.allowInterop((Function resolve, Function reject) {
future.then((result) => resolve(result), onError: reject);
}));
}
@js.JS()
abstract class Promise<T> {
external factory Promise(
void Function(void Function(T result) resolve, Function reject) executor);
external Promise then(void Function(T result) onFulfilled, [Function onRejected]);
}
I used the awesome js_bindings package for many js class bindings, but One might consider creating dedicated bindings for it, or even adding them to |
Is there any reason why we shouldn't just point people to fetch_client? |
I see no reason not to suggest an alternative implementation. I don't know if we should only do that - I expect that users would benefit from |
fetch
is widely supported enough to usefetch
API
|
Also note that fetch_client doesn't actually solve the problem "aside from changing your pubspec.yaml and a few lines of configuration, your application should not have to change" is just plain false. If you actually use StreamingRequest, you'll notice that fetch_client doesn't work properly at all. For example it sets keepAlive: true when you try to stream requests and fails unless you make more changes to your code specifically to use the package. Also, the package is published by "unverified uploader" on pub.dev, so Google telling everyone to use some random package by an unverified publisher instead of the built it one is an xz style vulnerability waiting to happen. |
@jezell, just to clarify, |
@Zekfad point is the fetch_client is not really a drop in replacement. Don't mean to say it's a bad package, just that it's not just a matter of changing some package ref. Code that works fine with built in http clients on iOS and Web doesn't work at all in fetch_client if you want streaming. For instance, if you try to make a StreamedRequest the same way you would on iOS or Web (even though the built in web implementation is a lie about streaming), it will blow up with an exception immediately due to setting the keepAlive to true. If you set it to "stream requests" in the constructor, it will stop blowing up, but it won't send your POST bodies at all anymore. So you have to go tweak your code to get it to work right. Should be just built in so all the same tests can be run against all the clients and no one has to worry about subtle differences in the implementations, or their apps randomly blowing up because the built in package doesn't do things properly for no good reason. |
@jezell request streaming is very experimental in web browsers (Chromium based only as of now). But that's a good catch, I'll repeat warning in class description and not only in property. In default node |
Using streaming in It would really be important to have this feature directly in |
We do intend for it to be low-change to code using We have a |
Is there more documentation on how to use this? The link to |
https://pub.dev/documentation/http/latest/http/runWithClient.html
Sorry for the broken link, looks like a syntax error. I'll fix it in #1184 |
@nietsmmar streaming is just plain broken in dart on web. you cannot stream. You cannot make large file uploads. I don't understand how the team thinks this is acceptable. |
@jezell Do you have a suggestion for a technical approach to make this work? According to the MDN compatibility chart, Likewise, the issue that you raised with Finally, the Dart Code of Conduct requires that you be courteous and respectful of people's work. Statements like "come on guys" and "I don't understand how the team thinks this is acceptable" are not acceptable ;-) |
There's a feature detection sample here to detect the presence of streaming requests: https://developer.chrome.com/docs/capabilities/web-apis/fetch-streaming-requests I think in general, feature detection is better than hard coding browser versions. It's not true to say FF doesn't support ReadableStream, it at least supports it for downloads. I think it would be preferable for that attempts to stream to throw an exception on browsers that don't support streaming saying that the browser doesn't support streaming, rather than fake support and blow up. The larger issue at play here is that large files can't be uploaded with Flutter web at the moment due to everything being buffered into memory for XmlHttpRequest. Streaming would intuitively be the way you would solve this problem as a developer, and it can solve this problem in Chrome, yet because dart is taking a lowest common denominator approach here you can't stream in the browsers that support it. There's already the non streaming API for browsers that don't support streaming. If streaming was supported, blame could be passed on to FF and Safari in the subset of cases they don't support, and developers could write code like: try {
await uploadStream(file);
} catch {
await uploadNonStream(file);
} This code could be made robust by the consuming app providing an appropriate message back to the end user about being in a browser that doesn't support large file uploads, instructing them to use another Browser and removing the blame from Flutter/Dart just not supporting large file uploads. Now raw streaming a byte at a time really isn't what's desired in a lot of cases. What people really want is the ability to push an entire File which fetch does support. Likely the reason FF doesn't support bidirectional streaming here is that most people are just need to upload a File object not a raw stream or download a file. However, generating a stream at runtime from some other source is pretty uncommon. Let's say I need to upload a 4GB file with the current dart api, what am I to do? Certainly this should be supported, and certainly it can be done with fetch, but at the moment it's impossible. StreamedRequest/StreamedResponse definitely should allow this problem to be supported when they are supported by the browser the app is running in, but what about when they aren't. I still should be able to upload the file. Fetch and upload file directly, but the current implementation is tied to XmlHttpRequest. These are the types fetch supports according to MDN: These are the types XmlHttpRequest supports according to MDN: Note that XmlHttpRequest lacks support for File, but Fetch does not. Note also that FormData is here for Fetch and due to the other problems the MultipartRequest impl is also going to blow up with 4GB files instead of using FormData on web when used with http. Slightly different problem because multipart mime requests are a little old school, but those are also kneecapped by buffering everything into memory in the current impl rather than using the browser native pathways. There are a few potential solutions to this problem without changing the API and without relying on StreamedRequest/StreamedResponse. The dart client interface itself supports sending objects just like the browser interface:
Yet this isn't leveraged at all. First off, MultipartRequest / MultiPart file on web should use the native objects and be supported for passthrough so the native objects get used. Second, if I use static interop to get a file object, I should be able to send that the body param here and have it work. browser_client should not assume everything it gets is a request with a byte stream that needs to be finalized and read into memory. Everyone on web will benefit from the dart web implementation moving to more modern APIs that don't result in very low limits on the amount of data you can send over the wire. At the moment it is very frustrating to deal with media in Flutter on web because of these gaps. |
@jezell while I understand your point, I think adding file to http package would be a bad decision, because http is designed to be cross platform, and there's no JS Your use case can be solved by actually using |
I didn't say add File to the http package. File is already in dart:web which is maintained by the dart team already (https://pub.dev/documentation/web/latest/web/web-library.html). I was just pointing out that the API itself can already accept an Object and should work if I pass it the native browser object. There is plenty of precedence for this in dart:ui_web already. It's just a simple fact that in the browser accomplishing some things requires using the native objects. It doesn't require creating a new API, it just requires browser_client.dart to respect the type of the object it is passed instead of blowing up. Dart should support uploading files that are larger than a 200MB. The fact that it doesn't remains a travesty. I'm just trying to present solutions to the problem. |
@Zekfad also note that the http package already has MultiPartFile, so the dart team already officially supports a file abstraction within the http package: https://pub.dev/documentation/http/latest/http/MultipartFile-class.html My suggestion there was that this should work, since FormData is actually supported in the browser. In this case it's again the browser_client implementation that is the problem, not the high level API being surfaced by dart http. While technically it "works" in a test, in practice multipart uploads are going to hit that upper bound of XmlHttpRequest much sooner, making it not very useful outside of a unit test. |
New here so apologies if this isn't helpful. Thinking pragmatically, the Web package has fetch (https://github.com/dart-lang/web/blob/main/web/lib/src/dom/fetch.dart), which i naively assume means we can use it within the browser_client? Would a simple approach work where the current BrowserClient internally checks if fetch is supported, if not then use the current implementation, else use the fetch from the Web package? Rubbish example code...
|
Any updates? I still really don't understand why this issue is still not solved after 3 years... especially knowing that:
I'm trying to develop a Web APP using Flutter/Dart to view this public stream [http://185.49.169.66:1024/control/faststream.jpg?stream=full&fps=16] and it is not possible to do that via http dart package? What is really happening? Is it so complicated? |
I wanted to understand this better from the technical perspective - so I decided that the best way to get informed is to try doing the migration myself. Here is the initial PR: #1401 Here is what I learned:
Let me explain more about the last bullet. First of all, there is no trivial documented way to detect if browser supports To make things worse, even a browser like Chrome (which theoretically supports request streaming) will still refuse to send a streaming request if it does not like the protocol: streaming requests require HTTP/2 or QUIC. This protocol based rejection is not even properly signaled in the exception you get: This means we can't simply switch We could consider changing behavior of Now there is a separate problem around file uploads which can be streamed if you pass Footnotes
|
Can we move forward with request streaming disabled? Feels like the rest of the value could be delivered and that be handled by a different ticket? |
That's my plan - unless we discover some blocker (I am not aware of any currently). |
Awesome 😎 Perfection is the enemy of good |
See #1401 ! |
Thanks @kevmoo |
I'm going to close this bug but note that it does not fix the streaming upload feature that @jezell needs. To request streaming uploads, please file a new bug (or use |
dart2js will stop supporting internet explorer soon - so all supported browser should support
fetch
which allows for streaming. I'm not sure if streaming works on firefox so that will need to be tested.https://api.dart.dev/stable/2.13.4/dart-html/Window/fetch.html
http/lib/src/browser_client.dart
Lines 27 to 29 in f93c76f
The text was updated successfully, but these errors were encountered: