createHttpClient function
Creates a HttpClient based on package:http2
This only supports HTTP/2.
Implementation
HttpClient createHttpClient({Http2ClientTransport? transport}) {
// Using a new variable will ensure it is inferred as
// a non nullable type.
final h2transport = transport ?? Http2ClientTransport();
return (req) async {
final uri = Uri.parse(req.url);
final sentinel = Sentinel.create();
final stream = await h2transport.makeRequest(uri, [
http2.Header.ascii(':method', req.method),
http2.Header.ascii(':scheme', uri.scheme),
http2.Header.ascii(':authority', uri.host),
http2.Header.ascii(
':path',
[uri.path, if (uri.hasQuery) uri.query].join("?"),
),
for (final header in req.header.entries)
http2.Header.ascii(header.name, header.value)
]);
req.signal?.future.then((err) {
sentinel.reject(err);
stream.terminate();
}).ignore();
stream.onTerminated = (code) {
if (code == null || code == 0) {
// Ignore RST_STREAM NO_ERROR codes.
return;
}
sentinel.reject(
errFromRstCode(code),
);
};
if (req.body case Stream<Uint8List> body) {
// Write request body in parallel to the response.
body
.addAll(sentinel, stream.outgoingMessages)
.catchError(
(Object err) => sentinel.reject(ConnectException.from(err)),
)
.ignore();
} else {
await stream.outgoingMessages.close();
}
final headers = Headers();
final trailers = Headers();
final status = Completer<int>();
final body = stream.incomingMessages.toBytes(
sentinel,
(h2Headers) {
int? parsedStatus;
for (final header in h2Headers) {
final name = utf8.decode(header.name);
final value = utf8.decode(header.value);
if (name == ":status") {
if (parsedStatus != null) {
throw ConnectException(
Code.unknown,
'protocol error: http/2: Duplicate status $value',
);
}
parsedStatus = int.tryParse(value);
if (parsedStatus == null) {
throw ConnectException(
Code.unknown,
'protocol error: http/2: Invalid status $value',
);
}
continue;
}
headers.add(name, value);
}
if (parsedStatus == null) {
throw ConnectException(
Code.unknown,
'protocol error: http/2: Missing status code',
);
}
status.complete(parsedStatus);
},
(h2Trailers) {
for (final trailer in h2Trailers) {
final name = utf8.decode(trailer.name);
final value = utf8.decode(trailer.value);
trailers.add(name, value);
}
},
);
return HttpResponse(
await sentinel.race(status.future),
headers,
body,
trailers,
);
};
}