Reusable Sync HttpClient Request

2k Views Asked by At

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.

1

There are 1 best solutions below

8
On BEST ANSWER

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);
}