fetch method

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

We should implement this method to make real http requests.

options are 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.

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<void>? cancelFuture,
) async {
  if (_closed) {
    throw StateError(
      "Can't establish connection after the adapter was closed!",
    );
  }
  final httpClient = _configHttpClient(cancelFuture, options.connectTimeout);
  final reqFuture = httpClient.openUrl(options.method, options.uri);

  late HttpClientRequest request;
  try {
    final connectionTimeout = options.connectTimeout;
    if (connectionTimeout != null) {
      request = await reqFuture.timeout(
        connectionTimeout,
        onTimeout: () {
          throw DioError.connectionTimeout(
            requestOptions: options,
            timeout: connectionTimeout,
          );
        },
      );
    } else {
      request = await reqFuture;
    }

    // Set Headers
    options.headers.forEach((k, v) {
      if (v != null) request.headers.set(k, v);
    });
  } on SocketException catch (e, stackTrace) {
    if (!e.message.contains('timed out')) {
      rethrow;
    }
    throw DioError.connectionTimeout(
      requestOptions: options,
      timeout: options.connectTimeout ??
          httpClient.connectionTimeout ??
          Duration.zero,
      error: e,
      stackTrace: stackTrace,
    );
  }

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

  if (requestStream != null) {
    // Transform the request data.
    Future<dynamic> future = request.addStream(requestStream);
    final sendTimeout = options.sendTimeout;
    if (sendTimeout != null) {
      future = future.timeout(
        sendTimeout,
        onTimeout: () {
          request.abort();
          throw DioError.sendTimeout(
            timeout: sendTimeout,
            requestOptions: options,
          );
        },
      );
    }
    await future;
  }

  final stopwatch = Stopwatch()..start();
  Future<HttpClientResponse> future = request.close();
  final receiveTimeout = options.receiveTimeout;
  if (receiveTimeout != null) {
    future = future.timeout(
      receiveTimeout,
      onTimeout: () {
        throw DioError.receiveTimeout(
          timeout: receiveTimeout,
          requestOptions: options,
        );
      },
    );
  }

  final responseStream = await future;

  if (validateCertificate != null) {
    final host = options.uri.host;
    final port = options.uri.port;
    final bool isCertApproved = validateCertificate!(
      responseStream.certificate,
      host,
      port,
    );
    if (!isCertApproved) {
      throw DioError(
        requestOptions: options,
        type: DioErrorType.badCertificate,
        error: responseStream.certificate,
        message: 'The certificate of the response is not approved.',
      );
    }
  }

  final stream = responseStream.transform<Uint8List>(
    StreamTransformer.fromHandlers(handleData: (data, sink) {
      stopwatch.stop();
      final duration = stopwatch.elapsed;
      final receiveTimeout = options.receiveTimeout;
      if (receiveTimeout != null && duration > receiveTimeout) {
        sink.addError(
          DioError.receiveTimeout(
            timeout: receiveTimeout,
            requestOptions: options,
          ),
        );
        responseStream.detachSocket().then((socket) => socket.destroy());
      } else {
        sink.add(Uint8List.fromList(data));
      }
    }),
  );

  final 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,
  );
}