-
Notifications
You must be signed in to change notification settings - Fork 362
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
Design an API to support aborting requests #424
Comments
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 ***@***.***> 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
<#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 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 |
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
This does get a bit trickier to implement - you are left with a few not so great options:
|
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 |
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
Attaching it to a
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 One thing that's tricky here, I didn't realize the I think it would be easier to build the error behavior in the browser than to build the never-complete behavior for 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 How about just supporting the |
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 |
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 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 The API between I'm not sure the best way to make this migration incremental. |
I wonder if it would work out to
This expands the API surface area of |
@natebosch Glad you're looking to add this feature to Dart. I've been so impressed with Dart, up until now. 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. |
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 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... |
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. |
I don't see how onPressed inside an IconButton could trigger cancelFuture. |
I tried that package a couple days ago. The problem I had was, it threw on |
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 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 I've successfully tested at least the ExamplesPartial Timeouts (for each step of the request-response cycle)import 'package:http/http.dart';
Future<void> main() async {
Client client = Client();
// Create a request controller, can be passed into multiple requests to
// cancel them all at once or to set timeouts on all of them at once.
// Or, one can be created per request.
final controller = RequestController(
// Optional timeout for the overall request (entire round trip).
timeout: Duration(seconds: 5),
// Optional timeouts for each state of the request.
// If a state is not specified, it will not timeout.
partialTimeouts: PartialTimeouts(
// Timeout for connecting to the server.
connectTimeout: Duration(seconds: 5),
// Timeout for the request body to be sent and a response to become
// available from the server.
sendTimeout: Duration(seconds: 4),
// Timeout for processing the response body client-side.
receiveTimeout: Duration(seconds: 6),
),
);
final response = await client.get(
// URL that hangs for 5 seconds before responding.
Uri.parse('http://localhost:3456/longrequest'),
controller: controller,
);
// Unhandled exception:
// TimeoutException after 0:00:04.000000: Future not completed
print(response.body);
} In this case, the send timeout triggers a Server-side logs:
Partial Timeouts (another example)import 'package:http/http.dart';
Future<void> main() async {
Client client = Client();
// Create a request controller, can be passed into multiple requests to
// cancel them all at once or to set timeouts on all of them at once.
// Or, one can be created per request.
final controller = RequestController(
// Optional timeout for the overall request (entire round trip).
timeout: Duration(seconds: 10),
// Optional timeouts for each state of the request.
// If a state is not specified, it will not timeout.
partialTimeouts: PartialTimeouts(
// Timeout for connecting to the server.
connectTimeout: Duration(seconds: 5),
// Timeout for the request body to be sent and a response to become
// available from the server.
sendTimeout: Duration(seconds: 4),
// Timeout for processing the response body client-side.
receiveTimeout: Duration(seconds: 6),
),
);
final response = await client.get(
// URL that immediately writes headers and a partial body, then hangs for
// 5 seconds before responding with the remainder.
Uri.parse('http://localhost:3456/longpoll'),
controller: controller,
);
// Hello, world! (the payload sent immediately)
// Hello, world! (the payload sent after 5 seconds)
print(response.body);
} In this case, none of the timeouts trigger an exception because the response body becomes available immediately, even if it is not complete and the server hangs for only 5 seconds before completing the response, meaning the receive timeout of 6 seconds is not triggered either. Server-side logs:
Cancelation Example (cancel during response)import 'package:http/http.dart';
Future<void> main() async {
Client client = Client();
// Create a request controller, can be passed into multiple requests to
// cancel them all at once or to set timeouts on all of them at once.
// Or, one can be created per request.
final controller = RequestController();
final responseFuture = client.get(
// URL that hangs for 5 seconds before responding.
Uri.parse('http://localhost:3456/longrequest'),
controller: controller,
);
await Future.delayed(Duration(milliseconds: 500));
controller.cancel();
final response = await responseFuture;
print(response.body);
// Unhandled exception:
// CancelledException: Request cancelled
print(response.body);
} Cancels after 500ms which is whilst the response is being written. The connection is destroyed so both the client and server clean up immediately after the request is cancelled. Server-side logs:
Cancellation Example (cancelled immediately)import 'package:http/http.dart';
Future<void> main() async {
Client client = Client();
// Create a request controller, can be passed into multiple requests to
// cancel them all at once or to set timeouts on all of them at once.
// Or, one can be created per request.
final controller = RequestController();
final responseFuture = client.get(
// URL that hangs for 5 seconds before responding.
Uri.parse('http://localhost:3456/longrequest'),
controller: controller,
);
controller.cancel();
final response = await responseFuture;
print(response.body);
// Unhandled exception:
// CancelledException: Request cancelled
print(response.body);
} The request is cancelled immediately and isn't even logged on the server. Server-side logs:
Server implementation and Dart client example used to test: |
Bump on this. This is a basic networking feature that should be in every networking library |
Double bump. I'm currently using a mixture of http and dio and it's messy. |
https://github.com/fluttercandies/http_client_helper provides a tricky solution. it throws an error to interrupt the request. it's not convenient to use. It needs to wrap the client. |
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
.http/lib/src/io_streamed_response.dart
Line 36 in 9063ba3
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
The text was updated successfully, but these errors were encountered: