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.03k stars 358 forks source link

Cannot catch SocketException when using .timeout() #329

Closed Gujie-Novade closed 5 years ago

Gujie-Novade commented 5 years ago

I want to create a login request and set a timeout of 5s.

If I use this code:

Future<void> login(String email, String password) async {
  final body = { 'email': email, 'password': password };
  final client = http.Client();
  http.Response res;
  try {
    res = await client
      .post(
        '$url/login',
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(body))
      .timeout(const Duration(seconds: 5));
  } on TimeoutException catch (e) {
    // Display an alert, no internet
  } catch (err) {
    print(err);
    return null;
  }

  // Do something with the response...
}

In case there is no internet, I will catch the Timeout Exception first. But 15-20s later, there will be another SocketException thrown asynchronously which I cannot catch...

However, if I only use dart.io with this code:

Future<void> login(String email, String password) async {
  final client = HttpClient();
  client.connectionTimeout = const Duration(seconds: 5);

  try {
    final res = await client.postUrl(Uri.parse('$url/login'));
    res.close();
  } on SocketException catch (_) {
    // If there is a timeout, you will catch the exception here
    print('caught socket exception');
  } catch (e) {
    print('caught another exception');
    throw e;
  } finally {
    client.close();
  }
}

In case of a timeout, the exception will be thrown as a SocketException so I don't have this issue.
BTW, someone already created a question on SO here

So there is a problem with the .timeout() function.

natebosch commented 5 years ago

The equivalent approach using this package is to set a timeout on the IO HttpClient like you would when using purely dart:io, then construct an IOClient to wrap it as a client from this package.

import 'dart:io';

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

Future<void> login(String email, String password) async {
  final ioClient = HttpClient();
  client.connectionTimeout = const Duration(seconds: 5);
  final body = { 'email': email, 'password': password };
  final client = http.IOClient(ioClient);
  http.Response res;
  try {
    res = await client
      .post(
        '$url/login',
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(body));
  } on SocketException catch (e) {
    // Display an alert, no internet
  } catch (err) {
    print(err);
    return null;
  }

  // Do something with the response...
}

The .timeout function on Future is unrelated to this package.

If you do want to use future.timeout you can install a separate error handler for the original future.

Edit: The place where I thought the exception could occur after a .timeout was incorrect.

ayazemre commented 3 years ago

The equivalent approach using this package is to set a timeout on the IO HttpClient like you would when using purely dart:io, then construct an IOClient to wrap it as a client from this package.

import 'dart:io';

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

Future<void> login(String email, String password) async {
  final ioClient = HttpClient();
  client.connectionTimeout = const Duration(seconds: 5);
  final body = { 'email': email, 'password': password };
  final client = http.IOClient(ioClient);
  http.Response res;
  try {
    res = await client
      .post(
        '$url/login',
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(body));
  } on SocketException catch (e) {
    // Display an alert, no internet
  } catch (err) {
    print(err);
    return null;
  }

  // Do something with the response...
}

The .timeout function on Future is unrelated to this package.

If you do want to use future.timeout you can install a separate error handler for the original future.

Future<void> login(String email, String password) async {
  final body = { 'email': email, 'password': password };
  final client = http.Client();
  http.Response res;
  try {
    res = await client
      .post(
        '$url/login',
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(body))
      .catchError((e) {
        // SocketException would show up here, potentially after the timeout.
      })
      .timeout(const Duration(seconds: 5));
  } on TimeoutException catch (e) {
    // Display an alert, no internet
  } catch (err) {
    print(err);
    return null;
  }

  // Do something with the response...
}

Is this solution still viable? I am having same error. With or without timeout it throws SocketException when there is no internet and I can not catch it with try catch block. When I try to construct a client it does not accept "ioClient" as a parameter.

My code;

try {
      final checkEmail = await http
          .get(credentials, headers: {headers}).catchError((error) {
        print(error);
      }).timeout(Duration(seconds: 5));

      print(checkEmail.body);
      print(checkEmail.statusCode);
      return checkEmail.statusCode == 200 ? "ok" : "Could not validate email.";
    } on Exception catch (f) {
      print(f.toString());
      return "Could not validate email.";
    } on Error catch (e) {
      print(e.toString());
      return "Could not validate email.";
    }
natebosch commented 3 years ago

With or without timeout it throws SocketException when there is no internet and I can not catch it with try catch block.

Do you have a reliable and minimal reproduction case for this? This isn't the first time I've seen a report of an uncatchable exception but I have never been able to set it up on the VM such that I get an unhandled async exception. Whenever I try this I am able to catch the SocketException in a try/catch block. https://github.com/dart-lang/http/pull/508

ayazemre commented 3 years ago

With or without timeout it throws SocketException when there is no internet and I can not catch it with try catch block.

Do you have a reliable and minimal reproduction case for this? This isn't the first time I've seen a report of an uncatchable exception but I have never been able to set it up on the VM such that I get an unhandled async exception. Whenever I try this I am able to catch the SocketException in a try/catch block.

508

I recorded my issue. In first the device is on airplane mode so it throws directly. Second part I cut and sped up the video. Server is offline and I successfully catch timeout exception. After close to a minute it throws the exception. https://youtu.be/dHUFB6jyE48

natebosch commented 3 years ago

After close to a minute it throws the exception.

It looks like the exception can be caught because it's showing up in a try block in your debugger.

I'm looking for help reproducing the case where a SocketException surfaces and cannot be caught at all.

ayazemre commented 3 years ago

After close to a minute it throws the exception.

It looks like the exception can be caught because it's showing up in a try block in your debugger.

I'm looking for help reproducing the case where a SocketException surfaces and cannot be caught at all.

Sorry if this is a noob question, I am a bit inexperienced. Can you tell me how I can catch it inside of current block?

Binozo commented 3 years ago

Still having same issue.

This is my Code:

  Future<ComputerModel?> checkIfComputerHasRufSohn(String ip) async {
    try {
      var response = await http
          .get(Uri.http(ip, '/test?test=hi'))
          .timeout(const Duration(seconds: 5))
          .catchError((error, stacktrace) {
        print("catched error");
      });
      //TODO parse daten
      return ComputerModel("Name", ip);
    } on SocketException catch (e) {
      //print("Caught socketexception: $e");
    } on TimeoutException catch (e) {
      //print('Caught: $e');
    } catch (e) {}
    //print('Done');
    return null;
  }

All exceptions get successfully caught. Only Socket Timeout Exception gets triggered after ~1 Minute and crashes App without being catchable.

Desync-o-tron commented 2 years ago

@natebosch I too am having this issue that @Binozo et. al. outlined.

Desync-o-tron commented 2 years ago

I suppose this isn't a huge issue because the first response gives a way around using a client object. That (sans the typo in the code) fixed it for me.