streamedAjax function
Sends a request with a streamed body and returns the response.
The streaming counterpart to ajax: instead of buffering the
request body up front, the caller writes bytes into the
EventSink passed to send (e.g. by piping an inbound HTTP
request stream via package:stream's copyToSink). The sink is
closed automatically when send returns or throws.
HEAD is not supported — use headAjax instead. This function
materializes the response via http.Response.fromStream, which
would lose the wire's Content-Length for HEAD (the body is empty
by definition); headAjax handles this case correctly.
-
contentLength— when known up front, set it so the outbound request is sent fixed-length. If omitted, the request is sentTransfer-Encoding: chunked(some endpoints, e.g. S3 PUT under SigV2, reject chunked bodies). -
timeout— bounds the entire request. If exceeded, the underlying HttpClient is force-closed and TimeoutException is thrown.
Implementation
Future<http.Response> streamedAjax(Uri url,
Future Function(EventSink<List<int>> sink) send,
{String method = "GET", Map<String, String>? headers,
int? contentLength, Duration? timeout}) {
if (method.toUpperCase() == "HEAD")
throw ArgumentError('HEAD: use headAjax');
final httpClient = HttpClient();
final client = http_io.IOClient(httpClient);
Future<http.Response> doIt() async {
try {
final request = http.StreamedRequest(method, url);
headers?.forEach((k, v) {
//Promote `Content-Length` to `.contentLength`; left in the map
//alone the request goes out as chunked.
if (k.toLowerCase() == 'content-length') {
assert(int.tryParse(v) != null, 'Content-Length: $v');
request.contentLength ??= int.tryParse(v);
} else {
request.headers[k] = v;
}
});
if (contentLength != null) request.contentLength = contentLength;
//Drive the body before awaiting the response: `client.send` only
//resolves after the body stream emits done.
final responseFuture = client.send(request);
responseFuture.ignore();
try {
await send(request.sink);
} finally {
request.sink.close();
}
return await http.Response.fromStream(await responseFuture);
} finally {
InvokeUtil.invokeSafely(client.close);
}
}
final inner = doIt();
if (timeout == null) return inner;
return inner.timeout(timeout, onTimeout: () {
httpClient.close(force: true);
inner.ignore();
throw TimeoutException('streamedAjax($method $url)', timeout);
});
}