dart-lang / http

A composable API for making HTTP requests in Dart.
https://pub.dev/packages/http
BSD 3-Clause "New" or "Revised" License
1.01k stars 352 forks source link

Best practices on if and when you should close HTTP connections in flutter apps? #422

Open yahya-uddin opened 4 years ago

yahya-uddin commented 4 years ago

According to the docs it says:

If you're making multiple requests to the same server, you can keep open a persistent connection by using a Client rather than making one-off requests. If you do this, make sure to close the client when you're done:

However, in most mobile apps, you tend to send most requests to a single server.

Does that mean, for an app that does a lot of API calls to a server, that we should keep the connection up throughout the app lifetime?

Also should we close and re-open the connection when the app suspends and resume states?

b1acKr0se commented 4 years ago

No response? I'm interested in this as well.

muskan911 commented 3 years ago

i wanna know this too as i have an application with a lot of api requests and which takes time in getting the response and whenever abruptly the screen is changed or another api is called before the earlier request was completed it hangs and shows an error Connection Closed Before Full Header was received. I have been stuck on this for very long any help is appreciated!

cheapestorderconcept commented 3 years ago

I have been facing the same issue when making api requests. I couldn't close the connection when my app navigate to other pages which makes my app to be hanging. Any form of help or explanation will be appreciated

mikeesouth commented 3 years ago

Hmm. I'm also following this. In my app I have a singleton http client that I initialize once. I then call this client synchronously with get/post/puts. I never close it. This seems to work just fine and I've made several hundreds calls without any issues. However. My end users have reported that the app seemed to stop talking to the server after a while but the animations and buttons were still working. Just that all requests times out. This problem occured after a while and what seems to be at random. I've debugged this for quite some time and at one point I got a state where I called myHttpClient.get() but I did not receive the request server side. After 30 seconds (my configured timeout) the request timed out. I could keep on sending requests to the server but none of these requests reached the server. It seemed like that http client was disposed but I did not get any exception other than requests timing out. So I figured, this is probably what my end users are experiencing. That all http requests suddenly just time out. Could this be because of some pool of requests being "filled up" with broken requests? If the wifi is shaky and a requests break, maybe that pool slot is "broken" and after a while all pool slots are broken and the http client is completely broken?

Would a better practice be to initialize a new HttpClient for every request and closing that after each request? Any downsides?

b1acKr0se commented 3 years ago

@mikeesouth one obvious downside is that it's much slower than using an existing client (about 2x slower in my rough testing). I also have a production app that I never close the connection and it seems that it doesn't have your problem.

yinkyAde commented 3 years ago

What i will do is keep open a persistent connection by using a Client rather than making a one-off request since multiple requests are sent to the same server

  var client = http.Client();
  try {
    var uriResponse = await client.post(Uri.parse('https://url.com'),body: {''});
  } finally {
    client.close();
  }
HasanElHefnawy commented 2 years ago

In terms of network traffic, it's better to use the same client throughout the app lifecycle. Establishing a new connection for each api is very expensive. However, as per the documentation,

It's important to close each client when it's done being used; failing to do so can cause the Dart process to hang.

Also, if client.close() isn't called, it doesn't mean that the server will keep the connection open forever. The server will close the connection if it is idle for a period more than the HTTP Keep-Alive Timeout. In this case, if the client sends a new request over the connection closed by server, he'll get a 408 Request Timeout.

So, if you decide to use the same client throughout the app lifecycle, keep in your mind the two possible issues that you may run into.

RANSoftRA commented 2 years ago

From @mikeesouth comment

This problem occured after a while and what seems to be at random. I've debugged this for quite some time and at one point I got a state where I called myHttpClient.get() but I did not receive the request server side. After 30 seconds (my configured timeout) the request timed out. I could keep on sending requests to the server but none of these requests reached the server.

We encountered this issue as well and narrowed it down to the following problem: https://stackoverflow.com/questions/66063913/socketexception-failed-host-lookup-com-os-error-nodename-nor-servname-p

In our case we had a HttpService class to perform get, put, post, etc. requests (adding headers etc.) - that was used throughout the app. We added a counter to record the request count and instanciate a new instance every 200 times (or something like that) and only if no pending calls are present (otherwise the request fails with Client already closed):

...
import 'package:http/http.dart' as http;
...

