| // 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; |
| } |
| } |
| } |