106 lines
3 KiB
Dart
106 lines
3 KiB
Dart
|
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<ClientTransportStream> 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>[
|
||
|
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>(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<bool> readResponse(ClientTransportStream stream,
|
||
|
Map<String, String> headers, BytesBuilder body) {
|
||
|
var c = Completer<bool>();
|
||
|
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<StreamedResponse> send(BaseRequest request) async {
|
||
|
var stream = await convertRequestToStream(request);
|
||
|
var headers = <String, String>{};
|
||
|
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,
|
||
|
);
|
||
|
}
|
||
|
}
|