class HttpService {
  // api call threshold
  // create a new instance of http client after a minimum given number of calls have been made
  // needed, because otherwise SocketException occurs after some time
  static int _calls = 0;
  static int _minCallsBeforeNewInstance = 200;
  // record ongoing calls - needed otherwise a forced close and new instance of http client may cause problems with pending calls
  static int _ongoingCallsCount = 0;

  static http.Client _httpClient = http.Client();

  static final HttpService _instance = HttpService._();

  HttpService._();

  factory HttpService() {
    return _instance;
  }

  ...

  Future<http.Response> get(
    Uri url, {
    Map<String, String>? headers,
  }) async {
    _calls++;
    _ongoingCallsCount++;
    Logger.debug('GET ($_calls): $url');
    return _httpClient
        .get(
          url,
          headers: await _headers(headers),
        )
        .whenComplete(_handleCallComplete);
  }

  ...

  void _handleCallComplete() {
    if (_ongoingCallsCount > 0) _ongoingCallsCount--;
    if (_ongoingCallsCount < 0) _ongoingCallsCount = 0;
    // create new instance only if no pending calls are out there and the minimum calls have been reached
    if (_ongoingCallsCount == 0 && _calls >= _minCallsBeforeNewInstance) {
      Logger.debug('Closing existing http client and create new one');
      _httpClient.close();
      _httpClient = http.Client();
      // reset calls
      _calls = 0;
    }
  }
}
dustin-graham commented 2 years ago

@RANSoftRA , Have you found that your posted solution resolves the host lookup exception issue that you linked to?

RANSoftRA commented 2 years ago

@RANSoftRA , Have you found that your posted solution resolves the host lookup exception issue that you linked to?

Yes, the linked issue was solved with this approach.

LeoAndo commented 1 year ago

It is client.close(); with _withClient. If you request continuously, client.close(); will be repeated, so in that case Should I use a Client object and close it as soon as I'm done with it????

get https://github.com/dart-lang/http/blob/06649afbb5847dbb0293816ba8348766b116e419/pkgs/http/lib/http.dart#L46

post https://github.com/dart-lang/http/blob/06649afbb5847dbb0293816ba8348766b116e419/pkgs/http/lib/http.dart#L68

_withClient https://github.com/dart-lang/http/blob/06649afbb5847dbb0293816ba8348766b116e419/pkgs/http/lib/http.dart#L166

refs

https://pub.dev/packages/http#using https://github.com/dart-lang/http/blob/06649afbb5847dbb0293816ba8348766b116e419/pkgs/http/lib/http.dart

vlowe85 commented 7 months ago

@mikeesouth one obvious downside is that it's much slower than using an existing client (about 2x slower in my rough testing). I also have a production app that I never close the connection and it seems that it doesn't have your problem.

Same issue for me, have implemented the suggested.

Could it be the addition of background_fetch, background_processing capabilities that keep the http instance alive so long?

malbolged commented 5 months ago

4 years :|

escamoteur commented 2 months ago

@natebosch @brianquinlan @kevmoo I just dug deeper into this issue because I'm trying to use the new cronet and cupertino Httpclients. The example at https://github.com/dart-lang/http/blob/master/pkgs/flutter_http_example/lib/http_client_factory.dart returns always new instances of the proxy which leads to the question that is also asked here for such a long time:

  1. How many HTTPClients should an app use?
    • a new one for every request?
    • separate ones if you do requests from different servers?
    • separate ones if you use isolates so that every isolate gets its own?
    • use the same one everywhere? which pros/cons to these options are there?
  2. do and when do we have to call client.close()?
  3. Should a client be closed and re-opened when the app suspends and resumes respectively?
  4. Or should it be closed only when the app is entirely closed?
  5. If yes to the previous question, should it be done explicitly?

What is the best strategy to keep connections alive?

pedromassango commented 2 months ago

Aren't some of/those questions answered by the comments on the http methods? Ref: https://github.com/dart-lang/http/blob/06649afbb5847dbb0293816ba8348766b116e419/pkgs/http/lib/http.dart#L121C30-L123C4

Screenshot 2024-06-12 at 10 12 15 PM
escamoteur commented 2 months ago

This still doesn't give a clear answer at least not for my understanding see this full thread Am 12. Juni 2024, 21:19 +0100 schrieb Pedro Massango @.***>:

