Open natebosch opened 4 years ago
The easiest option might be to add a method on StreamedResponse. The proposed dart:io's abort() is a method in Class HttpClientRequest. The method on StreamedResponse should not be able to access abort().(Technically you can, but it won't make a difference as the response has already come back)
I think the only use case for abort() is timing out the request if it takes
too long. We can adapt send
to accept an optional parameter duration
and possibly a callback to be fired when timing out.
On Thu, May 28, 2020 at 12:52 PM Nate Bosch notifications@github.com wrote:
On the web HttpRequest supports abort: https://api.dart.dev/stable/2.8.1/dart-html/HttpRequest/abort.html
We have a proposal for adding abort on the dart:io HttpClientRequest: https://dart-review.googlesource.com/c/sdk/+/147339
We need to come up with a design for supporting abort through the interfaces in this package and validate that we can build it on both dart:html and dart:io implementations.
The easiest option might be to add a method on StreamedResponse. There is some precedent for that with IOStreamedResponse.detachSocket. https://github.com/dart-lang/http/blob/9063ba379b7e7386cced830feb27cc7a5fd3ec7e/lib/src/io_streamed_response.dart#L36 (we need to make sure the differences between these are clearly documented.
I do wonder if we need to consider some sort of support for the more convenient methods than send though...
cc @kevmoo https://github.com/kevmoo @jakemac53 https://github.com/jakemac53 @grouma https://github.com/grouma @zichangg https://github.com/zichangg for thoughts
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dart-lang/http/issues/424, or unsubscribe https://github.com/notifications/unsubscribe-auth/AK3IVS7MXK2PWZYCPT4NA7TRT26GZANCNFSM4NNMB7PA .
-- Best Regards, Zichang
Here are a couple other options - I'm not advocating for these, just trying to brainstorm. Both of these are breaking changes for anyone with implements http.Client
.
Allow passing a Future back to the client to cancel it. We'd have some state that lets us abort if you give us back the exact same instance.
var response = client.get(url);
// something happens
client.abort(response);
Add a named argument to all the methods:
var abortCompleter = Completer<void>();
var response = client.get(url, abortOn: abortCompleter.future);
// something happens
abortCompleter.complete();
The latter could pair well with a timeout
argument. Passing timeout: Duration(seconds: 5)
would have the same effect as passing abortOn: Future.delayed(Duration(seconds: 5))
I like the latter assuming a future that completes after the request has completed doesn't cause an issue.
Hey, in my app I want to give the user an option to abort while uploading a file. I saw that dart:io is going to support it soon. However I use http package in my app so I looked here and saw this thread. Do you think you'll support it too, soon? Thanks
I think having an abort
method on the response itself is really the ideal design here, for a few reasons:
This does get a bit trickier to implement - you are left with a few not so great options:
Future
with some custom type, which beyond being considered an anti-pattern also raises some difficult questions around how you handle the cancel
case. I think for this you could just complete with a special type of error
, and not try to do what CancelableOperation
does (with a special onCancel
callback that complicates error handling).CancelableOperation
).Do you think you'll support it too, soon?
The trickiest part of this is rolling it out, I think no matter what design we use it's going to be a breaking change for anyone with a class which implements Client
.
- It doesn't require the Client to keep track of outgoing requests. This is cleaner, more memory efficient, and less likely to result in memory leaks.
I don't think that will be difficult in any case. We don't need an extra collection - we can handle this all at the point where we have the HttpClientRequest
or the HttpRequest
.
- It is more intuitive as a user of the API - all the useful functionality is encapsulated on the response itself - you can pass that one object around to other parts of the code and they can decide to cancel it, etc. They don't also need a client, and you can decouple the knowledge about when to cancel from the service making the actual requests.
Attaching it to a StreamedResponse
has the downside of pushing more users onto that API over http.get
. I think cancellation (usually timeout) is a common enough use case that we want to support it on those APIs as well. Changing the return type from Future
to CancelablleOperation
is more breaking, and I'm not entirely convinced one way or the other whether it's a better API if we were starting from scratch.
- It allows you to synchronously cancel requests, instead of waiting an arbitrary amount of time for the event queue to catch up. This helps to mitigate race conditions between the cancellation and completion of the request, and it helps to waste less data by cancelling the request sooner.
I would expect that the typical case cancelations only happen after some async trigger either way. I think at most we could shave a microtask or 2, but I don't think that gives much power to typical usages. Which likely can't make many guarantees about behavior even if we give them a synchronous cancel.
I'm running with the abortOn
approach for now.
One thing that's tricky here, I didn't realize the dart:io
implementation causes the done
future to complete as an error as opposed to never completing. I'm pretty sure the implementation in the browsers wouldn't do that.
I think it would be easier to build the error behavior in the browser than to build the never-complete behavior for dart:io
but probably either is possible.
I don't have a strong opinion between completing as error on abort, or never completing on abort. One nice thing about the error approach is that if you do want to forward an error object and stack trace through it's possible to do so.
cc @lrhn for opinions on completing as error vs never completing for aborted requests.
I really don't think abortOn
with a Future
type is the right api here... it has a lot of downsides. Specifically since you can't cancel the future at all I think it will be prone to memory leaks, hung processes on exit, and other similar issues.
How about just supporting the timeout
argument which we can ensure is implemented better (using a timer which we can cancel) as opposed to a delayed future?
This is a must have feature. A lot of community backed packages are using this package at their core. And because there is not yet a abort functionality for downloads, there are huge amount of wasted data for Flutter users. This is really bad user experience when users check their data usage on their phones.
I am not sure why people are so concerned about breaking changes. Flutter is still new. Breaking changes are to be expected and for a core functionality like this, I am sure devs wont complain.
Imho what devs would really complain about could be a platform with APIs so hard to understand because they were patched for every new feature instead of getting redesigned. If we want Flutter to remain and feel fresh and different from the old and slow SDks then we have to live with breaking changes for a while.
Personally, the lack of aborting is a big deal for us because we have a video heavy app and we are using flutter_cache_manager to pre-download some of the videos in the list. If the user scrolls even further, we would like to abort the started downloads and start new ones. But for now we are stuck with 10's of started downloads which will be just byte waste.
Hi, is there any updates after half a year?
Related proposal: HttpClient deadline and retry (dart-lang/sdk#46557)
@natebosch – what's the current story here?
This is still something we plan on looking at but we don't have a concrete plan.
Moving on to 2022, we would still be very grateful to have this fixed. I agree 100% with @aytunch - a breaking change is something many developers can live with. At least it's much better than to leave this issue stale as it's affecting many simple use cases like stopping a download/upload of a very big file. If you can please move this to a higher priority... Thanks 🙏🏻
After two years (and half a year after last comments), is there any updates...? This is very frequently used and wanted feature.
Putting this on your radar, @brianquinlan
I'll be following this thread too
Any updates? I am willing to PR
Meanwhile, Dio supports abort()
since March
Proof: https://github.com/flutterchina/dio/pull/1440
@subzero911 Is it a "real" abort, i.e. it cancels the underlying HTTP request / TCP stream? Or, the underlying request is still going on?
@subzero911 Is it a "real" abort, i.e. it cancels the underlying HTTP request / TCP stream? Or, the underlying request is still going on?
Yes, I believe it closes the connection or destroys socket.
Looking at this from the implementation side instead of the the signature side.
This is going to be challenging to roll out. I think whatever signature we propose, we're going to end up needing to use CancelableOperation
in the implementation for Client
subclasses.
I don't think we can get away from having an incremental migration. I think we need some version that we can publish which is compatible with both old and new implementation of Client
. There are dependencies that are not likely to be comfortable taking a git
dependency for any consequential period of time like flutter_tools
, and enough external dependencies in general that trying to coordinate getting everything landed at once will be tough.
The API between BaseClient
and subclasses is to override the send
method. To support cancellation in different types of clients, I think we'd want to change that signature from Future<StreamedResponse> send(BaseRequest)
to CancelableOperation<StreamedResponse> send(BaseRequest)
. (Clients will also want to handle cancellation of a listener on the stream in most cases)
I'm not sure the best way to make this migration incremental.
I wonder if it would work out to
CancelableOperation<StreamedResponse> sendCancelable(BaseRequest) { /* implement with `send` */ }
send
based on sendCancelable
. Overriding either one is sufficient, but if neither is overridden it's a runtime error, instead of a static error.@override send
to @override sendCancelable
.sendCancellable
so that the override becomes statically mandatory instead of runtime mandatory.This expands the API surface area of BaseClient
, but I think Client
can keep the same API so consumers are not impacted, only implementors.
@natebosch Glad you're looking to add this feature to Dart. I've been so impressed with Dart, up until now. I just spent a bunch of time trying to add a cancel download feature to my app. Only to find Dart has no practical way of doing this.
I don't use a listener on my stream. A listener is not compatible with IOSink (wrong data type), this is my use case:
`http.StreamedResponse response = await client.send(request);
await response.stream.map((event) => showDownloadProgress(event)).pipe(out);
IOSink out = audioFile.openWrite();`
Until there is some elegant way to cancel the response my app will not have the ability to stop a download.
Until there is some elegant way to cancel the response my app will not have the ability to stop a download.
We may consider some API with a cancelation token or similar, which may be able to interrupt the stream with an error.
Depending on the API though, you can workaround this by interrupting the stream in between. Perhaps using something like .takeUntil
from package:stream_transform
await response.stream.map(showDownloadProgress).takeUntil(cancelFuture).pipe(out);
https://pub.dev/documentation/stream_transform/latest/stream_transform/TakeUntil/takeUntil.html
Although that will currently end the stream and finish writing the file, not result in an error... Edit: I think it should. https://github.com/dart-lang/stream_transform/pull/165
Depending on the API though, you can workaround this by interrupting the stream in between. Perhaps using something like
.takeUntil
frompackage:stream_transform
If the stream is interrupted does http.Client() close the connection with the server?
If the stream is interrupted does http.Client() close the connection with the server?
Yes, it should.
I've created a fork of this package to support cancellation via a token, which includes cancelling the underlying request: https://pub.dev/packages/cancellation_token_http
I went with the token approach for convenience when chaining multiple requests, or making a request and using a cancellable isolate for parsing the result.
await response.stream.map(showDownloadProgress).takeUntil(cancelFuture).pipe(out);
I don't see how onPressed inside an IconButton could trigger cancelFuture.
I've created a fork of this package to support cancellation via a token, which includes cancelling the underlying request: https://pub.dev/packages/cancellation_token_http
I tried that package a couple days ago. The problem I had was, it threw on token.cancel();
instead of executing the code inside of the block on http.CancelledException
.
Edit: Although, that may not matter for a StreamedResponse. I may revisit your cancellation_token_http package.
I tried that package a couple days ago. The problem I had was, it threw on
token.cancel();
instead of executing the code inside of the blockon http.CancelledException
. Edit: Although, that may not matter for a StreamedResponse. I may revisit your cancellation_token_http package.
If you have time please open an issue with a reproducible example and I'll look into this. The request future itself should be throwing a CancelledException after the token is cancelled.
I also would welcome a possibility to cancel/abort the request by user or some program logic. Now I'm looking for a solution how to cancel a request which takes a long time to process on server side as it filters a lot of data to be displayed on the map in Flutter app. Whenever the map is moved, new request is sent to get data for new boundaries. So I need to cancel previous request as it's data will be discarded and it just stresses server.
I'll take a look on cancelation_token_http
, maybe it will solve my problem.
Otherwise I like proposed solution with CancelableOperation<...>
I'll take a look on
cancelation_token_http
, maybe it will solve my problem.
I got it working for my application. Check out the open issue I created for cancelation_token_http package. You can gleam some usage tips from it.
I'm working on an HTTP library that wraps this package and augments it with functionality from dart:io, etc., and needed to implement request cancellation and timeouts, so I also forked this package to implement cancellable requests.
https://github.com/SamJakob/dart-http/tree/feat/cancellation
My approach was a little different to the above:
I went with a Controller
class that can be optionally passed into the Request
class (it is entirely opt-in which makes the functionality, at least, backwards-compatible by simply not specifying a controller).
I've successfully tested at least the dart:io
IOClient
with an external server application that logs the lifecycle state at each point. I have also implemented the same functionality for the dart:html
BrowserClient
using onReadyState
to track the lifecycle but haven't tested the browser side of things yet and haven't written formal unit tests for it.
Server implementation and Dart client example used to test: https://github.com/SamJakob/cancellable_http_dart_testing
Bump on this. This is a basic networking feature that should be in every networking library
On the web
HttpRequest
supportsabort
: https://api.dart.dev/stable/2.8.1/dart-html/HttpRequest/abort.htmlWe have a proposal for adding abort on the
dart:io
HttpClientRequest
: https://dart-review.googlesource.com/c/sdk/+/147339We need to come up with a design for supporting
abort
through the interfaces in this package and validate that we can build it on bothdart:html
anddart:io
implementations.The easiest option might be to add a method on
StreamedResponse
. There is some precedent for that withIOStreamedResponse.detachSocket
. https://github.com/dart-lang/http/blob/9063ba379b7e7386cced830feb27cc7a5fd3ec7e/lib/src/io_streamed_response.dart#L36 (we need to make sure the differences between these are clearly documented.I do wonder if we need to consider some sort of support for the more convenient methods than
send
though...cc @kevmoo @jakemac53 @grouma @zichangg @lrhn for thoughts