ajax function
Sends an Ajax request to the given url.
Returns the data received, or null on error.
To send data in List<int>, pass it via data.
To send in String, pass it via body.
onResponseused to retrieve the status code, the response's headers, or force ajax to return the body. Ignored if not specified.
Notice that, by default, ajax returns null if the status code is not between 200 and 299 (see isHttpStatusOK). That is, by default, ajax ignores the body if the status code indicates an error.
If the error description will be in the request's body, you have
to specify onResponse with a callback returning true.
On the other hand, if onResponse returns false, ajax will ignore
the body, and returns null.
If onResponse returns null, it is handled as default (as described
above).
timeoutbounds the entire request — connect, send, response, and body read. If exceeded, the underlying HttpClient is force-closed (releasing the socket immediately) and TimeoutException is thrown. If null (default), there is no timeout.
Implementation
Future<List<int>?> ajax(Uri url, {String method = "GET",
List<int>? data, String? body, Map<String, String>? headers,
bool? Function(HttpClientResponse response)? onResponse,
Duration? timeout}) {
assert(data == null || body == null,
'pass either `data` or `body`, not both');
final client = HttpClient();
Future<List<int>?> doIt() async {
try {
final xhr = await client.openUrl(method, url);
//Use `set` (not `add`) so caller-supplied headers replace Dart's
//defaults, matching package:http's IOClient.send.
headers?.forEach(xhr.headers.set);
//Send fixed-length (matches package:http) instead of
//`Transfer-Encoding: chunked`, which some endpoints (e.g. S3 PUT
//under SigV2) reject. Length is always known: 0 when no body.
final List<int> bytes = data
?? (body != null ? utf8.encode(body): const <int>[]);
xhr.contentLength = bytes.length;
if (bytes.isNotEmpty) xhr.add(bytes);
final resp = await xhr.close(),
statusCode = resp.statusCode;
if (!(onResponse?.call(resp) ?? isHttpStatusOK(statusCode))) {
// Always discard the body so the connection can finish cleanly.
await resp.drain();
return null;
}
// Read the whole body.
final result = BytesBuilder(copy: false);
await for (final chunk in resp) {
result.add(chunk);
}
return result.takeBytes();
} finally {
InvokeUtil.invokeSafely(client.close);
}
}
final inner = doIt();
if (timeout == null) return inner;
return inner.timeout(timeout, onTimeout: () {
//force-close so the in-flight socket is released immediately
//(otherwise the connection would linger until the OS-level TCP timeout).
client.close(force: true);
//after force-close, `inner` will error out late — suppress it
inner.ignore();
throw TimeoutException('ajax($method $url)', timeout);
});
}