createHttpClient function

HttpClient createHttpClient({
  1. Http2ClientTransport? transport,
})

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