2018-11-08 04:11:10 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:io';
|
|
|
|
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 ?? 443,
|
|
|
|
onBadCertificate: (_) => true,
|
|
|
|
supportedProtocols: ['h2'],
|
|
|
|
);
|
|
|
|
|
2019-05-02 22:48:31 +00:00
|
|
|
var connection = ClientTransportConnection.viaSocket(socket);
|
2018-11-08 04:11:10 +00:00
|
|
|
|
|
|
|
var headers = <Header>[
|
2019-05-02 22:48:31 +00:00
|
|
|
Header.ascii(':authority', request.url.authority),
|
|
|
|
Header.ascii(':method', request.method),
|
|
|
|
Header.ascii(
|
2019-02-03 18:00:42 +00:00
|
|
|
':path',
|
|
|
|
request.url.path +
|
|
|
|
(request.url.hasQuery ? ('?' + request.url.query) : '')),
|
2019-05-02 22:48:31 +00:00
|
|
|
Header.ascii(':scheme', request.url.scheme),
|
2018-11-08 04:11:10 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
var bb = await request
|
|
|
|
.finalize()
|
2019-05-02 22:48:31 +00:00
|
|
|
.fold<BytesBuilder>(BytesBuilder(), (out, list) => out..add(list));
|
2018-11-08 04:11:10 +00:00
|
|
|
var body = bb.takeBytes();
|
|
|
|
|
|
|
|
if (body.isNotEmpty) {
|
2019-05-02 22:48:31 +00:00
|
|
|
headers.add(Header.ascii('content-length', body.length.toString()));
|
2018-11-08 04:11:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
request.headers.forEach((k, v) {
|
2019-05-02 22:48:31 +00:00
|
|
|
headers.add(Header.ascii(k, v));
|
2018-11-08 04:11:10 +00:00
|
|
|
});
|
|
|
|
|
2018-12-09 15:49:59 +00:00
|
|
|
var stream = await connection.makeRequest(headers, endStream: body.isEmpty);
|
2018-11-08 04:11:10 +00:00
|
|
|
|
|
|
|
if (body.isNotEmpty) {
|
|
|
|
stream.sendData(body, endStream: true);
|
|
|
|
} else {
|
2020-02-05 23:02:46 +00:00
|
|
|
(stream.outgoingMessages.close());
|
2018-11-08 04:11:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if the response stream was closed.
|
|
|
|
static Future<bool> readResponse(ClientTransportStream stream,
|
|
|
|
Map<String, String> headers, BytesBuilder body) {
|
2019-05-02 22:48:31 +00:00
|
|
|
var c = Completer<bool>();
|
2018-11-08 04:11:10 +00:00
|
|
|
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>{};
|
2019-05-02 22:48:31 +00:00
|
|
|
var body = BytesBuilder();
|
2018-11-08 04:11:10 +00:00
|
|
|
var closed = await readResponse(stream, headers, body);
|
2019-05-02 22:48:31 +00:00
|
|
|
return StreamedResponse(
|
|
|
|
Stream.fromIterable([body.takeBytes()]),
|
2021-03-20 08:11:18 +00:00
|
|
|
int.parse(headers[':status']!),
|
2018-11-08 04:11:10 +00:00
|
|
|
headers: headers,
|
|
|
|
isRedirect: headers.containsKey('location'),
|
|
|
|
contentLength: headers.containsKey('content-length')
|
2021-03-20 08:11:18 +00:00
|
|
|
? int.parse(headers['content-length']!)
|
2018-11-08 04:11:10 +00:00
|
|
|
: null,
|
|
|
|
request: request,
|
|
|
|
reasonPhrase: null,
|
|
|
|
// doesn't exist in HTTP/2
|
|
|
|
persistentConnection: !closed,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|