Set timeout for HTTPClient get() request

52,951

Solution 1

There are two different ways to configure this behavior in Dart

Set a per request timeout

You can set a timeout on any Future using the Future.timeout method. This will short-circuit after the given duration has elapsed by throwing a TimeoutException.

try {
  final request = await client.get(...);
  final response = await request.close()
    .timeout(const Duration(seconds: 2));
  // rest of the code
  ...
} on TimeoutException catch (_) {
  // A timeout occurred.
} on SocketException catch (_) {
  // Other exception
}

Set a timeout on HttpClient

You can also set a timeout on the HttpClient itself using HttpClient.connectionTimeout. This will apply to all requests made by the same client, after the timeout was set. When a request exceeds this timeout, a SocketException is thrown.

final client = new HttpClient();
client.connectionTimeout = const Duration(seconds: 5);

Solution 2

You can use timeout

http.get(Uri.parse('url')).timeout(
  const Duration(seconds: 1),
  onTimeout: () {
    // Time has run out, do what you wanted to do.
    return http.Response('Error', 408); // Request Timeout response status code
  },
);

Solution 3

There is no option to set timeout using Dart's http. However, as it returns Future, we can set timeout on the Future.

The example below sets timeout to 15 second. If it has been 15 seconds and no response received, it will throw TimeoutException

Future<dynamic> postAPICall(String url, Map param, BuildContext context) async {
  try {
    final response =  await http.post(url,
        body: param).timeout(const Duration(seconds: 10),onTimeout : () {
      throw TimeoutException('The connection has timed out, Please try again!');
    });

    print("Success");
    return response;
  } on SocketException {
    print("You are not connected to internet");
  }
}

Solution 4

The HttpClient.connectionTimeout didn't work for me. However, I knew that the Dio packet allows request cancellation. Then, I dig into the packet to find out how they achieve it and I adapted it to me. What I did was to create two futures:

  • A Future.delayed where I set the duration of the timeout.
  • The HTTP request.

Then, I passed the two futures to a Future.any which returns the result of the first future to complete and the results of all the other futures are discarded. Therefore, if the timeout future completes first, your connection times out and no response will arrive. You can check it out in the following code:

Future<Response> get(
    String url, {
    Duration timeout = Duration(seconds: 30),
  }) async {
    
    final request = Request('GET', Uri.parse(url))..followRedirects = false;
    headers.forEach((key, value) {
      request.headers[key] = value;
    });

    final Completer _completer = Completer();

    /// Fake timeout by forcing the request future to complete if the duration
    /// ends before the response arrives.
    Future.delayed(timeout, () => _completer.complete());

    final response = await Response.fromStream(await listenCancelForAsyncTask(
      _completer,
      Future(() {
        return _getClient().send(request);
      }),
    ));
  }
    
  Future<T> listenCancelForAsyncTask<T>(
    Completer completer,
    Future<T> future,
  ) {
    /// Returns the first future of the futures list to complete. Therefore,
    /// if the first future is the timeout, the http response will not arrive
    /// and it is possible to handle the timeout.
    return Future.any([
      if (completer != null) completeFuture(completer),
      future,
    ]);
  }

  Future<T> completeFuture<T>(Completer completer) async {
    await completer.future;
    throw TimeoutException('TimeoutError');
  }

Solution 5

This is an example of how to extend the http.BaseClient class to support timeout and ignore the exception of the S.O. if the client's timeout is reached first. you just need to override the "send" method...

the timeout should be passed as a parameter to the class constructor.

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

// as dart does not support tuples i create an Either class
class _Either<L, R> {
  final L? left;
  final R? right;

  _Either(this.left, this.right);
  _Either.Left(L this.left) : right = null;
  _Either.Right(R this.right) : left = null;
}

class TimeoutClient extends http.BaseClient {
  final http.Client _httpClient;
  final Duration timeout;

  TimeoutClient(
      {http.Client? httpClient, this.timeout = const Duration(seconds: 30)})
      : _httpClient = httpClient ?? http.Client();

  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    // wait for result between two Futures (the one that is reached first) in silent mode (no throw exception)
    _Either<http.StreamedResponse, Exception> result = await Future.any([
      Future.delayed(
          timeout,
          () => _Either.Right(
                TimeoutException(
                    'Client connection timeout after ${timeout.inMilliseconds} ms.'),
              )),
      Future(() async {
        try {
          return _Either.Left(await _httpClient.send(request));
        } on Exception catch (e) {
          return _Either.Right(e);
        }
      })
    ]);

    // this code is reached only for first Future response,
    // the second Future is ignorated and does not reach this point
    if (result.right != null) {
      throw result.right!;
    }

    return result.left!;
  }
}

Share:
52,951
SteAp
Author by

SteAp

Fiddling around with Flutter. Writing Frog (spare-time project). Profile image: Please don't copy my painting.

Updated on January 05, 2022

Comments

  • SteAp
    SteAp over 2 years

    This method submits a simple HTTP request and calls a success or error callback just fine:

      void _getSimpleReply( String command, callback, errorCallback ) async {
    
        try {
    
          HttpClientRequest request = await _myClient.get( _serverIPAddress, _serverPort, '/' );
    
          HttpClientResponse response = await request.close();
    
          response.transform( utf8.decoder ).listen( (onData) { callback( onData ); } );
    
        } on SocketException catch( e ) {
    
          errorCallback( e.toString() );
    
        }
      }
    

    If the server isn't running, the Android-app more or less instantly calls the errorCallback.

    On iOS, the errorCallback takes a very long period of time - more than 20 seconds - until any callback gets called.

    May I set for HttpClient() a maximum number of seconds to wait for the server side to return a reply - if any?

  • Ahmad
    Ahmad over 3 years
    Connection timeout and request timeout are two different concepts. These two ways specified in this answer are not interchangeable and each one does a different thing.
  • L. Chi
    L. Chi almost 3 years
    This worked fine. Just in case you are using this you can not return null. Use something like this "return http.Response('', 500);" replace 500 for any http code you need
  • ch271828n
    ch271828n almost 3 years
    Hi, about request.close().timeout(), I am afraid it will waste resource: if the http request last for, say, 20s, then even if it "timeouts", the resource is still be used! How can I solve it?
  • ch271828n
    ch271828n almost 3 years
    Hi, about request.timeout(), I am afraid it will waste resource: if the http request last for, say, 20s, then even if it "timeouts", the resource is still be used! How can I solve it?
  • ch271828n
    ch271828n almost 3 years
    Hi, about request.timeout(), I am afraid it will waste resource: if the http request last for, say, 20s, then even if it "timeouts", the resource is still be used! How can I solve it?
  • jamesdlin
    jamesdlin almost 3 years
    This answer probably should clarify that it's using package:http and not dart:io's HttpClient.
  • jamesdlin
    jamesdlin almost 3 years
    For any future readers, see @ch271828n's question: Proper way of setting request timeout for Flutter http requests?.
  • Justin
    Justin over 2 years
    How about setting the http status code to 408?