send method
Sends an HTTP request and asynchronously returns the response.
Implementers should call BaseRequest.finalize to get the body of the
request as a ByteStream. They shouldn't make any assumptions about the
state of the stream; it could have data written to it asynchronously at a
later point, or it could already be closed when it's returned. Any
internal HTTP errors should be wrapped as ClientExceptions.
Implementation
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
if (_isClosed) {
throw http.ClientException(
'HTTP request failed. Client is already closed.',
request.url,
);
}
if (!request.url.hasScheme || !request.url.hasAuthority) {
throw http.ClientException(
'Request URL must include a scheme and authority: ${request.url}',
request.url,
);
}
final byteStream = request.finalize();
final requestFields = _requestHeadersToFields(
_types,
request.url,
request.headers,
);
final Option<Stream<Uint8List>> contents;
if (request.contentLength == 0) {
contents = Option.none;
} else {
final bodyStream = byteStream.map(
(chunk) => chunk is Uint8List ? chunk : Uint8List.fromList(chunk),
);
contents = Option.some(bodyStream);
}
final (wasiRequest, transmitFuture) = _types.staticRequestNew(
headers: requestFields,
contents: contents,
trailers: Future.syncValue(const Result.ok(Option.none)),
options: Option.none,
);
transmitFuture.ignore();
final reqBorrow = wasiRequest.borrow();
if (_types.methodRequestSetMethod(
self: reqBorrow,
method: _toWasiMethod(request.method),
)
case ErrorResult()) {
wasiRequest.drop();
throw http.ClientException(
'Invalid HTTP method: ${request.method}',
request.url,
);
}
if (_types.methodRequestSetScheme(
self: reqBorrow,
scheme: Option.some(_toWasiScheme(request.url.scheme)),
)
case ErrorResult()) {
wasiRequest.drop();
throw http.ClientException(
'Invalid URI scheme: ${request.url.scheme}',
request.url,
);
}
if (_types.methodRequestSetAuthority(
self: reqBorrow,
authority: Option.some(request.url.authority),
)
case ErrorResult()) {
wasiRequest.drop();
throw http.ClientException(
'Invalid URI authority: ${request.url.authority}',
request.url,
);
}
final path = request.url.path.isEmpty ? '/' : request.url.path;
final pathWithQuery = request.url.hasQuery
? '$path?${request.url.query}'
: path;
if (_types.methodRequestSetPathWithQuery(
self: reqBorrow,
pathWithQuery: Option.some(pathWithQuery),
)
case ErrorResult()) {
wasiRequest.drop();
throw http.ClientException(
'Invalid URI path/query: $pathWithQuery',
request.url,
);
}
final sendResult = await _client.send(request: wasiRequest);
final Owned<TypesResponse> wasiResponse;
switch (sendResult) {
case OkResult(:final value):
wasiResponse = value;
case ErrorResult(value: final error):
throw http.ClientException(
'WASI HTTP error (${_describeErrorCode(error)})',
request.url,
);
}
final respBorrow = wasiResponse.borrow();
final statusCode = _types.methodResponseGetStatusCode(self: respBorrow);
final rawHeadersOwned = _types.methodResponseGetHeaders(self: respBorrow);
final rawHeaderPairs = _types.methodFieldsCopyAll(
self: rawHeadersOwned.borrow(),
);
rawHeadersOwned.drop();
final responseHeaders = <String, String>{};
for (final (name, bytes) in rawHeaderPairs) {
final key = name.toLowerCase();
final value = utf8.decode(bytes, allowMalformed: true);
final existing = responseHeaders[key];
responseHeaders[key] = existing == null ? value : '$existing, $value';
}
final responseResCompleter = Completer<Result<void, TypesErrorCode>>();
final (rawBodyStream, responseTrailers) = _types.staticResponseConsumeBody(
$this: wasiResponse,
res: responseResCompleter.future,
);
void finishResponse() {
if (!responseResCompleter.isCompleted) {
responseResCompleter.complete(const Result.ok(null));
}
}
final responseBodyStream = _trackStreamCompletion(
rawBodyStream,
responseTrailers,
request.url,
finishResponse,
);
final contentLengthHeader = responseHeaders['content-length'];
final contentLength = contentLengthHeader != null
? int.tryParse(contentLengthHeader)
: null;
return http.StreamedResponse(
responseBodyStream,
statusCode,
contentLength: contentLength,
request: request,
headers: responseHeaders,
);
}