fetch method
Implement this method to make real HTTP requests.
options are the request options.
requestStream is the request stream. It will not be null only when
the request body is not empty.
Use requestStream if your code rely on RequestOptions.onSendProgress.
cancelFuture corresponds to CancelToken handling.
When the request is canceled, cancelFuture will be resolved.
To await if a request has been canceled:
cancelFuture?.then((_) => print('request cancelled!'));
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.",
);
}
// Resolve before anything is allocated: a typo in `extra` is a programming
// error and should surface without a socket being opened.
final cacheMode = _cacheModeOf(options);
final token = nh.CancelToken();
// This is why the engine takes a token rather than a bare Future: dio only
// reports cancellation as a Future, and the engine needs a signal it can
// register a listener on to abort a transfer already on the wire.
unawaited(
cancelFuture?.whenComplete(() => token.cancel('cancelled by dio')),
);
_pending.add(token);
final method = _methodOf(options.method);
final request = nh.HttpRequest(
method: method,
// Carries the verb verbatim, so `PROPFIND` reaches the wire with the
// casing dio was given.
customMethod: method == nh.HttpMethod.custom ? options.method : null,
// dio has already merged its own `baseUrl` and query parameters into
// `uri`. Handing the engine the absolute URL keeps `ClientSettings
// .baseUrl` from resolving it a second time.
url: options.uri,
headers: _requestHeaders(options),
body: requestStream == null
? null
: nh.HttpBody.stream(
requestStream,
contentLength: _declaredContentLength(options),
),
// Always streamed: dio decides whether the caller wanted bytes, a string
// or JSON, and buffering here would defeat `ResponseType.stream`.
expectedBody: nh.HttpExpectedBody.stream,
options: nh.RequestOptions(
connectTimeout: _positive(options.connectTimeout),
// dio's `sendTimeout` has no separate engine deadline; libcurl times
// the whole transfer, so upload stalls are covered by this one.
timeout: _positive(options.receiveTimeout),
followRedirects: options.followRedirects,
maxRedirects: options.maxRedirects,
cacheMode: cacheMode,
),
cancelToken: token,
// No progress callbacks by design. dio wraps the request stream with its
// own send-progress transformer and the returned response stream with its
// own receive-progress handler, so wiring the engine's events here would
// fire every user callback twice. It also leaves `reportProgress` false
// on the wire, which suppresses native XFERINFO emission entirely.
);
nh.HttpStreamResponse response;
try {
response = await _client.request(request) as nh.HttpStreamResponse;
} on nh.NitroHttpException catch (error, stackTrace) {
_release(token);
throw _toDioException(error, options, stackTrace);
} on Object {
_release(token);
rethrow;
}
return ResponseBody(
_trackBody(response.body, token, options),
response.statusCode,
headers: _responseHeaders(response.headers),
isRedirect: _isRedirect(response),
// HTTP/2 and HTTP/3 dropped the reason phrase from the protocol, so the
// engine reports `''` for them. dio derives a phrase from the status code
// when this is null, which is better than handing it an empty string.
statusMessage: response.reasonPhrase.isEmpty
? null
: response.reasonPhrase,
);
}