blob: 590b831e884ce531ad925a451858224f70ac2aa5 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'base_client.dart';
import 'base_request.dart';
import 'exception.dart';
import 'io_streamed_response.dart';
/// Create an [IOClient].
///
/// Used from conditional imports, matches the definition in `client_stub.dart`.
BaseClient createClient() => IOClient();
/// A `dart:io`-based HTTP client.
class IOClient extends BaseClient {
/// The underlying `dart:io` HTTP client.
HttpClient? _inner;
IOClient([HttpClient? inner]) : _inner = inner ?? HttpClient();
/// Sends an HTTP request and asynchronously returns the response.
@override
Future<IOStreamedResponse> send(BaseRequest request,
{Duration? contentTimeout}) {
final completer = Completer<IOStreamedResponse>();
_send(request, contentTimeout, completer);
return completer.future;
}
Future<void> _send(BaseRequest request, Duration? contentTimeout,
Completer<IOStreamedResponse> completer) async {
var stream = request.finalize();
Timer? timer;
void Function() onTimeout;
if (contentTimeout != null) {
onTimeout = () {
if (!completer.isCompleted) {
completer.completeError(
TimeoutException('Request aborted', contentTimeout));
}
};
timer = Timer(contentTimeout, () {
onTimeout();
});
}
try {
var ioRequest = (await _inner!.openUrl(request.method, request.url))
..followRedirects = request.followRedirects
..maxRedirects = request.maxRedirects
..contentLength = (request.contentLength ?? -1)
..persistentConnection = request.persistentConnection;
if (completer.isCompleted) return;
request.headers.forEach((name, value) {
ioRequest.headers.set(name, value);
});
if (contentTimeout != null) {
onTimeout = () {
ioRequest.abort();
if (!completer.isCompleted) {
completer.completeError(
TimeoutException('Request aborted', contentTimeout));
}
};
}
var response = await stream.pipe(ioRequest) as HttpClientResponse;
if (completer.isCompleted) return;
var headers = <String, String>{};
response.headers.forEach((key, values) {
headers[key] = values.join(',');
});
var wasTimedOut = false;
if (contentTimeout != null) {
onTimeout = () {
wasTimedOut = true;
response.detachSocket().then((socket) => socket.destroy());
};
}
var responseStream = response.handleError((error) {
final httpException = error as HttpException;
throw ClientException(httpException.message, httpException.uri);
}, test: (error) => error is HttpException).transform<List<int>>(
StreamTransformer.fromHandlers(handleDone: (sink) {
timer?.cancel();
if (wasTimedOut) {
sink.addError(TimeoutException('Request aborted', contentTimeout));
}
sink.close();
}));
if (!completer.isCompleted) {
completer.complete(IOStreamedResponse(
responseStream, response.statusCode,
contentLength:
response.contentLength == -1 ? null : response.contentLength,
request: request,
headers: headers,
isRedirect: response.isRedirect,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase,
inner: response));
}
} on HttpException catch (error) {
if (completer.isCompleted) return;
completer.completeError(ClientException(error.message, error.uri));
} catch (error, stackTrace) {
if (completer.isCompleted) return;
completer.completeError(error, stackTrace);
}
}
/// Closes the client.
///
/// Terminates all active connections. If a client remains unclosed, the Dart
/// process may not terminate.
@override
void close() {
if (_inner != null) {
_inner!.close(force: true);
_inner = null;
}
}
}