import 'dart:async'; import 'dart:convert'; import 'dart:io' hide BytesBuilder; import 'dart:typed_data'; import 'package:http/http.dart'; import 'package:http2/transport.dart'; /// Simple HTTP/2 client class Http2Client extends BaseClient { static Future convertRequestToStream( BaseRequest request) async { // Connect a socket var socket = await SecureSocket.connect( request.url.host, request.url.port, onBadCertificate: (_) => true, supportedProtocols: ['h2'], ); var connection = ClientTransportConnection.viaSocket(socket); var headers =
[ Header.ascii(':authority', request.url.authority), Header.ascii(':method', request.method), Header.ascii( ':path', request.url.path + (request.url.hasQuery ? ('?${request.url.query}') : '')), Header.ascii(':scheme', request.url.scheme), ]; var bb = await request .finalize() .fold(BytesBuilder(), (out, list) => out..add(list)); var body = bb.takeBytes(); if (body.isNotEmpty) { headers.add(Header.ascii('content-length', body.length.toString())); } request.headers.forEach((k, v) { headers.add(Header.ascii(k, v)); }); var stream = connection.makeRequest(headers, endStream: body.isEmpty); if (body.isNotEmpty) { stream.sendData(body, endStream: true); } else { await (stream.outgoingMessages.close()); } return stream; } /// Returns `true` if the response stream was closed. static Future readResponse(ClientTransportStream stream, Map headers, BytesBuilder body) { var c = Completer(); var closed = false; stream.incomingMessages.listen( (msg) { if (msg is HeadersStreamMessage) { for (var header in msg.headers) { var name = ascii.decode(header.name).toLowerCase(), value = ascii.decode(header.value); headers[name] = value; //print('$name: $value'); } } else if (msg is DataStreamMessage) { body.add(msg.bytes); } if (!closed && msg.endStream) closed = true; }, cancelOnError: true, onError: c.completeError, onDone: () => c.complete(closed), ); return c.future; } @override Future send(BaseRequest request) async { var stream = await convertRequestToStream(request); var headers = {}; var body = BytesBuilder(); var closed = await readResponse(stream, headers, body); return StreamedResponse( Stream.fromIterable([body.takeBytes()]), int.parse(headers[':status']!), headers: headers, isRedirect: headers.containsKey('location'), contentLength: headers.containsKey('content-length') ? int.parse(headers['content-length']!) : null, request: request, reasonPhrase: null, // doesn't exist in HTTP/2 persistentConnection: !closed, ); } }