Search code examples
darthttprequestdart-asyncdart-http

Reusable Sync HttpClient Request


I need a reusable function that makes an HTTP request and awaits for its completion before returning the response as a String.

Here's the main function:

main() async {
  var json;
  json = await makeRequest('https://...');
  print(json);
  print('*** request complete ***');
}

(First Case) This is the reusable function that makes the HTTP request:

makeRequest(String url) async {
  var request = await new HttpClient().postUrl(Uri.parse(url));

  // Includes the access token in the request headers.
  request.headers.add(...);

  // Waits until the request is complete.
  var response = await request.close();

  await for (var contents in response.transform(UTF8.decoder)) {
    return contents;
  }
}

This works as expected and the output is:

// Response contents as a String...
*** request complete ***

(Second Case) Then I tried to do this and it didn't work:

makeRequest(String url) async {
  var request = await new HttpClient().postUrl(Uri.parse(url));

  // Includes the access token in the request headers.
  request.headers.add(...);

  // Waits until the request is complete.
  var response = await request.close();

  var json = '';
  await response.transform(UTF8.decoder).listen((contents) {
    // At first I tried to return contents here, but then I added onDone().
    json += contents;
  }, onDone: () {
    return json;
  });

  return json;
}

I've tried defining the function within listen with async and await, returning contents within listen without onDone(), but the output is the same:

// Empty line.
*** request complete ***
// Waits a few seconds doing nothing before terminating...

Does anyone know why the second case doesn't work?

EDIT:

After updating the code it does what it was supposed to do, but takes a few seconds before terminating execution:

Future<String> twitterRequest(String url) async {
  var request = await new HttpClient().postUrl(Uri.parse(url));

  // Includes the access token in the request headers.
  request.headers.add(...);

  // Waits until the request is complete.
  var response = await request.close();

  var json = '';
  await for (var contents in response.transform(UTF8.decoder)) {
    json += contents;
    // Putting a break here produces the same output but terminates immediately (as wanted).
  }

  return json;
}

Output:

// Prints response contents...
*** request complete ***
// Takes a few seconds before execution terminates. With the break the code terminates immediately.

EDIT2:

After submitting this issue on GitHub, I found out that instances of the HttpClient have a connection pool and keep persistent connections by default, which keeps the Dart VM alive. Please consult the issue page to find out about the possible solutions.


Solution

  • It's probably caused by the await before the response.transform.

    You might want something like

      return response.transform(UTF8.decoder).join('');
    

    The pause is not related to makeRequest(). The Dart VM seems to wait for something before it exits. Adding exit(0); as last line in main() makes the application exit immediately.

    Update

    According to the response on the Dart SDK issue

    This is caused by the HttpClient instance having a connection pool which can keep the Dart VM alive. There are two ways of avoiding this:

    1) Close the HttpClient explicitly
    2) Use non-persistent connections

    import 'dart:async';
    import 'dart:convert' show UTF8;
    import 'dart:io';
    
    Future main() async {
      await makeRequest();
      print('end of main');
    }
    
    Future makeRequest() async {
      var client = new HttpClient();
      var request = await client.postUrl(Uri.parse('https://example.com'));
      var response = await request.close();
      var contents = await response.transform(UTF8.decoder).join();
      print(contents);
      client.close();  // Close the client.
    }
    
    import 'dart:async';
    import 'dart:convert' show UTF8;
    import 'dart:io';
    
    Future main() async {
      await makeRequest();
      print('end of main');
    }
    
    Future makeRequest() async {
      var request = await new HttpClient().postUrl(Uri.parse('https://example.com'));
      request.persistentConnection = false;  // Use non-persistent connection.
      var response = await request.close();
      var contents = await response.transform(UTF8.decoder).join();
      print(contents);
    }