Skip to content
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

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

Open
yahya-uddin opened this issue May 23, 2020 · 23 comments

Comments

@yahya-uddin
Copy link

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
Copy link

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

@muskan911
Copy link

muskan911 commented Jan 4, 2021

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
Copy link

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
Copy link

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
Copy link

@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
Copy link

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
Copy link

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
Copy link

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
Copy link

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

@RANSoftRA
Copy link

@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
Copy link

LeoAndo commented Feb 8, 2023

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

_withClient((client) => client.get(url, headers: headers));

post

_withClient((client) =>

_withClient

client.close();

refs

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

@vlowe85
Copy link

vlowe85 commented Feb 1, 2024

@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
Copy link

4 years :|

@escamoteur
Copy link

@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?
  1. do and when do we have to call client.close()?
  2. Should a client be closed and re-opened when the app suspends and resumes respectively?
  3. Or should it be closed only when the app is entirely closed?
  4. If yes to the previous question, should it be done explicitly?

What is the best strategy to keep connections alive?

@pedromassango
Copy link

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
Copy link

escamoteur commented Jun 12, 2024 via email

@brianquinlan
Copy link
Collaborator

@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
Copy link

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
Copy link
Collaborator

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
Copy link

escamoteur commented Jun 14, 2024 via email

@absar
Copy link

absar commented Nov 5, 2024

These comments comment 2 from Brian Quinlan have very useful info, probably these should be added to http pub docs as FAQs or Tips, which will immensely help in designing http client usage, it will also remove many ambiguities

@b3qer
Copy link

b3qer commented Jan 9, 2025

in 2025 and there is no clear answer , im facing a hanging issue after multiple requests , i use client.close() when request is done and it works fine and never faced hanging again but i got a new problem , every request being slow when use close function
need to clear answer what to do 😢

@escamoteur
Copy link

I recommend my article https://blog.burkharts.net/everything-you-always-wanted-to-know-about-httpclients

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests