fetch method

  1. @override
Future<ResponseBody> fetch(
  1. RequestOptions options,
  2. Stream<Uint8List>? requestStream,
  3. Future? cancelFuture
)
override

We should implement this method to make real http requests.

options: The request options

requestStream The request stream, It will not be null only when http method is one of "POST","PUT","PATCH" and the request body is not empty.

We should give priority to using requestStream(not options.data) as request data. because supporting stream ensures the onSendProgress works.

cancelFuture: When cancelled the request, cancelFuture will be resolved! you can listen cancel event by it, for example:

 cancelFuture?.then((_)=>print("request cancelled!"))

cancelFuture: will be null when the request is not set CancelToken.

Implementation

@override
Future<ResponseBody> fetch(
  RequestOptions options,
  Stream<Uint8List>? requestStream,
  Future? cancelFuture,
) async {
  if (_closed) {
    throw Exception(
        "Can't establish connection after [HttpClientAdapter] closed!");
  }
  var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
  var reqFuture = _httpClient.openUrl(options.method, options.uri);

  void _throwConnectingTimeout() {
    throw DioError(
      requestOptions: options,
      error: 'Connecting timed out [${options.connectTimeout}ms]',
      type: DioErrorType.connectTimeout,
    );
  }

  late HttpClientRequest request;
  try {
    request = await reqFuture;
    if (options.connectTimeout > 0) {
      request = await reqFuture
          .timeout(Duration(milliseconds: options.connectTimeout));
    } else {
      request = await reqFuture;
    }

    //Set Headers
    options.headers.forEach((k, v) {
      if (v != null) request.headers.set(k, '$v');
    });
  } on SocketException catch (e) {
    if (e.message.contains('timed out')) {
      _throwConnectingTimeout();
    }
    rethrow;
  } on TimeoutException {
    _throwConnectingTimeout();
  }

  request.followRedirects = options.followRedirects;
  request.maxRedirects = options.maxRedirects;

  if (requestStream != null) {
    // Transform the request data
    var future = request.addStream(requestStream);
    if (options.sendTimeout > 0) {
      future = future.timeout(Duration(milliseconds: options.sendTimeout));
    }
    try {
      await future;
    } on TimeoutException {
      request.abort();
      throw DioError(
        requestOptions: options,
        error: 'Sending timeout[${options.sendTimeout}ms]',
        type: DioErrorType.sendTimeout,
      );
    }
  }

  // [receiveTimeout] represents a timeout during data transfer! That is to say the
  // client has connected to the server.
  int receiveStart = DateTime.now().millisecondsSinceEpoch;
  var future = request.close();
  if (options.receiveTimeout > 0) {
    future = future.timeout(Duration(milliseconds: options.receiveTimeout));
  }
  late HttpClientResponse responseStream;
  try {
    responseStream = await future;
  } on TimeoutException {
    throw DioError(
      requestOptions: options,
      error: 'Receiving data timeout[${options.receiveTimeout}ms]',
      type: DioErrorType.receiveTimeout,
    );
  }

  var stream =
      responseStream.transform<Uint8List>(StreamTransformer.fromHandlers(
    handleData: (data, sink) {
      if (options.receiveTimeout > 0 &&
          DateTime.now().millisecondsSinceEpoch - receiveStart >
              options.receiveTimeout) {
        sink.addError(
          DioError(
            requestOptions: options,
            error: 'Receiving data timeout[${options.receiveTimeout}ms]',
            type: DioErrorType.receiveTimeout,
          ),
        );
        responseStream.detachSocket().then((socket) => socket.destroy());
      } else {
        sink.add(Uint8List.fromList(data));
      }
    },
  ));

  var headers = <String, List<String>>{};
  responseStream.headers.forEach((key, values) {
    headers[key] = values;
  });
  return ResponseBody(
    stream,
    responseStream.statusCode,
    headers: headers,
    isRedirect:
        responseStream.isRedirect || responseStream.redirects.isNotEmpty,
    redirects: responseStream.redirects
        .map((e) => RedirectRecord(e.statusCode, e.method, e.location))
        .toList(),
    statusMessage: responseStream.reasonPhrase,
  );
}