diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index 674bc7bf9..173ef64af 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -12,6 +12,7 @@ See the [Migration Guide][] for the complete breaking changes list.** - Remove `http` from `dev_dependencies`. - Add support for cloning `MultipartFile` from `FormData`. - Only produce null response body when `ResponseType.json`. +- Improve comments. ## 5.2.1+1 diff --git a/dio/README-ZH.md b/dio/README-ZH.md index ae98a093a..74eb83a7e 100644 --- a/dio/README-ZH.md +++ b/dio/README-ZH.md @@ -389,6 +389,8 @@ print(response.requestOptions); print(response.statusCode); ``` +注意,`Response.extra` 与 `RequestOptions.extra` 是不同的实例,互相之间无关。 + ### 拦截器 每个 Dio 实例都可以添加任意多个拦截器,他们会组成一个队列,拦截器队列的执行顺序是先进先出。 diff --git a/dio/README.md b/dio/README.md index c877d65ed..f5bcb66b3 100644 --- a/dio/README.md +++ b/dio/README.md @@ -345,8 +345,8 @@ ResponseType? responseType; /// the request will be perceived as successful; otherwise, considered as failed. ValidateStatus? validateStatus; -/// Custom field that you can retrieve it later in -/// [Interceptor], [Transformer] and the [Response] object. +/// Custom field that you can retrieve it later in [Interceptor], +/// [Transformer] and the [Response.requestOptions] object. Map? extra; /// Common query parameters. @@ -391,7 +391,7 @@ bool isRedirect; /// implementation of the adapter supports it or not. List redirects; -/// Custom fields that are constructed in the [RequestOptions]. +/// Custom fields that only for the [Response]. Map extra; /// Response headers. @@ -408,6 +408,9 @@ print(response.requestOptions); print(response.statusCode); ``` +Be aware, the `Response.extra` is different from `RequestOptions.extra`, +they are not related to each other. + ### Interceptors For each dio instance, we can add one or more interceptors, diff --git a/dio/lib/dio.dart b/dio/lib/dio.dart index aead831ef..1a4a5bafa 100644 --- a/dio/lib/dio.dart +++ b/dio/lib/dio.dart @@ -1,6 +1,6 @@ -/// A powerful Http client for Dart, which supports Interceptors, -/// Global configuration, FormData, File downloading etc. and Dio is -/// very easy to use. +/// A powerful HTTP client for Dart and Flutter, which supports global settings, +/// [Interceptors], [FormData], aborting and canceling a request, +/// files uploading and downloading, requests timeout, custom adapters, etc. library dio; export 'src/adapter.dart'; diff --git a/dio/lib/src/adapter.dart b/dio/lib/src/adapter.dart index da5755591..4cb52602f 100644 --- a/dio/lib/src/adapter.dart +++ b/dio/lib/src/adapter.dart @@ -19,84 +19,87 @@ import 'adapters/io_adapter.dart' /// If you want to customize the [HttpClientAdapter] you should instead use /// either [IOHttpClientAdapter] on `dart:io` platforms /// or [BrowserHttpClientAdapter] on `dart:html` platforms. -/// -/// ```dart -/// dio.httpClientAdapter = HttpClientAdapter(); -/// ``` abstract class HttpClientAdapter { + /// Create a [HttpClientAdapter] based on the current platform (IO/Web). factory HttpClientAdapter() => adapter.createAdapter(); - /// We should implement this method to make real http requests. + /// 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. + /// [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]. /// - /// When cancelled the request, [cancelFuture] will be resolved! - /// you can listen cancel event by it, for example: + /// [cancelFuture] will be null when the [CancelToken] + /// is not set [CancelToken] for the request. /// + /// When the request is cancelled, [cancelFuture] will be resolved. + /// The adapter can listen cancel event like: /// ```dart - /// cancelFuture?.then((_)=>print("request cancelled!")) + /// cancelFuture?.then((_)=>print("request cancelled!")) /// ``` - /// [cancelFuture] will be null when the request is not set [CancelToken]. Future fetch( RequestOptions options, Stream? requestStream, Future? cancelFuture, ); + /// Close the current adapter and its inner clients or requests. void close({bool force = false}); } +/// The response wrapper class for adapters. +/// +/// This class should not be used in regular usages. class ResponseBody { ResponseBody( this.stream, this.statusCode, { - this.headers = const {}, this.statusMessage, this.isRedirect = false, this.redirects, - }); - - /// The response stream. - Stream stream; - - /// The response headers. - Map> headers; - - /// HTTP status code. - int statusCode; - - /// Returns the reason phrase associated with the status code. - /// The reason phrase must be set before the body is written - /// to. Setting the reason phrase after writing to the body. - String? statusMessage; - - /// Whether this response is a redirect. - final bool isRedirect; - - List? redirects; - - Map extra = {}; + Map>? headers, + }) : headers = headers ?? {}; ResponseBody.fromString( String text, this.statusCode, { - this.headers = const {}, this.statusMessage, this.isRedirect = false, - }) : stream = Stream.value(Uint8List.fromList(utf8.encode(text))); + Map>? headers, + }) : stream = Stream.value(Uint8List.fromList(utf8.encode(text))), + headers = headers ?? {}; ResponseBody.fromBytes( List bytes, this.statusCode, { - this.headers = const {}, this.statusMessage, this.isRedirect = false, - }) : stream = Stream.value(Uint8List.fromList(bytes)); + Map>? headers, + }) : stream = Stream.value(Uint8List.fromList(bytes)), + headers = headers ?? {}; + + /// Whether this response is a redirect. + final bool isRedirect; + + /// The response stream. + Stream stream; + + /// HTTP status code. + int statusCode; + + /// Returns the reason phrase corresponds to the status code. + /// The message can be [HttpRequest.statusText] + /// or [HttpClientResponse.reasonPhrase]. + String? statusMessage; + + /// Stores redirections during the request. + List? redirects; + + /// The response headers. + Map> headers; + + /// The extra field which will pass-through to the [Response.extra]. + Map extra = {}; } diff --git a/dio/lib/src/adapters/browser_adapter.dart b/dio/lib/src/adapters/browser_adapter.dart index 9d7ad7e5f..5d8b2634d 100644 --- a/dio/lib/src/adapters/browser_adapter.dart +++ b/dio/lib/src/adapters/browser_adapter.dart @@ -4,6 +4,7 @@ import 'dart:html'; import 'dart:typed_data'; import 'package:meta/meta.dart'; + import '../adapter.dart'; import '../dio_exception.dart'; import '../headers.dart'; @@ -24,7 +25,8 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { /// /// Defaults to `false`. /// - /// You can also override this value in Options.extra['withCredentials'] for each request + /// You can also override this value using `Options.extra['withCredentials']` + /// for each request. bool withCredentials; @override @@ -201,9 +203,6 @@ class BrowserHttpClientAdapter implements HttpClientAdapter { try { xhr.abort(); } catch (_) {} - // xhr.onError will not triggered when xhr.abort() called. - // so need to manual throw the cancel error to avoid Future hang ups. - // or added xhr.onAbort like axios did https://github.com/axios/axios/blob/master/lib/adapters/xhr.js#L102-L111 if (!completer.isCompleted) { completer.completeError( DioException.requestCancelled( diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart index 8d3ad2128..b56b42018 100644 --- a/dio/lib/src/adapters/io_adapter.dart +++ b/dio/lib/src/adapters/io_adapter.dart @@ -12,18 +12,22 @@ import '../redirect_record.dart'; @Deprecated('Use IOHttpClientAdapter instead. This will be removed in 6.0.0') typedef DefaultHttpClientAdapter = IOHttpClientAdapter; +/// The signature of [IOHttpClientAdapter.onHttpClientCreate]. @Deprecated('Use CreateHttpClient instead. This will be removed in 6.0.0') typedef OnHttpClientCreate = HttpClient? Function(HttpClient client); +/// The signature of [IOHttpClientAdapter.createHttpClient]. /// Can be used to provide a custom [HttpClient] for Dio. typedef CreateHttpClient = HttpClient Function(); +/// The signature of [IOHttpClientAdapter.validateCertificate]. typedef ValidateCertificate = bool Function( X509Certificate? certificate, String host, int port, ); +/// Creates an [IOHttpClientAdapter]. HttpClientAdapter createAdapter() => IOHttpClientAdapter(); /// The default [HttpClientAdapter] for native platforms. @@ -53,7 +57,6 @@ class IOHttpClientAdapter implements HttpClientAdapter { ValidateCertificate? validateCertificate; HttpClient? _cachedHttpClient; - bool _closed = false; @override @@ -64,7 +67,7 @@ class IOHttpClientAdapter implements HttpClientAdapter { ) async { if (_closed) { throw StateError( - "Can't establish connection after the adapter was closed!", + "Can't establish connection after the adapter was closed.", ); } final operation = CancelableOperation.fromFuture(_fetch( diff --git a/dio/lib/src/cancel_token.dart b/dio/lib/src/cancel_token.dart index 292018ab8..2dfa656ad 100644 --- a/dio/lib/src/cancel_token.dart +++ b/dio/lib/src/cancel_token.dart @@ -3,12 +3,12 @@ import 'dart:async'; import 'dio_exception.dart'; import 'options.dart'; -/// An instance which controls cancellation of [Dio]'s requests, -/// build from [Completer]. +/// {@template dio.CancelToken} +/// Controls cancellation of [Dio]'s requests. /// -/// You can cancel requests by using a [CancelToken]. -/// One token can be shared with different requests. -/// When [cancel] is invoked, all requests using this token will be cancelled. +/// The same token can be shared between different requests. +/// When [cancel] is invoked, requests bound to this token will be cancelled. +/// {@endtemplate} class CancelToken { CancelToken(); @@ -22,6 +22,9 @@ class CancelToken { DioException? get cancelError => _cancelError; DioException? _cancelError; + /// Corresponding request options for the request. + /// + /// This field can be null if the request was never submitted. RequestOptions? requestOptions; /// Whether the token is cancelled. diff --git a/dio/lib/src/dio.dart b/dio/lib/src/dio.dart index 18ace8173..a782cad6a 100644 --- a/dio/lib/src/dio.dart +++ b/dio/lib/src/dio.dart @@ -11,43 +11,46 @@ import 'response.dart'; import 'dio/dio_for_native.dart' if (dart.library.html) 'dio/dio_for_browser.dart'; -/// A powerful Http client for Dart, which supports Interceptors, -/// Global configuration, FormData, File downloading etc. and Dio is -/// very easy to use. +/// Dio enables you to make HTTP requests easily. /// -/// You can create a dio instance and config it by two ways: -/// 1. create first , then config it -/// -/// ```dart -/// final dio = Dio(); -/// dio.options.baseUrl = "http://www.dtworkroom.com/doris/1/2.0.0/"; -/// dio.options.connectTimeout = const Duration(seconds: 5); -/// dio.options.receiveTimeout = const Duration(seconds: 5); -/// dio.options.headers = {HttpHeaders.userAgentHeader: 'dio', 'common-header': 'xx'}; -/// ``` -/// 2. create and config it: +/// Creating a [Dio] instance with configurations: +/// ```dart +/// final dio = Dio( +/// BaseOptions( +/// baseUrl: "https://pub.dev", +/// connectTimeout: const Duration(seconds: 5), +/// receiveTimeout: const Duration(seconds: 5), +/// headers: { +/// HttpHeaders.userAgentHeader: 'dio', +/// 'common-header': 'xx', +/// }, +/// ) +/// ); +/// ``` /// +/// The [Dio.options] can be updated in anytime: /// ```dart -/// final dio = Dio(BaseOptions( -/// baseUrl: "http://www.dtworkroom.com/doris/1/2.0.0/", -/// connectTimeout: const Duration(seconds: 5), -/// receiveTimeout: const Duration(seconds: 5), -/// headers: {HttpHeaders.userAgentHeader: 'dio', 'common-header': 'xx'}, -/// )); -/// ``` - +/// dio.options.baseUrl = "https://pub.dev"; +/// dio.options.connectTimeout = const Duration(seconds: 5); +/// dio.options.receiveTimeout = const Duration(seconds: 5); +/// ``` abstract class Dio { + /// Create the default [Dio] instance with the default implementation + /// based on different platforms. factory Dio([BaseOptions? options]) => createDio(options); /// Default Request config. More see [BaseOptions] . late BaseOptions options; + /// Return the interceptors added into the instance. Interceptors get interceptors; + /// The adapter that the instance is using. late HttpClientAdapter httpClientAdapter; - /// [transformer] allows changes to the request/response data before it is sent/received to/from the server - /// This is only applicable for request methods 'PUT', 'POST', and 'PATCH'. + /// [Transformer] allows changes to the request/response data before it is + /// sent/received to/from the server. + /// This is only applicable for requests that have payload. late Transformer transformer; /// Shuts down the dio client. @@ -60,48 +63,44 @@ abstract class Dio { /// calling [close] will throw an exception. void close({bool force = false}); - /// Handy method to make http GET request, which is a alias of [dio.fetch(RequestOptions)]. - Future> get( + /// Convenience method to make an HTTP HEAD request. + Future> head( String path, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, - ProgressCallback? onReceiveProgress, }); - /// Handy method to make http GET request, which is a alias of [dio.fetch(RequestOptions)]. - Future> getUri( + /// Convenience method to make an HTTP HEAD request with [Uri]. + Future> headUri( Uri uri, { Object? data, Options? options, CancelToken? cancelToken, - ProgressCallback? onReceiveProgress, }); - /// Handy method to make http POST request, which is a alias of [dio.fetch(RequestOptions)]. - Future> post( + /// Convenience method to make an HTTP GET request. + Future> get( String path, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, - ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }); - /// Handy method to make http POST request, which is a alias of [dio.fetch(RequestOptions)]. - Future> postUri( + /// Convenience method to make an HTTP GET request with [Uri]. + Future> getUri( Uri uri, { Object? data, Options? options, CancelToken? cancelToken, - ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, }); - /// Handy method to make http PUT request, which is a alias of [dio.fetch(RequestOptions)]. - Future> put( + /// Convenience method to make an HTTP POST request. + Future> post( String path, { Object? data, Map? queryParameters, @@ -111,8 +110,8 @@ abstract class Dio { ProgressCallback? onReceiveProgress, }); - /// Handy method to make http PUT request, which is a alias of [dio.fetch(RequestOptions)]. - Future> putUri( + /// Convenience method to make an HTTP POST request with [Uri]. + Future> postUri( Uri uri, { Object? data, Options? options, @@ -121,59 +120,63 @@ abstract class Dio { ProgressCallback? onReceiveProgress, }); - /// Handy method to make http HEAD request, which is a alias of [dio.fetch(RequestOptions)]. - Future> head( + /// Convenience method to make an HTTP PUT request. + Future> put( String path, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, }); - /// Handy method to make http HEAD request, which is a alias of [dio.fetch(RequestOptions)]. - Future> headUri( + /// Convenience method to make an HTTP PUT request with [Uri]. + Future> putUri( Uri uri, { Object? data, Options? options, CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, }); - /// Handy method to make http DELETE request, which is a alias of [dio.fetch(RequestOptions)]. - Future> delete( + /// Convenience method to make an HTTP PATCH request. + Future> patch( String path, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, }); - /// Handy method to make http DELETE request, which is a alias of [dio.fetch(RequestOptions)]. - Future> deleteUri( + /// Convenience method to make an HTTP PATCH request with [Uri]. + Future> patchUri( Uri uri, { Object? data, Options? options, CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, }); - /// Handy method to make http PATCH request, which is a alias of [dio.fetch(RequestOptions)]. - Future> patch( + /// Convenience method to make an HTTP DELETE request. + Future> delete( String path, { Object? data, Map? queryParameters, Options? options, CancelToken? cancelToken, - ProgressCallback? onSendProgress, - ProgressCallback? onReceiveProgress, }); - /// Handy method to make http PATCH request, which is a alias of [dio.fetch(RequestOptions)]. - Future> patchUri( + /// Convenience method to make an HTTP DELETE request with [Uri]. + Future> deleteUri( Uri uri, { Object? data, Options? options, CancelToken? cancelToken, - ProgressCallback? onSendProgress, - ProgressCallback? onReceiveProgress, }); /// {@template dio.Dio.download} @@ -182,23 +185,23 @@ abstract class Dio { /// /// [urlPath] is the file url. /// - /// [savePath] is the path to save the downloading file later. it can be a String or - /// a callback: - /// 1. A path with String type, eg "xs.jpg" - /// 2. A callback `String Function(Headers headers)`; for example: - /// ```dart - /// await dio.download( - /// url, - /// (Headers headers) { - /// // Extra info: redirect counts - /// print(headers.value('redirects')); - /// // Extra info: real uri - /// print(headers.value('uri')); - /// // ... - /// return (await getTemporaryDirectory()).path + 'file_name'; - /// }, - /// ); - /// ``` + /// The file will be saved to the path specified by [savePath]. + /// The following two types are accepted: + /// 1. `String`: A path, eg "xs.jpg" + /// 2. `FutureOr Function(Headers headers)`, for example: + /// ```dart + /// await dio.download( + /// url, + /// (Headers headers) { + /// // Extra info: redirect counts + /// print(headers.value('redirects')); + /// // Extra info: real uri + /// print(headers.value('uri')); + /// // ... + /// return (await getTemporaryDirectory()).path + 'file_name'; + /// }, + /// ); + /// ``` /// /// [onReceiveProgress] is the callback to listen downloading progress. /// Please refer to [ProgressCallback]. @@ -208,24 +211,26 @@ abstract class Dio { /// /// [lengthHeader] : The real size of original file (not compressed). /// When file is compressed: - /// 1. If this value is 'content-length', the `total` argument of [onReceiveProgress] will be -1 - /// 2. If this value is not 'content-length', maybe a custom header indicates the original - /// file size , the `total` argument of [onReceiveProgress] will be this header value. + /// 1. If this value is 'content-length', the `total` argument of + /// [onReceiveProgress] will be -1. + /// 2. If this value is not 'content-length', maybe a custom header indicates + /// the original file size, the `total` argument of [onReceiveProgress] + /// will be this header value. /// - /// You can also disable the compression by specifying - /// the 'accept-encoding' header value as '*' to assure the value of - /// `total` argument of [onReceiveProgress] is not -1. for example: + /// You can also disable the compression by specifying the 'accept-encoding' + /// header value as '*' to assure the value of `total` argument of + /// [onReceiveProgress] is not -1. For example: /// /// ```dart /// await dio.download( /// url, /// (await getTemporaryDirectory()).path + 'flutter.svg', /// options: Options( - /// headers: {HttpHeaders.acceptEncodingHeader: "*"}, // Disable gzip + /// headers: {HttpHeaders.acceptEncodingHeader: '*'}, // Disable gzip /// ), /// onReceiveProgress: (received, total) { /// if (total != -1) { - /// print((received / total * 100).toStringAsFixed(0) + "%"); + /// print((received / total * 100).toStringAsFixed(0) + '%'); /// } /// }, /// ); @@ -266,13 +271,9 @@ abstract class Dio { ); } - /// Make http request with options. - /// - /// [path] The url path. - /// [data] The request data - /// [options] The request options. + /// Make HTTP request with options. Future> request( - String path, { + String url, { Object? data, Map? queryParameters, CancelToken? cancelToken, @@ -281,11 +282,7 @@ abstract class Dio { ProgressCallback? onReceiveProgress, }); - /// Make http request with options. - /// - /// [uri] The uri. - /// [data] The request data - /// [options] The request options. + /// Make http request with options with [Uri]. Future> requestUri( Uri uri, { Object? data, @@ -295,5 +292,7 @@ abstract class Dio { ProgressCallback? onReceiveProgress, }); + /// The eventual method to submit requests. All callers for requests should + /// eventually go through this method. Future> fetch(RequestOptions requestOptions); } diff --git a/dio/lib/src/dio/dio_for_browser.dart b/dio/lib/src/dio/dio_for_browser.dart index 83218fde5..6b13609a5 100644 --- a/dio/lib/src/dio/dio_for_browser.dart +++ b/dio/lib/src/dio/dio_for_browser.dart @@ -1,10 +1,15 @@ import '../adapters/browser_adapter.dart'; +import '../cancel_token.dart'; import '../dio.dart'; import '../dio_mixin.dart'; +import '../headers.dart'; import '../options.dart'; +import '../response.dart'; +/// Create the [Dio] instance for Web platforms. Dio createDio([BaseOptions? options]) => DioForBrowser(options); +/// Implements features for [Dio] on Web platforms. class DioForBrowser with DioMixin implements Dio { /// Create Dio instance with default [Options]. /// It's mostly just one Dio instance in your application. @@ -12,4 +17,21 @@ class DioForBrowser with DioMixin implements Dio { this.options = options ?? BaseOptions(); httpClientAdapter = BrowserHttpClientAdapter(); } + + @override + Future download( + String urlPath, + dynamic savePath, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError = true, + String lengthHeader = Headers.contentLengthHeader, + Object? data, + Options? options, + }) { + throw UnsupportedError( + 'The download method is not available in the Web environment.', + ); + } } diff --git a/dio/lib/src/dio/dio_for_native.dart b/dio/lib/src/dio/dio_for_native.dart index 27ecb92ff..521a813e0 100644 --- a/dio/lib/src/dio/dio_for_native.dart +++ b/dio/lib/src/dio/dio_for_native.dart @@ -11,8 +11,10 @@ import '../headers.dart'; import '../options.dart'; import '../adapters/io_adapter.dart'; +/// Create the [Dio] instance for native platforms. Dio createDio([BaseOptions? baseOptions]) => DioForNative(baseOptions); +/// Implements features for [Dio] on native platforms. class DioForNative with DioMixin implements Dio { /// Create Dio instance with default [BaseOptions]. /// It is recommended that an application use only the same DIO singleton. diff --git a/dio/lib/src/dio_exception.dart b/dio/lib/src/dio_exception.dart index 2fd50644d..8e45dca6f 100644 --- a/dio/lib/src/dio_exception.dart +++ b/dio/lib/src/dio_exception.dart @@ -6,10 +6,11 @@ import 'response.dart'; typedef DioErrorType = DioExceptionType; /// [DioError] describes the exception info when a request failed. -/// Deprecated in favor of [DioException] and will be removed in future major versions. @Deprecated('Use DioException instead. This will be removed in 6.0.0') typedef DioError = DioException; +/// The exception enumeration indicates what type of exception +/// has happened during requests. enum DioExceptionType { /// Caused by a connection timeout. connectionTimeout, diff --git a/dio/lib/src/dio_mixin.dart b/dio/lib/src/dio_mixin.dart index fdc270127..f26341c57 100644 --- a/dio/lib/src/dio_mixin.dart +++ b/dio/lib/src/dio_mixin.dart @@ -24,21 +24,21 @@ import 'progress_stream/io_progress_stream.dart' part 'interceptor.dart'; abstract class DioMixin implements Dio { - /// Default Request config. More see [BaseOptions]. + /// The base request config for the instance. @override late BaseOptions options; - /// Each Dio instance has a interceptor by which you can intercept requests or responses before they are - /// handled by `then` or `catchError`. the [interceptor] field - /// contains a [RequestInterceptor] and a [ResponseInterceptor] instance. - final Interceptors _interceptors = Interceptors(); - + /// Each Dio instance has a interceptor group by which you can + /// intercept requests or responses before they are ended. @override Interceptors get interceptors => _interceptors; + final Interceptors _interceptors = Interceptors(); @override late HttpClientAdapter httpClientAdapter; + /// The default [Transformer] that transfers requests and responses + /// into corresponding content to send. @override Transformer transformer = BackgroundTransformer(); @@ -50,7 +50,6 @@ abstract class DioMixin implements Dio { httpClientAdapter.close(force: force); } - /// Handy method to make http GET request, which is a alias of [BaseDio.requestOptions]. @override Future> get( String path, { @@ -70,7 +69,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http GET request, which is a alias of [BaseDio.requestOptions]. @override Future> getUri( Uri uri, { @@ -88,7 +86,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http POST request, which is a alias of [BaseDio.requestOptions]. @override Future> post( String path, { @@ -110,7 +107,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http POST request, which is a alias of [BaseDio.requestOptions]. @override Future> postUri( Uri uri, { @@ -130,7 +126,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http PUT request, which is a alias of [BaseDio.requestOptions]. @override Future> put( String path, { @@ -152,7 +147,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http PUT request, which is a alias of [BaseDio.requestOptions]. @override Future> putUri( Uri uri, { @@ -172,7 +166,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http HEAD request, which is a alias of [BaseDio.requestOptions]. @override Future> head( String path, { @@ -190,7 +183,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http HEAD request, which is a alias of [BaseDio.requestOptions]. @override Future> headUri( Uri uri, { @@ -206,7 +198,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http DELETE request, which is a alias of [BaseDio.requestOptions]. @override Future> delete( String path, { @@ -224,7 +215,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http DELETE request, which is a alias of [BaseDio.requestOptions]. @override Future> deleteUri( Uri uri, { @@ -240,7 +230,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http PATCH request, which is a alias of [BaseDio.requestOptions]. @override Future> patch( String path, { @@ -262,7 +251,6 @@ abstract class DioMixin implements Dio { ); } - /// Handy method to make http PATCH request, which is a alias of [BaseDio.requestOptions]. @override Future> patchUri( Uri uri, { @@ -305,28 +293,6 @@ abstract class DioMixin implements Dio { ); } - @override - Future download( - String urlPath, - dynamic savePath, { - ProgressCallback? onReceiveProgress, - Map? queryParameters, - CancelToken? cancelToken, - bool deleteOnError = true, - String lengthHeader = Headers.contentLengthHeader, - Object? data, - Options? options, - }) { - throw UnsupportedError( - 'download() is not available in the current environment.', - ); - } - - /// Make http request with options. - /// - /// [uri] The uri. - /// [data] The request data - /// [options] The request options. @override Future> requestUri( Uri uri, { @@ -346,11 +312,6 @@ abstract class DioMixin implements Dio { ); } - /// Make http request with options. - /// - /// [path] The url path. - /// [data] The request data - /// [options] The request options. @override Future> request( String path, { @@ -454,7 +415,7 @@ abstract class DioMixin implements Dio { return errorHandler.future; } - // The request has already been canceled, + // The request has already been cancelled, // there is no need to listen for another cancellation. if (state.data is DioException && state.data.type == DioExceptionType.cancel) { @@ -473,13 +434,11 @@ abstract class DioMixin implements Dio { // Build a request flow in which the processors(interceptors) // execute in FIFO order. - - // Start the request flow Future future = Future( () => InterceptorState(requestOptions), ); - // Add request interceptors to request flow + // Add request interceptors into the request flow. for (final interceptor in interceptors) { final fun = interceptor is QueuedInterceptor ? interceptor._handleRequest @@ -487,7 +446,7 @@ abstract class DioMixin implements Dio { future = future.then(requestInterceptorWrapper(fun)); } - // Add dispatching callback to request flow + // Add dispatching callback into the request flow. future = future.then( requestInterceptorWrapper(( RequestOptions reqOpt, @@ -502,7 +461,7 @@ abstract class DioMixin implements Dio { }), ); - // Add response interceptors to request flow + // Add response interceptors into the request flow for (final interceptor in interceptors) { final fun = interceptor is QueuedInterceptor ? interceptor._handleResponse @@ -510,14 +469,14 @@ abstract class DioMixin implements Dio { future = future.then(responseInterceptorWrapper(fun)); } - // Add error handlers to request flow + // Add error handlers into the request flow. for (final interceptor in interceptors) { final fun = interceptor is QueuedInterceptor ? interceptor._handleError : interceptor.onError; future = future.catchError(errorInterceptorWrapper(fun)); } - // Normalize errors, we convert error to the DioException. + // Normalize errors, converts errors to [DioException]. return future.then>((data) { return assureResponse( data is InterceptorState ? data.data : data, @@ -534,7 +493,6 @@ abstract class DioMixin implements Dio { }); } - // Initiate Http requests Future> _dispatchRequest(RequestOptions reqOpt) async { final cancelToken = reqOpt.cancelToken; try { @@ -545,7 +503,7 @@ abstract class DioMixin implements Dio { cancelToken?.whenCancel, ); final headers = Headers.fromMap(responseBody.headers); - // Make sure headers and responseBody.headers point to a same Map + // Make sure headers and [ResponseBody.headers] are the same instance. responseBody.headers = headers.map; final ret = Response( headers: headers, @@ -620,7 +578,7 @@ abstract class DioMixin implements Dio { final data = options.data; if (data != null) { final Stream> stream; - // Handle the FormData + // Handle the FormData. int? length; if (data is Stream) { if (data is! Stream>) { @@ -647,22 +605,21 @@ abstract class DioMixin implements Dio { options.headers[Headers.contentLengthHeader] = length.toString(); } else { final List bytes; - if (data is Uint8List) { - // Handle binary data which does not need to be transformed + // Handle binary data which does not need to be transformed. bytes = data; } else { - // Call request transformer for anything else + // Call the request transformer. final transformed = await transformer.transformRequest(options); if (options.requestEncoder != null) { bytes = options.requestEncoder!(transformed, options); } else { - // Default convert to utf8 + // Converts the data to UTF-8 by default. bytes = utf8.encode(transformed); } } - // support data sending progress + // Allocate send progress. length = bytes.length; options.headers[Headers.contentLengthHeader] = length.toString(); @@ -680,7 +637,7 @@ abstract class DioMixin implements Dio { return null; } - // If the request has been cancelled, stop request and throw error. + // If the request has been cancelled, stop the request and throw error. @internal static void checkCancelled(CancelToken? cancelToken) { final error = cancelToken?.cancelError; @@ -713,7 +670,6 @@ abstract class DioMixin implements Dio { RequestOptions requestOptions, ) { if (err is DioException) { - // nothing to be done return err; } return DioException( @@ -757,12 +713,11 @@ abstract class DioMixin implements Dio { /// A null-check function for function parameters in Null Safety enabled code. /// -/// Because Dart does not have full null safety -/// until all legacy code has been removed from a program, -/// a non-nullable parameter can still end up with a `null` value. -/// This function can be used to guard those functions against null arguments. -/// It throws a [TypeError] because we are really seeing the failure to -/// assign `null` to a non-nullable type. +/// Because Dart does not have full null safety until all legacy code has been +/// removed from a program, a non-nullable parameter can still end up with a +/// `null` value. This function can be used to guard those functions against +/// null arguments. It throws a [TypeError] because we are really seeing +/// the failure to assign `null` to a non-nullable type. /// /// See http://dartbug.com/40614 for context. T _checkNotNullable(T value, String name) { diff --git a/dio/lib/src/form_data.dart b/dio/lib/src/form_data.dart index e3d5fd5ca..512024643 100644 --- a/dio/lib/src/form_data.dart +++ b/dio/lib/src/form_data.dart @@ -9,32 +9,7 @@ import 'utils.dart'; /// A class to create readable "multipart/form-data" streams. /// It can be used to submit forms and file uploads to http server. class FormData { - static const String _boundaryPrefix = '--dio-boundary-'; - static const int _boundaryLength = _boundaryPrefix.length + 10; - - late String _boundary; - - /// The boundary of FormData, it consists of a constant prefix and a random - /// postfix to assure the the boundary unpredictable and unique, each FormData - /// instance will be different. - String get boundary => _boundary; - - final _newlineRegExp = RegExp(r'\r\n|\r|\n'); - - /// The form fields to send for this request. - final fields = >[]; - - /// The [files]. - final files = >[]; - - /// Whether [finalize] has been called. - bool get isFinalized => _isFinalized; - bool _isFinalized = false; - final bool camelCaseContentDisposition; - - FormData({ - this.camelCaseContentDisposition = false, - }) { + FormData({this.camelCaseContentDisposition = false}) { _init(); } @@ -67,6 +42,29 @@ class FormData { random.nextInt(4294967296).toString().padLeft(10, '0'); } + static const String _boundaryPrefix = '--dio-boundary-'; + static const int _boundaryLength = _boundaryPrefix.length + 10; + + late String _boundary; + + /// The boundary of FormData, it consists of a constant prefix and a random + /// postfix to assure the the boundary unpredictable and unique, each FormData + /// instance will be different. + String get boundary => _boundary; + + final _newlineRegExp = RegExp(r'\r\n|\r|\n'); + + /// The form fields to send for this request. + final fields = >[]; + + /// The [files]. + final files = >[]; + + /// Whether [finalize] has been called. + bool get isFinalized => _isFinalized; + bool _isFinalized = false; + final bool camelCaseContentDisposition; + /// Returns the header string for a field. String _headerForField(String name, String value) { return '${camelCaseContentDisposition ? 'Content-Disposition' : 'content-disposition'}' diff --git a/dio/lib/src/headers.dart b/dio/lib/src/headers.dart index d43907cd9..42854c3e2 100644 --- a/dio/lib/src/headers.dart +++ b/dio/lib/src/headers.dart @@ -2,17 +2,25 @@ import 'package:http_parser/http_parser.dart'; import 'utils.dart'; +/// The signature that iterates header fields. typedef HeaderForEachCallback = void Function(String name, List values); +/// The headers class for requests and responses. class Headers { - // Header field name + Headers() : _map = caseInsensitiveKeyMap>(); + + /// Create the [Headers] from a [Map] instance. + Headers.fromMap(Map> map) + : _map = caseInsensitiveKeyMap>( + map.map((k, v) => MapEntry(k.trim().toLowerCase(), v)), + ); + static const acceptHeader = 'accept'; static const contentEncodingHeader = 'content-encoding'; static const contentLengthHeader = 'content-length'; static const contentTypeHeader = 'content-type'; static const wwwAuthenticateHeader = 'www-authenticate'; - // Header field value static const jsonContentType = 'application/json'; static const formUrlEncodedContentType = 'application/x-www-form-urlencoded'; static const textPlainContentType = 'text/plain'; @@ -24,13 +32,6 @@ class Headers { Map> get map => _map; - Headers() : _map = caseInsensitiveKeyMap>(); - - Headers.fromMap(Map> map) - : _map = caseInsensitiveKeyMap>( - map.map((k, v) => MapEntry(k.trim().toLowerCase(), v)), - ); - /// Returns the list of values for the header named [name]. If there /// is no header with the provided name, [:null:] will be returned. List? operator [](String name) { @@ -82,10 +83,12 @@ class Headers { _map.remove(name); } + /// Clearing all fields in the headers. void clear() { _map.clear(); } + /// Whether the headers has no fields. bool get isEmpty => _map.isEmpty; /// Enumerates the headers, applying the function [f] to each diff --git a/dio/lib/src/interceptor.dart b/dio/lib/src/interceptor.dart index 2d1682fb7..eebdceb0e 100644 --- a/dio/lib/src/interceptor.dart +++ b/dio/lib/src/interceptor.dart @@ -1,6 +1,5 @@ part of 'dio_mixin.dart'; -/// Internal enum /// @nodoc enum InterceptorResultType { next, @@ -10,7 +9,7 @@ enum InterceptorResultType { rejectCallFollowing, } -/// Internal class, It is used to pass state between current and next interceptors. +/// Used to pass state between interceptors. /// @nodoc class InterceptorState { const InterceptorState(this.data, [this.type = InterceptorResultType.next]); @@ -23,25 +22,30 @@ abstract class _BaseHandler { final _completer = Completer(); void Function()? _processNextInQueue; + @protected Future get future => _completer.future; bool get isCompleted => _completer.isCompleted; } -/// Handler for request interceptor. +/// The handler for interceptors to handle before the request has been sent. class RequestInterceptorHandler extends _BaseHandler { - /// Continue to call the next request interceptor. + /// Deliver the [requestOptions] to the next interceptor. + /// + /// Typically, the method should be called once interceptors done + /// manipulating the [requestOptions]. void next(RequestOptions requestOptions) { _completer.complete(InterceptorState(requestOptions)); _processNextInQueue?.call(); } - /// Return the response directly! Other request interceptor(s) will not be executed, - /// but response and error interceptor(s) may be executed, which depends on whether - /// the value of parameter [callFollowingResponseInterceptor] is true. + /// Completes the request by resolves the [response] as the result. /// - /// [response]: Response object to return. - /// [callFollowingResponseInterceptor]: Whether to call the response interceptor(s). + /// Invoking the method will make the rest of interceptors in the queue + /// skipped to handle the request, + /// unless [callFollowingResponseInterceptor] is true + /// which delivers [InterceptorResultType.resolveCallFollowing] + /// to the [InterceptorState]. void resolve( Response response, [ bool callFollowingResponseInterceptor = false, @@ -57,16 +61,15 @@ class RequestInterceptorHandler extends _BaseHandler { _processNextInQueue?.call(); } - /// Complete the request with an error! Other request/response interceptor(s) will not - /// be executed, but error interceptor(s) may be executed, which depends on whether the - /// value of parameter [callFollowingErrorInterceptor] is true. + /// Completes the request by reject with the [error] as the result. /// - /// [error]: Error info to reject. - /// [callFollowingErrorInterceptor]: Whether to call the error interceptor(s). - void reject( - DioException error, [ - bool callFollowingErrorInterceptor = false, - ]) { + /// Invoking the method will make the rest of interceptors in the queue + /// skipped to handle the request, + /// unless [callFollowingErrorInterceptor] is true + /// which delivers [InterceptorResultType.rejectCallFollowing] + /// to the [InterceptorState]. + void reject(DioException error, + [bool callFollowingErrorInterceptor = false]) { _completer.completeError( InterceptorState( error, @@ -80,9 +83,12 @@ class RequestInterceptorHandler extends _BaseHandler { } } -/// Handler for response interceptor. +/// The handler for interceptors to handle after respond. class ResponseInterceptorHandler extends _BaseHandler { - /// Continue to call the next response interceptor. + /// Deliver the [response] to the next interceptor. + /// + /// Typically, the method should be called once interceptors done + /// manipulating the [response]. void next(Response response) { _completer.complete( InterceptorState(response), @@ -90,7 +96,7 @@ class ResponseInterceptorHandler extends _BaseHandler { _processNextInQueue?.call(); } - /// Return the response directly! Other response i02 + /// Completes the request by resolves the [response] as the result. void resolve(Response response) { _completer.complete( InterceptorState( @@ -101,16 +107,15 @@ class ResponseInterceptorHandler extends _BaseHandler { _processNextInQueue?.call(); } - /// Complete the request with an error! Other response interceptor(s) will not - /// be executed, but error interceptor(s) may be executed, which depends on whether the - /// value of parameter [callFollowingErrorInterceptor] is true. + /// Completes the request by reject with the [error] as the result. /// - /// [error]: Error info to reject. - /// [callFollowingErrorInterceptor]: Whether to call the error interceptor(s). - void reject( - DioException error, [ - bool callFollowingErrorInterceptor = false, - ]) { + /// Invoking the method will make the rest of interceptors in the queue + /// skipped to handle the request, + /// unless [callFollowingErrorInterceptor] is true + /// which delivers [InterceptorResultType.rejectCallFollowing] + /// to the [InterceptorState]. + void reject(DioException error, + [bool callFollowingErrorInterceptor = false]) { _completer.completeError( InterceptorState( error, @@ -124,21 +129,21 @@ class ResponseInterceptorHandler extends _BaseHandler { } } -/// Handler for error interceptor. +/// The handler for interceptors to handle error occurred during the request. class ErrorInterceptorHandler extends _BaseHandler { - /// Continue to call the next error interceptor. - void next(DioException err) { + /// Deliver the [error] to the next interceptor. + /// + /// Typically, the method should be called once interceptors done + /// manipulating the [error]. + void next(DioException error) { _completer.completeError( - InterceptorState(err), - err.stackTrace, + InterceptorState(error), + error.stackTrace, ); _processNextInQueue?.call(); } - /// Complete the request with Response object and other error interceptor(s) will not be executed. - /// This will be considered a successful request! - /// - /// [response]: Response object to return. + /// Completes the request by resolves the [response] as the result. void resolve(Response response) { _completer.complete( InterceptorState( @@ -149,41 +154,33 @@ class ErrorInterceptorHandler extends _BaseHandler { _processNextInQueue?.call(); } - /// Complete the request with a error directly! Other error interceptor(s) will not be executed. + /// Completes the request by reject with the [error] as the result. void reject(DioException error) { _completer.completeError( - InterceptorState( - error, - InterceptorResultType.reject, - ), + InterceptorState(error, InterceptorResultType.reject), error.stackTrace, ); _processNextInQueue?.call(); } } -/// Dio instance may have one or more interceptors by which you can intercept -/// requests/responses/errors before they are handled by `then` or `catchError`. +/// [Interceptor] helps to deal with [RequestOptions], [Response], +/// and [DioError] during the lifecycle of a request before it reaches users. /// -/// Interceptors are called once per request and response. That means that redirects -/// aren't triggering interceptors. +/// Interceptors are called once per request and response, +/// that means redirects aren't triggering interceptors. /// /// See also: -/// - [InterceptorsWrapper] A helper class to create Interceptor(s). -/// - [QueuedInterceptor] Serialize the request/response/error before they enter the interceptor. -/// - [QueuedInterceptorsWrapper] A helper class to create QueuedInterceptor(s). +/// - [InterceptorsWrapper], the helper class to create [Interceptor]s. +/// - [QueuedInterceptor], resolves interceptors as a task in the queue. +/// - [QueuedInterceptorsWrapper], +/// the helper class to create [QueuedInterceptor]s. class Interceptor { + /// The constructor only helps sub-classes to inherit from. + /// Do not use it directly. const Interceptor(); - /// The callback will be executed before the request is initiated. - /// - /// If you want to continue the request, call [handler.next]. - /// - /// If you want to complete the request with some custom data, - /// you can resolve a [Response] object with [handler.resolve]. - /// - /// If you want to complete the request with an error message, - /// you can reject a [DioException] object with [handler.reject]. + /// Called when the request is about to be sent. void onRequest( RequestOptions options, RequestInterceptorHandler handler, @@ -191,15 +188,7 @@ class Interceptor { handler.next(options); } - /// The callback will be executed on success. - /// If you want to continue the response, call [handler.next]. - /// - /// If you want to complete the response with some custom data directly, - /// you can resolve a [Response] object with [handler.resolve] and other - /// response interceptor(s) will not be executed. - /// - /// If you want to complete the response with an error message, - /// you can reject a [DioException] object with [handler.reject]. + /// Called when the response is about to be resolved. void onResponse( Response response, ResponseInterceptorHandler handler, @@ -207,17 +196,7 @@ class Interceptor { handler.next(response); } - /// The callback will be executed on error. - /// - /// If you want to continue the error , call [handler.next]. - /// - /// If you want to complete the response with some custom data directly, - /// you can resolve a [Response] object with [handler.resolve] and other - /// error interceptor(s) will be skipped. - /// - /// If you want to complete the response with an error message directly, - /// you can reject a [DioException] object with [handler.reject], and other - /// error interceptor(s) will be skipped. + /// Called when an exception was occurred during the request. void onError( DioException err, ErrorInterceptorHandler handler, @@ -226,16 +205,19 @@ class Interceptor { } } +/// The signature of [Interceptor.onRequest]. typedef InterceptorSendCallback = void Function( RequestOptions options, RequestInterceptorHandler handler, ); +/// The signature of [Interceptor.onResponse]. typedef InterceptorSuccessCallback = void Function( Response e, ResponseInterceptorHandler handler, ); +/// The signature of [Interceptor.onError]. typedef InterceptorErrorCallback = void Function( DioException e, ErrorInterceptorHandler handler, @@ -283,12 +265,10 @@ mixin _InterceptorWrapperMixin on Interceptor { } } -/// [InterceptorsWrapper] is a helper class, which is used to conveniently -/// create interceptor(s). +/// A helper class to create interceptors in ease. +/// /// See also: -/// - [Interceptor] -/// - [QueuedInterceptor] Serialize the request/response/error before they enter the interceptor. -/// - [QueuedInterceptorsWrapper] A helper class to create QueuedInterceptor(s). +/// - [QueuedInterceptorsWrapper], creates [QueuedInterceptor]s in ease. class InterceptorsWrapper extends Interceptor with _InterceptorWrapperMixin { InterceptorsWrapper({ InterceptorSendCallback? onRequest, @@ -311,8 +291,9 @@ class InterceptorsWrapper extends Interceptor with _InterceptorWrapperMixin { final InterceptorErrorCallback? __onError; } -/// Interceptors are a queue, and you can add any number of interceptors, -/// All interceptors will be executed in first in first out order. +/// A Queue-Model list for [Interceptor]s. +/// +/// Interceptors will be executed with FIFO. class Interceptors extends ListMixin { /// Define a nullable list to be capable with growable elements. final List _list = [const ImplyContentTypeInterceptor()]; @@ -343,28 +324,25 @@ class Interceptors extends ListMixin { } } -class _InterceptorParams { +class _InterceptorParams { const _InterceptorParams(this.data, this.handler); final T data; final V handler; } -class _TaskQueue { - final queue = Queue<_InterceptorParams>(); +class _TaskQueue { + final queue = Queue<_InterceptorParams>(); bool processing = false; } -/// Serialize the request/response/error before they enter the interceptor. +/// [Interceptor] in queue. /// -/// If there are multiple concurrent requests, the request is added to a queue before -/// entering the interceptor. Only one request at a time enters the interceptor, and -/// after that request is processed by the interceptor, the next request will enter -/// the interceptor. +/// Concurrent requests will be added to the queue for interceptors. class QueuedInterceptor extends Interceptor { - final _TaskQueue _requestQueue = _TaskQueue(); - final _TaskQueue _responseQueue = _TaskQueue(); - final _TaskQueue _errorQueue = _TaskQueue(); + final _requestQueue = _TaskQueue(); + final _responseQueue = _TaskQueue(); + final _errorQueue = _TaskQueue(); void _handleRequest( RequestOptions options, @@ -388,16 +366,21 @@ class QueuedInterceptor extends Interceptor { } void _handleQueue( - _TaskQueue taskQueue, + _TaskQueue taskQueue, T data, V handler, - callback, + void Function(T, V) callback, ) { final task = _InterceptorParams(data, handler); - task.handler._processNextInQueue = _processNextTaskInQueueCallback( - taskQueue, - callback, - ); + task.handler._processNextInQueue = () { + if (taskQueue.queue.isNotEmpty) { + final next = taskQueue.queue.removeFirst(); + assert(next.handler._processNextInQueue != null); + callback(next.data, next.handler); + } else { + taskQueue.processing = false; + } + }; taskQueue.queue.add(task); if (!taskQueue.processing) { taskQueue.processing = true; @@ -405,31 +388,16 @@ class QueuedInterceptor extends Interceptor { try { callback(task.data, task.handler); } catch (e) { - task.handler._processNextInQueue(); + task.handler._processNextInQueue!(); } } } } -void Function() _processNextTaskInQueueCallback(_TaskQueue taskQueue, cb) { - return () { - if (taskQueue.queue.isNotEmpty) { - final next = taskQueue.queue.removeFirst(); - assert(next.handler._processNextInQueue != null); - cb(next.data, next.handler); - } else { - taskQueue.processing = false; - } - }; -} - -/// [QueuedInterceptorsWrapper] is a helper class, which is used to conveniently -/// create [QueuedInterceptor]s. +/// A helper class to create queued-interceptors in ease. /// /// See also: -/// - [Interceptor] -/// - [InterceptorsWrapper] -/// - [QueuedInterceptors] +/// - [InterceptorsWrapper], creates [Interceptor]s in ease. class QueuedInterceptorsWrapper extends QueuedInterceptor with _InterceptorWrapperMixin { QueuedInterceptorsWrapper({ diff --git a/dio/lib/src/multipart_file.dart b/dio/lib/src/multipart_file.dart index 6bcb42f26..07d44b303 100644 --- a/dio/lib/src/multipart_file.dart +++ b/dio/lib/src/multipart_file.dart @@ -12,26 +12,6 @@ import 'multipart_file/io_multipart_file.dart' /// MultipartFile is based on stream, and a stream can be read only once, /// so the same MultipartFile can't be read multiple times. class MultipartFile { - /// The size of the file in bytes. This must be known in advance, even if this - /// file is created from a [ByteStream]. - final int length; - - /// The basename of the file. May be null. - final String? filename; - - /// The additional headers the file has. May be null. - final Map>? headers; - - /// The content-type of the file. Defaults to `application/octet-stream`. - final MediaType? contentType; - - /// The stream that will emit the file's contents. - final Stream> _stream; - - /// Whether [finalize] has been called. - bool get isFinalized => _isFinalized; - bool _isFinalized = false; - /// Creates a new [MultipartFile] from a chunked [Stream] of bytes. The length /// of the file in bytes must be known in advance. If it's not, read the data /// from the stream and use [MultipartFile.fromBytes] instead. @@ -95,6 +75,26 @@ class MultipartFile { ); } + /// The size of the file in bytes. This must be known in advance, even if this + /// file is created from a [ByteStream]. + final int length; + + /// The basename of the file. May be null. + final String? filename; + + /// The additional headers the file has. May be null. + final Map>? headers; + + /// The content-type of the file. Defaults to `application/octet-stream`. + final MediaType? contentType; + + /// The stream that will emit the file's contents. + final Stream> _stream; + + /// Whether [finalize] has been called. + bool get isFinalized => _isFinalized; + bool _isFinalized = false; + /// Creates a new [MultipartFile] from a path to a file on disk. /// /// [filename] defaults to the basename of [filePath]. [contentType] currently diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index e43849216..3e0314fcf 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -6,60 +6,60 @@ import 'headers.dart'; import 'transformer.dart'; import 'utils.dart'; -/// Callback to listen the progress for sending/receiving data. +/// {@template dio.options.ProgressCallback} +/// The type of a progress listening callback when sending or receiving data. /// /// [count] is the length of the bytes have been sent/received. /// /// [total] is the content length of the response/request body. -/// 1.When sending data: -/// [total] is the request body length. -/// 2.When receiving data: -/// [total] will be -1 if the size of the response body is not known in advance, -/// for example: response data is compressed with gzip or no content-length header. +/// 1. When sending data, [total] is the request body length. +/// 2. When receiving data, [total] will be -1 if the size of the response body, +/// typically with no `content-length` header. +/// {@endtemplate} typedef ProgressCallback = void Function(int count, int total); -/// ResponseType indicates which transformation should -/// be automatically applied to the response data by Dio. +/// Indicates which transformation should be applied to the response data. enum ResponseType { /// Transform the response data to JSON object only when the /// content-type of response is "application/json" . json, - /// Get the response stream without any transformation. The - /// Response data will be a [ResponseBody] instance. + /// Get the response stream directly, + /// the [Response.data] will be [ResponseBody]. /// - /// Response rs = await Dio().get( - /// url, - /// options: Options( - /// responseType: ResponseType.stream, - /// ), - /// ); + /// ```dart + /// Response rs = await Dio().get( + /// url, + /// options: Options(responseType: ResponseType.stream), + /// ); stream, - /// Transform the response data to a String encoded with UTF8. + /// Transform the response data to an UTF-8 encoded [String]. plain, - /// Get original bytes, the type of [Response.data] will be List - bytes + /// Get the original bytes, the [Response.data] will be [List]. + bytes, } -/// ListFormat specifies the array format -/// (a single parameter with multiple parameter or multiple parameters with the same name) +/// {@template dio.options.ListFormat} +/// Specifies the array format (a single parameter with multiple parameter +/// or multiple parameters with the same name). /// and the separator for array items. +/// {@endtemplate} enum ListFormat { - /// Comma-separated values + /// Comma-separated values. /// e.g. (foo,bar,baz) csv, - /// Space-separated values + /// Space-separated values. /// e.g. (foo bar baz) ssv, - /// Tab-separated values + /// Tab-separated values. /// e.g. (foo\tbar\tbaz) tsv, - /// Pipe-separated values + /// Pipe-separated values. /// e.g. (foo|bar|baz) pipes, @@ -67,25 +67,57 @@ enum ListFormat { /// e.g. (foo=value&foo=another_value) multi, - /// Forward compatibility + /// Forward compatibility. /// e.g. (foo[]=value&foo[]=another_value) multiCompatible, } +/// The type of a response status code validate callback. typedef ValidateStatus = bool Function(int? status); +/// The type of a response decoding callback. typedef ResponseDecoder = String? Function( List responseBytes, RequestOptions options, ResponseBody responseBody, ); + +/// The type of a response encoding callback. typedef RequestEncoder = List Function( String request, RequestOptions options, ); -/// The common config for the Dio instance. -/// `dio.options` is a instance of [BaseOptions] +/// The mixin class for options that provides common attributes. +mixin OptionsMixin { + /// Request base url, it can contain sub paths like: https://pub.dev/api/. + late String baseUrl; + + /// Common query parameters. + /// + /// List values use the default [ListFormat.multiCompatible]. + /// The value can be overridden per parameter by adding a [ListParam] + /// object wrapping the actual List value and the desired format. + late Map queryParameters; + + /// Timeout when opening url. + /// + /// [Dio] will throw the [DioException] with + /// [DioExceptionType.connectionTimeout] type when time out. + /// + /// `null` meanings no timeout limit. + Duration? get connectTimeout => _connectTimeout; + Duration? _connectTimeout; + + set connectTimeout(Duration? value) { + if (value != null && value.isNegative) { + throw StateError('connectTimeout should be positive'); + } + _connectTimeout = value; + } +} + +/// The base config for the Dio instance, used by [Dio.options]. class BaseOptions extends _RequestConfig with OptionsMixin { BaseOptions({ String? method, @@ -130,7 +162,7 @@ class BaseOptions extends _RequestConfig with OptionsMixin { this.connectTimeout = connectTimeout; } - /// Create a Option from current instance with merging attributes. + /// Create a [BaseOptions] from current instance with merged attributes. BaseOptions copyWith({ String? method, String? baseUrl, @@ -176,33 +208,6 @@ class BaseOptions extends _RequestConfig with OptionsMixin { } } -mixin OptionsMixin { - /// Request base url, it can contain sub paths like: https://pub.dev/api/. - late String baseUrl; - - /// Common query parameters. - /// - /// List values use the default [ListFormat.multiCompatible]. - /// - /// The value can be overridden per parameter by adding a [ListParam] - /// object wrapping the actual List value and the desired format. - late Map queryParameters; - - /// Timeout in milliseconds for opening url. - /// [Dio] will throw the [DioException] with [DioExceptionType.connectionTimeout] type - /// when time out. - Duration? get connectTimeout => _connectTimeout; - - set connectTimeout(Duration? value) { - if (value != null && value.isNegative) { - throw StateError('connectTimeout should be positive'); - } - _connectTimeout = value; - } - - Duration? _connectTimeout; -} - /// Every request can pass an [Options] object which will be merged with [Dio.options] class Options { Options({ @@ -343,96 +348,97 @@ class Options { return requestOptions; } - /// Http method. + /// The HTTP request method. String? method; - /// Http request headers. The keys of initial headers will be converted to lowercase, - /// for example 'Content-Type' will be converted to 'content-type'. + /// HTTP request headers. /// - /// The key of Header Map is case-insensitive, eg: content-type and Content-Type are - /// regard as the same key. + /// The keys of the header are case-insensitive, + /// e.g.: content-type and Content-Type will be treated as the same key. Map? headers; - /// Timeout in milliseconds for sending data. - /// [Dio] will throw the [DioException] with [DioExceptionType.sendTimeout] type - /// when time out. + /// Timeout when sending data. + /// + /// [Dio] will throw the [DioException] with + /// [DioExceptionType.sendTimeout] type when timed out. + /// + /// `null` meanings no timeout limit. Duration? get sendTimeout => _sendTimeout; + Duration? _sendTimeout; set sendTimeout(Duration? value) { if (value != null && value.isNegative) { - throw StateError('sendTimeout should be positive'); + throw ArgumentError.value(value, 'sendTimeout', 'should be positive'); } _sendTimeout = value; } - Duration? _sendTimeout; - - /// Timeout in milliseconds for receiving data. + /// Timeout when receiving data. + /// + /// The timeout represents the timeout during data transfer of each bytes, + /// rather than the overall timing during the receiving. /// - /// Note: [receiveTimeout] represents a timeout during data transfer! That is to say the - /// client has connected to the server, and the server starts to send data to the client. + /// [Dio] will throw the [DioException] with + /// [DioExceptionType.receiveTimeout] type when time out. /// /// `null` meanings no timeout limit. Duration? get receiveTimeout => _receiveTimeout; + Duration? _receiveTimeout; set receiveTimeout(Duration? value) { if (value != null && value.isNegative) { - throw StateError('receiveTimeout should be positive'); + throw ArgumentError.value(value, 'receiveTimeout', 'should be positive'); } _receiveTimeout = value; } - Duration? _receiveTimeout; - - /// The request Content-Type. - /// - /// [Dio] will automatically encode the request body accordingly. + /// The request content-type. /// /// {@macro dio.interceptors.ImplyContentTypeInterceptor} String? contentType; - /// [responseType] indicates the type of data that the server will respond with - /// options which defined in [ResponseType] are `json`, `stream`, `plain`. + /// Indicates the type of data that the server will respond with options. /// /// The default value is [ResponseType.json], [Dio] will parse response string /// to JSON object automatically when the content-type of response is /// [Headers.jsonContentType]. /// - /// If you want to receive response data with binary bytes, for example, - /// downloading a image, use `stream`. + /// If you want to receive response data with binary bytes, use `stream`. /// /// If you want to receive the response data with String, use `plain`. /// - /// If you want to receive the response data with original bytes, - /// that's to say the type of [Response.data] will be List, use `bytes` + /// If you want to receive the response data with original bytes, use `bytes`. ResponseType? responseType; - /// [validateStatus] defines whether the request is successful for a given - /// HTTP response status code. If [validateStatus] returns `true` , - /// the request will be perceived as successful; otherwise, considered as failed. + /// Defines whether the request is succeed with the given status code. + /// The request will be treated as succeed if the callback returns true. ValidateStatus? validateStatus; - /// Whether receiving response data when http status code is not successful. - /// The default value is true + /// Whether to retrieve the data if status code indicates a failed request. + /// + /// Defaults to true. bool? receiveDataWhenStatusError; - /// Custom field that you can retrieve it later in - /// [Interceptor], [Transformer] and the [Response] object. + /// An extra map that you can retrieve in [Interceptor], [Transformer] + /// and [Response.requestOptions]. + /// + /// The field is designed to be non-identical with [Response.extra]. Map? extra; - /// see [HttpClientRequest.followRedirects], - /// The default value is true + /// See [HttpClientRequest.followRedirects]. + /// + /// Defaults to true. bool? followRedirects; - /// Set this property to the maximum number of redirects to follow - /// when [followRedirects] is `true`. If this number is exceeded - /// an error event will be added with a [RedirectException]. + /// The maximum number of redirects when [followRedirects] is `true`. + /// [RedirectException] will be thrown if redirects exceeded the limit. /// - /// The default value is 5. + /// Defaults to 5. int? maxRedirects; - /// see [HttpClientRequest.persistentConnection], - /// The default value is true + /// See [HttpClientRequest.persistentConnection]. + /// + /// Defaults to true. bool? persistentConnection; /// The default request encoder is utf8encoder, you can set custom @@ -443,13 +449,15 @@ class Options { /// decoder by this option, it will be used in [Transformer]. ResponseDecoder? responseDecoder; - /// The [listFormat] indicates the format of collection data in request - /// query parameters and `x-www-url-encoded` body data. - /// Possible values defined in [ListFormat] are `csv`, `ssv`, `tsv`, `pipes`, `multi`, `multiCompatible`. - /// The default value is `multi`. + /// Indicates the format of collection data in request query parameters and + /// `x-www-url-encoded` body data. + /// + /// Defaults to [ListFormat.multi]. ListFormat? listFormat; } +/// The internal request option class that is the eventual result after +/// [BaseOptions] and [Options] are composed. class RequestOptions extends _RequestConfig with OptionsMixin { RequestOptions({ this.path = '', @@ -501,7 +509,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin { this.connectTimeout = connectTimeout; } - /// Create a Option from current instance with merging attributes. + /// Create a [RequestOptions] from current instance with merged attributes. RequestOptions copyWith({ String? method, Duration? sendTimeout, @@ -575,15 +583,14 @@ class RequestOptions extends _RequestConfig with OptionsMixin { return ro; } - /// The source [StackTrace] which should always point to - /// the invocation of [DioMixin.request] or if not provided, - /// to the construction of the [RequestOptions] instance. - /// In both instances the source context should still be - /// available before it is lost due to asynchronous operations. + /// The source [StackTrace] which should always point to the invocation of + /// [DioMixin.request] or if not provided, to the construction of the + /// [RequestOptions] instance. In both instances the source context should + /// still be available before it is lost due to asynchronous operations. @internal StackTrace? sourceStackTrace; - /// generate uri + /// Generate the requesting [Uri] from the options. Uri get uri { String url = path; if (!url.startsWith(RegExp(r'https?:'))) { @@ -601,27 +608,28 @@ class RequestOptions extends _RequestConfig with OptionsMixin { return Uri.parse(url).normalizePath(); } - /// Request data, can be any type. - /// - /// When using `x-www-url-encoded` body data, - /// List values use the default [ListFormat.multi]. - /// - /// The value can be overridden per value by adding a [ListParam] - /// object wrapping the actual List value and the desired format. + /// Request data in dynamic types. dynamic data; - /// If the `path` starts with 'http(s)', the `baseURL` will be ignored, - /// otherwise, it will be combined and then resolved with the baseUrl. + /// Defines the path of the request. If it starts with "http(s)", + /// [baseUrl] will be ignored. Otherwise, it will be combined and resolved + /// with the [baseUrl]. String path; + /// {@macro dio.CancelToken} CancelToken? cancelToken; + /// {@macro dio.options.ProgressCallback} ProgressCallback? onReceiveProgress; + /// {@macro dio.options.ProgressCallback} ProgressCallback? onSendProgress; } -/// The [_RequestConfig] class describes the http request information and configuration. +bool _defaultValidateStatus(int? status) { + return status != null && status >= 200 && status < 300; +} + class _RequestConfig { _RequestConfig({ Duration? receiveTimeout, @@ -642,43 +650,36 @@ class _RequestConfig { }) : assert(receiveTimeout == null || !receiveTimeout.isNegative), _receiveTimeout = receiveTimeout, assert(sendTimeout == null || !sendTimeout.isNegative), - _sendTimeout = sendTimeout { + _sendTimeout = sendTimeout, + method = method ?? 'GET', + listFormat = listFormat ?? ListFormat.multi, + extra = extra ?? {}, + followRedirects = followRedirects ?? true, + maxRedirects = maxRedirects ?? 5, + persistentConnection = persistentConnection ?? true, + receiveDataWhenStatusError = receiveDataWhenStatusError ?? true, + validateStatus = validateStatus ?? _defaultValidateStatus, + responseType = responseType ?? ResponseType.json { this.headers = headers; - - final contentTypeInHeader = + final hasContentTypeHeader = this.headers.containsKey(Headers.contentTypeHeader); - assert( - !(contentType != null && contentTypeInHeader) || - this.headers[Headers.contentTypeHeader] == contentType, - 'You cannot set different values for contentType param and a content-type header', - ); - - this.method = method ?? 'GET'; - this.listFormat = listFormat ?? ListFormat.multi; - this.extra = extra ?? {}; - this.followRedirects = followRedirects ?? true; - this.maxRedirects = maxRedirects ?? 5; - this.persistentConnection = persistentConnection ?? true; - this.receiveDataWhenStatusError = receiveDataWhenStatusError ?? true; - this.validateStatus = validateStatus ?? - (int? status) { - return status != null && status >= 200 && status < 300; - }; - this.responseType = responseType ?? ResponseType.json; - if (!contentTypeInHeader) { + if (contentType != null && + hasContentTypeHeader && + this.headers[Headers.contentTypeHeader] != contentType) { + throw ArgumentError.value( + contentType, + 'contentType', + 'Unable to set different values for ' + '`contentType` and the content-type header.', + ); + } + if (!hasContentTypeHeader) { this.contentType = contentType; } } - /// Http method. late String method; - /// Http request headers. The keys of initial headers will be converted to lowercase, - /// for example 'Content-Type' will be converted to 'content-type'. - /// - /// The key of Header Map is case-insensitive, eg: content-type and Content-Type are - /// regard as the same key. - Map get headers => _headers; late Map _headers; @@ -690,12 +691,8 @@ class _RequestConfig { } } - /// Timeout in milliseconds for sending data. - /// [Dio] will throw the [DioException] with [DioExceptionType.sendTimeout] type - /// when time out. - /// - /// `null` meanings no timeout limit. Duration? get sendTimeout => _sendTimeout; + Duration? _sendTimeout; set sendTimeout(Duration? value) { if (value != null && value.isNegative) { @@ -704,15 +701,8 @@ class _RequestConfig { _sendTimeout = value; } - Duration? _sendTimeout; - - /// Timeout in milliseconds for receiving data. - /// - /// Note: [receiveTimeout] represents a timeout during data transfer! That is to say the - /// client has connected to the server, and the server starts to send data to the client. - /// - /// `null` meanings no timeout limit. Duration? get receiveTimeout => _receiveTimeout; + Duration? _receiveTimeout; set receiveTimeout(Duration? value) { if (value != null && value.isNegative) { @@ -721,13 +711,8 @@ class _RequestConfig { _receiveTimeout = value; } - Duration? _receiveTimeout; + String? _defaultContentType; - /// The request Content-Type. - /// - /// [Dio] will automatically encode the request body accordingly. - /// - /// {@macro dio.interceptors.ImplyContentTypeInterceptor} String? get contentType => _headers[Headers.contentTypeHeader] as String?; set contentType(String? contentType) { @@ -740,64 +725,14 @@ class _RequestConfig { } } - String? _defaultContentType; - - /// [responseType] indicates the type of data that the server will respond with - /// options which defined in [ResponseType] are `json`, `stream`, `plain`. - /// - /// The default value is `json`, dio will parse response string to json object automatically - /// when the content-type of response is 'application/json'. - /// - /// If you want to receive response data with binary bytes, for example, - /// downloading a image, use `stream`. - /// - /// If you want to receive the response data with String, use `plain`. - /// - /// If you want to receive the response data with original bytes, - /// that's to say the type of [Response.data] will be List, use `bytes` late ResponseType responseType; - - /// `validateStatus` defines whether the request is successful for a given - /// HTTP response status code. If `validateStatus` returns `true` , - /// the request will be perceived as successful; otherwise, considered as failed. late ValidateStatus validateStatus; - - /// Whether receiving response data when http status code is not successful. - /// The default value is true late bool receiveDataWhenStatusError; - - /// Custom field that you can retrieve it later in [Interceptor]、[Transformer] and the [Response] object. late Map extra; - - /// see [HttpClientRequest.followRedirects], - /// The default value is true late bool followRedirects; - - /// Set this property to the maximum number of redirects to follow - /// when [followRedirects] is `true`. If this number is exceeded - /// an error event will be added with a [RedirectException]. - /// - /// The default value is 5. late int maxRedirects; - - /// see [HttpClientRequest.persistentConnection], - /// The default value is true late bool persistentConnection; - - /// The default request encoder is utf8encoder, you can set custom - /// encoder by this option. RequestEncoder? requestEncoder; - - /// The default response decoder is utf8decoder, you can set custom - /// decoder by this option, it will be used in [Transformer]. ResponseDecoder? responseDecoder; - - /// The [listFormat] indicates the format of collection data in request - /// query parameters and `x-www-url-encoded` body data. - /// Possible values defined in [ListFormat] are `csv`, `ssv`, `tsv`, `pipes`, `multi`, `multiCompatible`. - /// The default value is `multi`. - /// - /// The value can be overridden per parameter by adding a [ListParam] - /// object to the query or body data map. late ListFormat listFormat; } diff --git a/dio/lib/src/parameter.dart b/dio/lib/src/parameter.dart index e78ed7bc4..ef44df948 100644 --- a/dio/lib/src/parameter.dart +++ b/dio/lib/src/parameter.dart @@ -1,14 +1,24 @@ -import 'package:dio/dio.dart'; +import 'options.dart'; +/// Indicates a param being used as queries or form data, +/// and how does it gets formatted. class ListParam { + const ListParam(this.value, this.format); + + /// The value used in queries or in form data. + final List value; + + /// How does the value gets formatted. final ListFormat format; - List value; - ListParam(this.value, this.format); + /// Generate a new [ListParam] by copying fields. + ListParam copyWith({List? value, ListFormat? format}) { + return ListParam(value ?? this.value, format ?? this.format); + } @override String toString() { - return 'ListParam{format: $format, value: $value}'; + return 'ListParam{value: $value, format: $format}'; } @override @@ -16,9 +26,9 @@ class ListParam { identical(this, other) || other is ListParam && runtimeType == other.runtimeType && - format == other.format && - value == other.value; + value == other.value && + format == other.format; @override - int get hashCode => format.hashCode ^ value.hashCode; + int get hashCode => value.hashCode ^ format.hashCode; } diff --git a/dio/lib/src/redirect_record.dart b/dio/lib/src/redirect_record.dart index 189ebe60b..415f988f5 100644 --- a/dio/lib/src/redirect_record.dart +++ b/dio/lib/src/redirect_record.dart @@ -1,5 +1,7 @@ +/// A record that records the redirection happens during requests, +/// including status code, request method, and the location. class RedirectRecord { - RedirectRecord(this.statusCode, this.method, this.location); + const RedirectRecord(this.statusCode, this.method, this.location); /// Returns the status code used for the redirect. final int statusCode; @@ -7,6 +9,12 @@ class RedirectRecord { /// Returns the method used for the redirect. final String method; - ///Returns the location for the redirect. + /// Returns the location for the redirect. final Uri location; + + @override + String toString() { + return 'RedirectRecord' + '{statusCode: $statusCode, method: $method, location: $location}'; + } } diff --git a/dio/lib/src/response.dart b/dio/lib/src/response.dart index 23de0f11d..b6ff998de 100644 --- a/dio/lib/src/response.dart +++ b/dio/lib/src/response.dart @@ -1,9 +1,14 @@ import 'dart:convert'; -import 'options.dart'; + import 'headers.dart'; +import 'options.dart'; import 'redirect_record.dart'; -/// Response describes the http Response info. +/// The [Response] class contains the payload (could be transformed) +/// that respond from the request, and other information of the response. +/// +/// The object is not sealed or immutable, which means it can be manipulated +/// in anytime, typically by [Interceptor] and [Transformer]. class Response { Response({ this.data, @@ -12,43 +17,41 @@ class Response { this.statusMessage, this.isRedirect = false, this.redirects = const [], - this.extra = const {}, + Map? extra, Headers? headers, - }) : headers = headers ?? Headers(); + }) : headers = headers ?? Headers(), + extra = extra ?? {}; - /// Response body. may have been transformed, please refer to [ResponseType]. + /// The response payload in specific type. + /// + /// The content could have been transformed by the [Transformer] + /// before it can use eventually. T? data; - /// The corresponding request info. + /// The [RequestOptions] used for the corresponding request. RequestOptions requestOptions; - /// HTTP status code. + /// The HTTP status code for the response. + /// + /// This can be null if the response was constructed manually. int? statusCode; /// Returns the reason phrase associated with the status code. - /// The reason phrase must be set before the body is written - /// to. Setting the reason phrase after writing to the body. String? statusMessage; - /// Whether this response is a redirect. - /// ** Attention **: Whether this field is available depends on whether the - /// implementation of the adapter supports it or not. + /// Headers for the response. + Headers headers; + + /// Whether the response has been redirected. + /// + /// The field rely on the implementation of the adapter. bool isRedirect; - /// The series of redirects this connection has been through. The list will be - /// empty if no redirects were followed. [redirects] will be updated both - /// in the case of an automatic and a manual redirect. + /// All redirections happened before the response respond. /// - /// ** Attention **: Whether this field is available depends on whether the - /// implementation of the adapter supports it or not. + /// The field rely on the implementation of the adapter. List redirects; - /// Custom fields that are constructed in the [RequestOptions]. - Map extra; - - /// Response headers. - Headers headers; - /// Return the final real request URI (may be redirected). /// /// Note: Whether the field is available depends on whether the adapter @@ -56,10 +59,16 @@ class Response { Uri get realUri => redirects.isNotEmpty ? redirects.last.location : requestOptions.uri; - /// We are more concerned about [data] field. + /// An extra map that you can save your custom information in. + /// + /// The field is designed to be non-identical with + /// [Options.extra] and [RequestOptions.extra]. + Map extra; + @override String toString() { if (data is Map) { + // Log encoded maps for better readability. return json.encode(data); } return data.toString(); diff --git a/dio/lib/src/transformer.dart b/dio/lib/src/transformer.dart index e915a0e8c..6bb007042 100644 --- a/dio/lib/src/transformer.dart +++ b/dio/lib/src/transformer.dart @@ -8,10 +8,10 @@ import 'utils.dart'; /// [Transformer] allows changes to the request/response data before /// it is sent/received to/from the server. /// -/// Dio has already implemented a [DefaultTransformer], and as the default +/// Dio has already implemented a [BackgroundTransformer], and as the default /// [Transformer]. If you want to custom the transformation of /// request/response data, you can provide a [Transformer] by your self, and -/// replace the [DefaultTransformer] by setting the [dio.Transformer]. +/// replace the [BackgroundTransformer] by setting the [Dio.Transformer]. abstract class Transformer { /// [transformRequest] allows changes to the request data before it is /// sent to the server, but **after** the [RequestInterceptor]. @@ -27,8 +27,8 @@ abstract class Transformer { // TODO(AlexV525): Add generic type for the method in v6.0.0. Future transformResponse(RequestOptions options, ResponseBody responseBody); - /// Deep encode the [Map] to percent-encoding. - /// It is mostly used with the "application/x-www-form-urlencoded" content-type. + /// Recursively encode the [Map] to percent-encoding. + /// Generally used with the "application/x-www-form-urlencoded" content-type. static String urlEncodeMap( Map map, [ ListFormat listFormat = ListFormat.multi, @@ -59,7 +59,7 @@ abstract class Transformer { ); } - /// Following: https://mimesniff.spec.whatwg.org/#json-mime-type + /// See https://mimesniff.spec.whatwg.org/#json-mime-type. static bool isJsonMimeType(String? contentType) { if (contentType == null) return false; final mediaType = MediaType.parse(contentType); diff --git a/dio/lib/src/transformers/background_transformer.dart b/dio/lib/src/transformers/background_transformer.dart index 3d66504f0..3283f958c 100644 --- a/dio/lib/src/transformers/background_transformer.dart +++ b/dio/lib/src/transformers/background_transformer.dart @@ -4,8 +4,10 @@ import 'dart:convert'; import 'package:dio/src/compute/compute.dart'; import 'package:dio/src/transformers/sync_transformer.dart'; -/// [BackgroundTransformer] will do the deserialization of JSON -/// in a background isolate if possible. +/// The default [Transformer] for [Dio]. +/// +/// [BackgroundTransformer] will do the deserialization of JSON in +/// a background isolate if possible. class BackgroundTransformer extends SyncTransformer { BackgroundTransformer() : super(jsonDecodeCallback: _decodeJson); } diff --git a/dio/lib/src/transformers/sync_transformer.dart b/dio/lib/src/transformers/sync_transformer.dart index 8fa5a4b14..1c37fdcf1 100644 --- a/dio/lib/src/transformers/sync_transformer.dart +++ b/dio/lib/src/transformers/sync_transformer.dart @@ -16,11 +16,9 @@ typedef JsonDecodeCallback = FutureOr Function(String); /// The callback definition for encoding a JSON object. typedef JsonEncodeCallback = FutureOr Function(Object); -/// The default [Transformer] for [Dio]. -/// /// If you want to custom the transformation of request/response data, /// you can provide a [Transformer] by your self, and replace -/// the [DefaultTransformer] by setting the [dio.transformer]. +/// the transformer by setting the [Dio.transformer]. class SyncTransformer extends Transformer { SyncTransformer({ this.jsonDecodeCallback = jsonDecode, diff --git a/dio/test/basic_test.dart b/dio/test/basic_test.dart index 1c54ad9b5..eabe7b029 100644 --- a/dio/test/basic_test.dart +++ b/dio/test/basic_test.dart @@ -41,15 +41,17 @@ void main() { test('send with an invalid URL', () async { await expectLater( Dio().get('http://http.invalid'), - throwsA(allOf([ - isA(), - (DioException e) => - e.type == - (isWeb - ? DioExceptionType.connectionError - : DioExceptionType.unknown), - if (!isWeb) (DioException e) => e.error is SocketException, - ])), + throwsA( + allOf([ + isA(), + (DioException e) => + e.type == + (isWeb + ? DioExceptionType.connectionError + : DioExceptionType.unknown), + if (!isWeb) (DioException e) => e.error is SocketException, + ]), + ), ); }); diff --git a/dio/test/cancel_token_test.dart b/dio/test/cancel_token_test.dart index 67024dd95..c6dc0da8c 100644 --- a/dio/test/cancel_token_test.dart +++ b/dio/test/cancel_token_test.dart @@ -11,11 +11,16 @@ void main() { final token = CancelToken(); const reason = 'cancel'; - expectLater(token.whenCancel, completion((error) { - return error is DioException && - error.type == DioExceptionType.cancel && - error.error == reason; - })); + expectLater( + token.whenCancel, + completion( + (error) { + return error is DioException && + error.type == DioExceptionType.cancel && + error.error == reason; + }, + ), + ); token.requestOptions = RequestOptions(); token.cancel(reason); }); @@ -55,10 +60,12 @@ void main() { for (final future in futures) { expectLater( future, - throwsA((error) => - error is DioError && - error.type == DioErrorType.cancel && - error.error == reason), + throwsA( + (error) => + error is DioException && + error.type == DioExceptionType.cancel && + error.error == reason, + ), ); } @@ -70,7 +77,7 @@ void main() { try { await Future.wait(futures); } catch (_) { - // ignore, just waiting here till all futures are completed + // ignore, just waiting here till all futures are completed. } for (final request in requests) { diff --git a/dio/test/stacktrace_test.dart b/dio/test/stacktrace_test.dart index 9451f515b..21bab3970 100644 --- a/dio/test/stacktrace_test.dart +++ b/dio/test/stacktrace_test.dart @@ -13,23 +13,25 @@ import 'mock/http_mock.mocks.dart'; void main() async { group('$DioException.stackTrace', () { test(DioExceptionType.badResponse, () async { - final dio = Dio(BaseOptions()) + final dio = Dio() ..httpClientAdapter = MockAdapter() ..options.baseUrl = MockAdapter.mockBase; await expectLater( dio.get('/foo'), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.badResponse, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (e) => e.type == DioExceptionType.badResponse, + (e) => + e.stackTrace.toString().contains('test/stacktrace_test.dart'), + ]), + ), ); }); test(DioExceptionType.cancel, () async { - final dio = Dio(BaseOptions()) + final dio = Dio() ..httpClientAdapter = MockAdapter() ..options.baseUrl = MockAdapter.mockBase; @@ -41,12 +43,14 @@ void main() async { await expectLater( dio.get('/test-timeout', cancelToken: token), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.cancel, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (e) => e.type == DioExceptionType.cancel, + (e) => + e.stackTrace.toString().contains('test/stacktrace_test.dart'), + ]), + ), ); }); @@ -55,25 +59,31 @@ void main() async { () async { await HttpOverrides.runWithHttpOverrides(() async { final timeout = Duration(milliseconds: 10); - final dio = Dio(BaseOptions() - ..connectTimeout = timeout - ..baseUrl = 'https://does.not.exist'); + final dio = Dio() + ..options.connectTimeout = timeout + ..options.baseUrl = 'https://does.not.exist'; - when(httpClientMock.openUrl('GET', any)).thenAnswer((_) async { - final request = MockHttpClientRequest(); - await Future.delayed( - Duration(milliseconds: timeout.inMilliseconds + 10)); - return request; - }); + when(httpClientMock.openUrl('GET', any)).thenAnswer( + (_) async { + final request = MockHttpClientRequest(); + await Future.delayed( + Duration(milliseconds: timeout.inMilliseconds + 10), + ); + return request; + }, + ); await expectLater( dio.get('/test'), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.connectionTimeout, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (e) => e.type == DioExceptionType.connectionTimeout, + (e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), + ), ); }, MockHttpOverrides()); }, @@ -85,28 +95,35 @@ void main() async { () async { await HttpOverrides.runWithHttpOverrides(() async { final timeout = Duration(milliseconds: 10); - final dio = Dio(BaseOptions() - ..receiveTimeout = timeout - ..baseUrl = 'https://does.not.exist'); + final dio = Dio() + ..options.receiveTimeout = timeout + ..options.baseUrl = 'https://does.not.exist'; - when(httpClientMock.openUrl('GET', any)).thenAnswer((_) async { - final request = MockHttpClientRequest(); - final response = MockHttpClientResponse(); - when(request.close()).thenAnswer((_) => Future.delayed( + when(httpClientMock.openUrl('GET', any)).thenAnswer( + (_) async { + final request = MockHttpClientRequest(); + final response = MockHttpClientResponse(); + when(request.close()).thenAnswer( + (_) => Future.delayed( Duration(milliseconds: timeout.inMilliseconds + 10), () => response, - )); - return request; - }); + ), + ); + return request; + }, + ); await expectLater( dio.get('/test'), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.receiveTimeout, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (DioException e) => e.type == DioExceptionType.receiveTimeout, + (DioException e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), + ), ); }, MockHttpOverrides()); }, @@ -118,29 +135,34 @@ void main() async { () async { await HttpOverrides.runWithHttpOverrides(() async { final timeout = Duration(milliseconds: 10); - final dio = Dio(BaseOptions() - ..sendTimeout = timeout - ..baseUrl = 'https://does.not.exist'); + final dio = Dio() + ..options.sendTimeout = timeout + ..options.baseUrl = 'https://does.not.exist'; - when(httpClientMock.openUrl('GET', any)).thenAnswer((_) async { - final request = MockHttpClientRequest(); - when(request.addStream(any)).thenAnswer( - (_) async => Future.delayed( - Duration(milliseconds: timeout.inMilliseconds + 10), - ), - ); - when(request.headers).thenReturn(MockHttpHeaders()); - return request; - }); + when(httpClientMock.openUrl('GET', any)).thenAnswer( + (_) async { + final request = MockHttpClientRequest(); + when(request.addStream(any)).thenAnswer( + (_) async => Future.delayed( + Duration(milliseconds: timeout.inMilliseconds + 10), + ), + ); + when(request.headers).thenReturn(MockHttpHeaders()); + return request; + }, + ); await expectLater( dio.get('/test', data: 'some data'), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.sendTimeout, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (DioException e) => e.type == DioExceptionType.sendTimeout, + (DioException e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), + ), ); }, MockHttpOverrides()); }, @@ -151,26 +173,33 @@ void main() async { DioExceptionType.badCertificate, () async { await HttpOverrides.runWithHttpOverrides(() async { - final dio = Dio(BaseOptions()..baseUrl = 'https://does.not.exist') - ..httpClientAdapter = (IOHttpClientAdapter() - ..validateCertificate = (certificate, host, port) => false); + final dio = Dio() + ..options.baseUrl = 'https://does.not.exist' + ..httpClientAdapter = IOHttpClientAdapter( + validateCertificate: (_, __, ___) => false, + ); - when(httpClientMock.openUrl('GET', any)).thenAnswer((_) async { - final request = MockHttpClientRequest(); - final response = MockHttpClientResponse(); - when(request.close()).thenAnswer((_) => Future.value(response)); - when(response.certificate).thenReturn(null); - return request; - }); + when(httpClientMock.openUrl('GET', any)).thenAnswer( + (_) async { + final request = MockHttpClientRequest(); + final response = MockHttpClientResponse(); + when(request.close()).thenAnswer((_) => Future.value(response)); + when(response.certificate).thenReturn(null); + return request; + }, + ); await expectLater( dio.get('/test'), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.badCertificate, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (DioException e) => e.type == DioExceptionType.badCertificate, + (DioException e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), + ), ); }, MockHttpOverrides()); }, @@ -181,7 +210,7 @@ void main() async { test( JsonUnsupportedObjectError, () async { - final dio = Dio(BaseOptions()..baseUrl = 'https://does.not.exist'); + final dio = Dio()..options.baseUrl = 'https://does.not.exist'; await expectLater( dio.get( @@ -189,13 +218,16 @@ void main() async { options: Options(contentType: Headers.jsonContentType), data: Object(), ), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.unknown, - (DioException e) => e.error is JsonUnsupportedObjectError, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (DioException e) => e.type == DioExceptionType.unknown, + (DioException e) => e.error is JsonUnsupportedObjectError, + (DioException e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), + ), ); }, testOn: '!browser', @@ -204,24 +236,25 @@ void main() async { test( 'SocketException on request', () async { - final dio = Dio(BaseOptions()..baseUrl = 'https://does.not.exist') + final dio = Dio() + ..options.baseUrl = 'https://does.not.exist' ..httpClientAdapter = IOHttpClientAdapter(); await expectLater( - dio.get( - '/test', - data: 'test', + dio.get('/test', data: 'test'), + throwsA( + allOf([ + isA(), + (e) => e.type == DioExceptionType.unknown, + (e) => e.error is SocketException, + (e) => (e.error as SocketException) + .message + .startsWith("Failed host lookup: 'does.not.exist'"), + (e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), ), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.unknown, - (DioException e) => e.error is SocketException, - (DioException e) => (e.error as SocketException) - .message - .startsWith("Failed host lookup: 'does.not.exist'"), - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), ); }, testOn: 'vm', @@ -230,18 +263,15 @@ void main() async { test( 'SocketException on response', () async { - final dio = Dio(BaseOptions()..baseUrl = 'https://does.not.exist') + final dio = Dio() + ..options.baseUrl = 'https://does.not.exist' ..httpClientAdapter = IOHttpClientAdapter( createHttpClient: () { final request = MockHttpClientRequest(); final client = MockHttpClient(); - when(client.openUrl(any, any)).thenAnswer( - (_) async => request, - ); + when(client.openUrl(any, any)).thenAnswer((_) async => request); when(request.headers).thenReturn(MockHttpHeaders()); - when(request.addStream(any)).thenAnswer( - (_) => Future.value(), - ); + when(request.addStream(any)).thenAnswer((_) => Future.value()); when(request.close()).thenAnswer( (_) async => Future.delayed(Duration(milliseconds: 50), () => throw SocketException('test')), @@ -251,17 +281,17 @@ void main() async { ); await expectLater( - dio.get( - '/test', - data: 'test', + dio.get('/test', data: 'test'), + throwsA( + allOf([ + isA(), + (e) => e.type == DioExceptionType.unknown, + (e) => e.error is SocketException, + (e) => e.stackTrace + .toString() + .contains('test/stacktrace_test.dart'), + ]), ), - throwsA(allOf([ - isA(), - (DioException e) => e.type == DioExceptionType.unknown, - (DioException e) => e.error is SocketException, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), ); }, testOn: 'vm', @@ -269,9 +299,9 @@ void main() async { }); test('Interceptor gets stacktrace in onError', () async { - final dio = Dio(); - dio.options.baseUrl = EchoAdapter.mockBase; - dio.httpClientAdapter = EchoAdapter(); + final dio = Dio() + ..options.baseUrl = EchoAdapter.mockBase + ..httpClientAdapter = EchoAdapter(); StackTrace? caughtStackTrace; dio.interceptors.addAll([ @@ -281,31 +311,32 @@ void main() async { handler.next(err); }, ), - InterceptorsWrapper(onRequest: (options, handler) { - final error = DioException( - error: Error(), - requestOptions: options, - ); - handler.reject(error, true); - }), + InterceptorsWrapper( + onRequest: (options, handler) { + final error = DioException(error: Error(), requestOptions: options); + handler.reject(error, true); + }, + ), ]); await expectLater( dio.get('/error'), - throwsA(allOf([ - isA(), - (DioException e) => e.stackTrace == caughtStackTrace, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (e) => e.stackTrace == caughtStackTrace, + (e) => + e.stackTrace.toString().contains('test/stacktrace_test.dart'), + ]), + ), reason: 'Stacktrace should be available in onError', ); }); test('QueuedInterceptor gets stacktrace in onError', () async { - final dio = Dio(); - dio.options.baseUrl = EchoAdapter.mockBase; - dio.httpClientAdapter = EchoAdapter(); + final dio = Dio() + ..options.baseUrl = EchoAdapter.mockBase + ..httpClientAdapter = EchoAdapter(); StackTrace? caughtStackTrace; dio.interceptors.addAll([ @@ -315,23 +346,27 @@ void main() async { handler.next(err); }, ), - QueuedInterceptorsWrapper(onRequest: (options, handler) { - final error = DioException( - error: Error(), - requestOptions: options, - ); - handler.reject(error, true); - }), + QueuedInterceptorsWrapper( + onRequest: (options, handler) { + final error = DioException( + error: Error(), + requestOptions: options, + ); + handler.reject(error, true); + }, + ), ]); await expectLater( dio.get('/error'), - throwsA(allOf([ - isA(), - (DioException e) => e.stackTrace == caughtStackTrace, - (DioException e) => - e.stackTrace.toString().contains('test/stacktrace_test.dart'), - ])), + throwsA( + allOf([ + isA(), + (e) => e.stackTrace == caughtStackTrace, + (e) => + e.stackTrace.toString().contains('test/stacktrace_test.dart'), + ]), + ), reason: 'Stacktrace should be available in onError', ); }); diff --git a/dio/test/timeout_test.dart b/dio/test/timeout_test.dart index eee46325a..7af397f78 100644 --- a/dio/test/timeout_test.dart +++ b/dio/test/timeout_test.dart @@ -18,9 +18,9 @@ void main() { expectLater( dio.get('/drip-lines?delay=2'), allOf( - throwsA(isA()), - throwsA(predicate((DioError e) => - e.type == DioErrorType.connectionTimeout && + throwsA(isA()), + throwsA(predicate((DioException e) => + e.type == DioExceptionType.connectionTimeout && e.message!.contains('0:00:00.003000'))), ), );