Aren't some of/those questions answered by the comments on the http methods? Ref: https://github.com/dart-lang/http/blob/06649afbb5847dbb0293816ba8348766b116e419/pkgs/http/lib/http.dart#L121C30-L123C4 Screenshot.2024-06-12.at.10.12.15.PM.png (view on web) — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

brianquinlan commented 2 months ago

@natebosch @brianquinlan @kevmoo I just dug deeper into this issue because I'm trying to use the new cronet and cupertino Httpclients. The example at https://github.com/dart-lang/http/blob/master/pkgs/flutter_http_example/lib/http_client_factory.dart returns always new instances of the proxy

You mean a new instance of the Client, right? A new Client is not created for every HTTP request - it is created once when it is first accessed. I'll add a comment explaining that.

which leads to the question that is also asked here for such a long time:

  1. How many HTTPClients should an app use?

Probably one would be fine for most applications. You may need more if you have different use cases where Clients with different properties are required. For example, you might want a separate client to download large assets with package:cupertino_http so that allowsConstrainedNetworkAccess can be enabled.

  • a new one for every request?

No.

  • separate ones if you do requests from different servers?

That should not be necessary except in the case of the exception that I mentioned above.

  • separate ones if you use isolates so that every isolate gets its own?

You will need a new Client per isolate because Clients are not shareable between isolates (even if they are in the same isolate group).

  • use the same one everywhere? which pros/cons to these options are there?
  1. do and when do we have to call client.close()?

You should call client.close() when you are done with the client. Depending on the client, this may cause the Clients connection pool to be released and may release significant native resources. If you only have a single Client then, in flutter, I wouldn't worry about closing it.

  1. Should a client be closed and re-opened when the app suspends and resumes respectively?

IOClient connections are not valid after network changes, proxy changes, etc. but I think that you should just not use IOClient on mobile devices.

  1. Or should it be closed only when the app is entirely closed?

I think that we covered this one.

  1. If yes to the previous question, should it be done explicitly?

I think that we covered this one.

What is the best strategy to keep connections alive?

I'm don't have a good answer for that.

escamoteur commented 2 months ago

thanks a lot @brianquinlan Ok, so to summarize:

If you don't need some special behavior clients 1 Client for the full app should be anough and it doesn't have any impact of the performance even if we do multiple async network calls on the same client in parallel and one client is able to handle multiple Http conenctions that use the keep-alive feature. and if we only use one we don't even have to close it manually because if the app closes it will be destroyed.

The second paragraph in the API dos might be the reason people are not sure what to do: image Could you explain what is really meant by this because it seems to contradict what I understand from your reply. I hope the connection pool will shrink once the connections are no longer used?

brianquinlan commented 2 months ago

Could you explain what is really meant by this because it seems to contradict what I understand from your reply. I hope the connection pool will shrink once the connections are no longer used?

Programs run using the Dart CLI will not exit while there are pending asynchronous operations. IOClient maintains a connection pool so the Dart CLI will not exit while there are still connections in the connection pool. How long a connection is kept active when not used is controlled by HttpClient.idleTimeout but the default is 15 seconds.

Other clients have different behavior.

If I understand correctly, Flutter doesn't care about pending asynchronous operations when deciding whether the application should terminate (and mobile operation systems can kill an application at anytime anyway).

escamoteur commented 2 months ago

Ah, that makes sense. I was already guessing that this might only apply to Dart clip clients. And if they will maximal block whole a connection is still active that should rarely make a problem. Maybe that API comment could get addition that explains this. Will write an article over the weekend Am 14. Juni 2024, 19:59 +0100 schrieb Brian Quinlan @.***>:

Could you explain what is really meant by this because it seems to contradict what I understand from your reply. I hope the connection pool will shrink once the connections are no longer used? Programs run using the Dart CLI will not exit while there are pending asynchronous operations. IOClient maintains a connection pool so the Dart CLI will not exit while there are still connections in the connection pool. How long a connection is kept active when not used is controlled by HttpClient.idleTimeout but the default is 15 seconds. Other clients have different behavior. If I understand correctly, Flutter doesn't care about pending asynchronous operations when deciding whether the application should terminate (and mobile operation systems can kill an application at anytime anyway). — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>