dio Documentation

A powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and...

12,842 stars Dart Dart 1 file · ~16,407 tokens #adapter#cancellable#dart#dio#flutter#http#interceptor#middleware
RAW Doc

File: README.md

dio

Language: English | 简体中文

This is the base repo of the dio project.
Please move to specific paths for project instructions.

Don't forget to add #dio
topic to your published dio-related packages!
See more: https://dart.dev/tools/pub/pubspec#topics

Versioning

Before you upgrade: Breaking changes might happen in major and minor versions of packages.
See the Migration Guide for the complete breaking changes list.

To know about our compatibility policy, see the Compatibility Policy doc.

All Packages

dio

Plugins

  • cookie_manager: link
  • compatibility_layer: link
  • http2_adapter: link
  • native_dio_adapter: link
  • web_adapter: link

Examples

  • example: link
  • example_flutter_app: link

The project and its underlying projects
are originally authored by
@wendux
with the organization
@flutterchina,
started being maintained by
Chinese Flutter User Group (@cfug)
since 2023.

The project consents the MIT license.


File: dio/doc/migration_guide.md

Migration Guide

This document gathered all breaking changes and migrations requirement between versions.

Breaking versions

5.0.0

Summary

  • get and getUri in Dio has different signature.
  • DefaultHttpClientAdapter is now named IOHttpClientAdapter,
    and the platform independent adapter can be initiated by HttpClientAdapter() which is a factory method.
  • Adapters that extends HttpClientAdapter must now implements instead of extends.
  • DioError has separate constructors and all fields are annotated as final.
  • DioErrorType has different values.
  • Imports are split into new libraries:
    • dio/io.dart is for natives specific classes;
    • dio/browser.dart is for web specific classes.
  • connectTimeout, sendTimeout, and receiveTimeout are now Duration instead of int.

Details

`get` and `getUri`

diff
Future<Response<T>> get<T>(
   String path, {
+  Object? data,
   Map<String, dynamic>? queryParameters,
   Options? options,
   CancelToken? cancelToken,
   ProgressCallback? onReceiveProgress,
 });
diff
Future<Response<T>> getUri<T>(
   Uri uri, {
+  Object? data,
   Map<String, dynamic>? queryParameters,
   Options? options,
   CancelToken? cancelToken,
   ProgressCallback? onReceiveProgress,
 });

`HttpClientAdapter`

Before:

dart
void initAdapter() {
  final dio = Dio();
  // For natives.
  dio.httpClientAdapter = DefaultHttpClientAdapter();
  // For web.
  dio.httpClientAdapter = BrowserHttpClientAdapter();
}

After:

dart
void initAdapter() {
  final dio = Dio();
  // Universal adapter that create the adapter for the corresponding platform.
  dio.httpClientAdapter = HttpClientAdapter();
  // For natives.
  dio.httpClientAdapter = IOHttpClientAdapter();
  // For web.
  dio.httpClientAdapter = BrowserHttpClientAdapter();
}

Implementing `HttpClientAdapter`

Before:

dart
class ExampleAdapter extends HttpClientAdapter { /* ... */ }

After:

dart
class ExampleAdapter implements HttpClientAdapter { /* ... */ }

Const `DioError`

Before:

dart
Never throwDioError() {
  final error = DioError(request: requestOptions, error: e);
  error.message = 'Custom message.';
  error.stackTrace = StackTrace.current;
  throw error;
}

After:

dart
Never throwDioError() {
  DioError error = DioError(
    request: requestOptions,
    error: e,
    stackTrace: StackTrace.current
  );
  error = error.copyWith(message: 'Custom message.');
  throw error;
}

`DioErrorType` values update

Before After
N/A badCertificate
response badResponse
connectTimeout connectionTimeout
other unknown

`Duration` instead of `int` for timeouts

Before:

dart
void request() {
  final dio = Dio(
    BaseOptions(
      connectTimeout: 5000,
      sendTimeout: 5000,
      receiveTimeout: 10000,
    ),
  );
}

After:

dart
void request() {
  final dio = Dio(
    BaseOptions(
      connectTimeout: const Duration(seconds: 5),
      sendTimeout: const Duration(seconds: 5),
      receiveTimeout: const Duration(seconds: 10),
    ),
  );
}

4.0.0

Details

  1. Null safety support (Dart >= 2.12).
  2. The Interceptor APIs signature has changed.
  3. Rename options.merge to options.copyWith.
  4. Rename DioErrorType enums from uppercase to camel style.
  5. Delete dio.resolve and dio.reject APIs (use handler instead in interceptors).
  6. Class BaseOptions no longer inherits from Options class.
  7. Change requestStream type of HttpClientAdapter.fetch from Stream<List> to Stream.
  8. Download API: Add real uri and redirect information to headers.

File: dio/doc/plugins.md

Repository Version Description
dio_cookie_manager A cookie manager for Dio
dio_http2_adapter A Dio HttpClientAdapter which support Http/2.0
native_dio_adapter An adapter for Dio which makes use of cupertino_http and cronet_http to delegate HTTP requests to the native platform.
dio_smart_retry Flexible retry library for Dio
http_certificate_pinning Https Certificate pinning for Flutter
dio_intercept_to_curl A Flutter curl-command generator for Dio.
dio_cache_interceptor Dio HTTP cache interceptor with multiple stores respecting HTTP directives (or not)
dio_http_cache A simple cache library for Dio like Rxcache in Android
pretty_dio_logger Pretty Dio logger is a Dio interceptor that logs network calls in a pretty, easy to read format.
dio_image_provider An image provider which makes use of package:dio to instead of dart:io
flutter_ume_kit_dio A debug kit of dio on flutter_ume
sentry_dio An integration which adds support for performance tracing for the Dio package.
talker_dio_logger Colorful and customizable dio logger with a lightweight design and talker additional functionality

File: dio/README.md

dio


Language: English | 简体中文

A powerful HTTP networking package for Dart/Flutter,
supports Global configuration, Interceptors, FormData,
Request cancellation, File uploading/downloading,
Timeout, Custom adapters, Transformers, etc.

Don't forget to add #dio
topic to your published dio related packages!
See more: https://dart.dev/tools/pub/pubspec#topics

Table of content

Get started

Install

Add the dio package to your
pubspec dependencies.

Before you upgrade: Breaking changes might happen in major and minor versions of packages.
See the Migration Guide for the complete breaking changes list.

Super simple to use

dart
import 'package:dio/dio.dart';

final dio = Dio();

void getHttp() async {
  final response = await dio.get('https://dart.dev');
  print(response);
}

Awesome dio

🎉 A curated list of awesome things related to dio.

Plugins

Plugins

Welcome to submit third-party plugins and related libraries
in here.

Examples

Performing a `GET` request

dart
import 'package:dio/dio.dart';

final dio = Dio();

void request() async {
  Response response;
  response = await dio.get('/test?id=12&name=dio');
  print(response.data.toString());
  // The below request is the same as above.
  response = await dio.get(
    '/test',
    queryParameters: {'id': 12, 'name': 'dio'},
  );
  print(response.data.toString());
}

Performing a `POST` request

dart
response = await dio.post('/test', data: {'id': 12, 'name': 'dio'});

Performing a `QUERY` request

The QUERY method (RFC 10008) is safe and idempotent like GET, but allows a
request body, which is useful for complex queries that do not fit in the URL.

dart
response = await dio.query('/search', data: {'filter': {'name': 'dio'}});

Performing multiple concurrent requests

dart
List<Response> responses = await Future.wait([dio.post('/info'), dio.get('/token')]);

Downloading a file

dart
response = await dio.download(
  'https://pub.dev/',
  (await getTemporaryDirectory()).path + 'pub.html',
);

On Web, the second argument is used as the browser's suggested filename instead
of a local filesystem path. The browser chooses the actual saved location, the
response is loaded into memory before the download is triggered, and CORS still
applies. FileAccessMode.append is not supported, deleteOnError has no local
file to delete, and custom lengthHeader values are not used for Web progress
totals.

Get response stream

dart
final rs = await dio.get(
  url,
  options: Options(responseType: ResponseType.stream), // Set the response type to `stream`.
);
print(rs.data.stream); // Response stream.

Get response with bytes

dart
final rs = await Dio().get<List>(
  url,
  options: Options(responseType: ResponseType.bytes), // Set the response type to `bytes`.
);
print(rs.data); // Type: List.

Sending a `FormData`

dart
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
});
final response = await dio.post('/info', data: formData);

Uploading multiple files to server by FormData

dart
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
  'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
  'files': [
    await MultipartFile.fromFile('./text1.txt', filename: 'text1.txt'),
    await MultipartFile.fromFile('./text2.txt', filename: 'text2.txt'),
  ]
});
final response = await dio.post('/info', data: formData);

Listening the uploading progress

dart
final response = await dio.post(
  'https://www.dtworkroom.com/doris/1/2.0.0/test',
  data: {'aa': 'bb' * 22},
  onSendProgress: (int sent, int total) {
    print('$sent $total');
  },
);

Post binary data with Stream

dart
// Binary data
final postData = [0, 1, 2];
await dio.post(
  url,
  data: Stream.fromIterable(postData.map((e) => [e])), // Creates a Stream<List>.
  options: Options(
    headers: {
      Headers.contentLengthHeader: postData.length, // Set the content-length.
    },
  ),
);

Note: content-length must be set if you want to subscribe to the sending progress.

See all examples code here.

Dio APIs

Creating an instance and set default configs

It is recommended to use a singleton of Dio in projects, which can manage configurations like headers, base urls,
and timeouts consistently.
Here is an example that use a singleton in Flutter.

You can create instance of Dio with an optional BaseOptions object:

dart
final dio = Dio(); // With default `Options`.

void configureDio() {
  // Set default configs
  dio.options.baseUrl = 'https://api.pub.dev';
  dio.options.connectTimeout = Duration(seconds: 5);
  dio.options.receiveTimeout = Duration(seconds: 3);

  // Or create `Dio` with a `BaseOptions` instance.
  final options = BaseOptions(
    baseUrl: 'https://api.pub.dev',
    connectTimeout: Duration(seconds: 5),
    receiveTimeout: Duration(seconds: 3),
  );
  final anotherDio = Dio(options);

  // Or clone the existing `Dio` instance with all fields.
  final clonedDio = dio.clone();
}

The core API in Dio instance is:

dart
Future<Response<T>> request<T>(
  String path, {
  Object? data,
  Map<String, dynamic>? queryParameters,
  CancelToken? cancelToken,
  Options? options,
  ProgressCallback? onSendProgress,
  ProgressCallback? onReceiveProgress,
});
dart
final response = await dio.request(
  '/test',
  data: {'id': 12, 'name': 'dio'},
  options: Options(method: 'GET'),
);

Request Options

There are two request options concepts in the Dio library:
BaseOptions and Options.
The BaseOptions include a set of base settings for each Dio(),
and the Options describes the configuration for a single request.
These options will be merged when making requests.
The Options declaration is as follows:

text
/* Detailed source-code truncated for AI context efficiency. */

There is a complete example here.

Response

The response for a request contains the following information.

dart
/// Response body. may have been transformed, please refer to [ResponseType].
T? data;

/// The corresponding request info.
RequestOptions requestOptions;

/// 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.
/// ** Attention **: Whether this field is available depends on whether the
/// implementation of the adapter supports it or not.
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.
///
/// ** Attention **: Whether this field is available depends on whether the
/// implementation of the adapter supports it or not.
List<RedirectRecord> redirects;

/// Custom fields that only for the [Response].
Map<String, dynamic> extra;

/// Response headers.
Headers headers;

When request is succeed, you will receive the response as follows:

dart
final response = await dio.get('https://pub.dev');
print(response.data);
print(response.headers);
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,
by which we can intercept requests, responses, and errors
before they are handled by then or catchError.

dart
dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
      // Do something before request is sent.
      // If you want to resolve the request with custom data,
      // you can resolve a `Response` using `handler.resolve(response)`.
      // If you want to reject the request with a error message,
      // you can reject with a `DioException` using `handler.reject(dioError)`.
      return handler.next(options);
    },
    onResponse: (Response response, ResponseInterceptorHandler handler) {
      // Do something with response data.
      // If you want to reject the request with a error message,
      // you can reject a `DioException` object using `handler.reject(dioError)`.
      return handler.next(response);
    },
    onError: (DioException error, ErrorInterceptorHandler handler) {
      // Do something with response error.
      // If you want to resolve the request with some custom data,
      // you can resolve a `Response` object using `handler.resolve(response)`.
      return handler.next(error);
    },
  ),
);

Simple interceptor example:

dart
import 'package:dio/dio.dart';
class CustomInterceptors extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('REQUEST[${options.method}] => PATH: ${options.path}');
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
    super.onResponse(response, handler);
  }

  @override
  Future onError(DioException err, ErrorInterceptorHandler handler) async {
    print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
    super.onError(err, handler);
  }
}

Resolve and reject the request

In all interceptors, you can interfere with their execution flow.
If you want to resolve the request/response with some custom data,
you can call handler.resolve(Response).
If you want to reject the request/response with a error message,
you can call handler.reject(dioError) .

dart
dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      return handler.resolve(
        Response(requestOptions: options, data: 'fake data'),
      );
    },
  ),
);
final response = await dio.get('/test');
print(response.data); // 'fake data'

QueuedInterceptor

Interceptor can be executed concurrently, that is,
all the requests enter the interceptor at once, rather than executing sequentially.
However, in some cases we expect that requests enter the interceptor sequentially like #590.
Therefore, we need to provide a mechanism for sequential access (step by step)
to interceptors and QueuedInterceptor can solve this problem.

Example

Because of security reasons, we need all the requests to set up
a csrfToken in the header, if csrfToken does not exist,
we need to request a csrfToken first, and then perform the network request,
because the request csrfToken progress is asynchronous,
so we need to execute this async request in request interceptor.

For the complete code see here.

LogInterceptor

You can apply the LogInterceptor to log requests and responses automatically.

Note: LogInterceptor should always be the last interceptor added,
otherwise modifications by following interceptors will not be logged.

Dart

dart
dio.interceptors.add(LogInterceptor(responseBody: false)); // Do not output responses body.

Note: When using the default logPrint function, logs will only be printed
in DEBUG mode (when the assertion is enabled).

Alternatively dart:developer's log can also be used to log messages (available in Flutter too).

Flutter

When using Flutter, Flutters own debugPrint function should be used.

This ensures, that debug messages are also available via flutter logs.

Note: debugPrint does not mean print logs under the DEBUG mode,
it's a throttled function which helps to print full logs without truncation.
Do not use it under any production environment unless you're intended to.

dart
dio.interceptors.add(
  LogInterceptor(
    logPrint: (o) => debugPrint(o.toString()),
  ),
);

Custom Interceptor

You can customize interceptor by extending the Interceptor/QueuedInterceptor class.
There is an example that implementing a simple cache policy:
custom cache interceptor.

Handling Errors

When an error occurs, Dio will wrap the Error/Exception to a DioException:

dart
try {
  // 404
  await dio.get('https://api.pub.dev/not-exist');
} on DioException catch (e) {
  // The request was made and the server responded with a status code
  // that falls out of the range of 2xx and is also not 304.
  if (e.response != null) {
    print(e.response.data)
    print(e.response.headers)
    print(e.response.requestOptions)
  } else {
    // Something happened in setting up or sending the request that triggered an Error
    print(e.requestOptions)
    print(e.message)
  }
}

DioException

dart
/// The request info for the request that throws exception.
RequestOptions requestOptions;

/// Response info, it may be `null` if the request can't reach to the
/// HTTP server, for example, occurring a DNS error, network is not available.
Response? response;

/// The type of the current [DioException].
DioExceptionType type;

/// The original error/exception object;
/// It's usually not null when `type` is [DioExceptionType.unknown].
Object? error;

/// The stacktrace of the original error/exception object;
/// It's usually not null when `type` is [DioExceptionType.unknown].
StackTrace? stackTrace;

/// The error message that throws a [DioException].
String? message;

DioExceptionType

See the source code.

Using application/x-www-form-urlencoded format

By default, Dio serializes request data (except String type) to JSON.
To send data in the application/x-www-form-urlencoded format instead:

dart
// Instance level
dio.options.contentType = Headers.formUrlEncodedContentType;
// or only works once
dio.post(
  '/info',
  data: {'id': 5},
  options: Options(contentType: Headers.formUrlEncodedContentType),
);

Sending FormData

You can also send FormData with Dio, which will send data in the multipart/form-data,
and it supports uploading files.

dart
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
  'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
});
final response = await dio.post('/info', data: formData);

You can also specify your desired boundary name which will be used
to construct boundaries of every FormData with additional prefix and suffix.

dart
final formDataWithBoundaryName = FormData(
  boundaryName: 'my-boundary-name',
);

FormData is supported with the POST method typically.

There is a complete example here.

Multiple files upload

There are two ways to add multiple files to FormData,
the only difference is that upload keys are different for array types。

dart
final formData = FormData.fromMap({
  'files': [
    MultipartFile.fromFileSync('path/to/upload1.txt', filename: 'upload1.txt'),
    MultipartFile.fromFileSync('path/to/upload2.txt', filename: 'upload2.txt'),
  ],
});

The upload key eventually becomes files[].
This is because many back-end services add a middle bracket to key
when they get an array of files.
If you don't want a list literal,
you should create FormData as follows (Don't use FormData.fromMap):

dart
final formData = FormData();
formData.files.addAll([
  MapEntry(
   'files',
    MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
  ),
  MapEntry(
    'files',
    MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
  ),
]);

Reuse `FormData`s and `MultipartFile`s

You should make a new FormData or MultipartFile every time in repeated requests.
A typical wrong behavior is setting the FormData as a variable and using it in every request.
It can be easy for the Cannot finalize exceptions to occur.
To avoid that, write your requests like the below code:

dart
Future<void> _repeatedlyRequest() async {
  Future<FormData> createFormData() async {
    return FormData.fromMap({
      'name': 'dio',
      'date': DateTime.now().toIso8601String(),
      'file': await MultipartFile.fromFile('./text.txt',filename: 'upload.txt'),
    });
  }
  
  await dio.post('some-url', data: await createFormData());
}

Transformer

Transformer allows changes to the request/response data
before it is sent/received to/from the server.
Dio has already implemented a BackgroundTransformer as default,
which calls jsonDecode in an isolate if the response is larger than 50 KB.
If you want to customize the transformation of request/response data,
you can provide a Transformer by your self,
and replace the BackgroundTransformer by setting the dio.transformer.

Transformer.transformRequest only takes effect when request with PUT/POST/PATCH,
they're methods that can contain the request body.
Transformer.transformResponse however, can be applied to all types of responses.

Transformer example

There is an example for customizing Transformer.

HttpClientAdapter

HttpClientAdapter is a bridge between Dio and HttpClient.

Dio implements standard and friendly APIs for developer.
HttpClient is the real object that makes Http requests.

We can use any HttpClient not just dart:io:HttpClient to make HTTP requests.
And all we need is providing a HttpClientAdapter.
The default HttpClientAdapter for Dio is IOHttpClientAdapter on native platforms,
and BrowserHttpClientAdapter on the Web platform.
They can be initiated by calling the HttpClientAdapter().

dart
dio.httpClientAdapter = HttpClientAdapter();

If you want to use platform adapters explicitly:

  • For the Web platform:
    dart
    import 'package:dio/browser.dart';
    // ...
    dio.httpClientAdapter = BrowserHttpClientAdapter();
  • For native platforms:
    dart
    import 'package:dio/io.dart';
    // ...
    dio.httpClientAdapter = IOHttpClientAdapter();

Here is a simple example to custom adapter.

Using proxy

IOHttpClientAdapter provide a callback to set proxy to dart:io:HttpClient,
for example:

dart
import 'package:dio/io.dart';

void initAdapter() {
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      final client = HttpClient();
      // Config the client.
      client.findProxy = (uri) {
        // Forward all request to proxy "localhost:8888".
        // Be aware, the proxy should went through you running device,
        // not the host platform.
        return 'PROXY localhost:8888';
      };
      // You can also create a new HttpClient for Dio instead of returning,
      // but a client must being returned here.
      return client;
    },
  );
}

There is a complete example here.

Web does not support to set proxy.

HTTPS certificate verification

HTTPS certificate verification (or public key pinning) refers to the process of ensuring that
the certificates protecting the TLS connection to the server are the ones you expect them to be.
The intention is to reduce the chance of a man-in-the-middle attack.
The theory is covered by OWASP.

Server Response Certificate

Unlike other methods, this one works with the certificate of the server itself.

dart
void initAdapter() {
  const String fingerprint = 'ee5ce1dfa7a53657c545c62b65802e4272878dabd65c0aadcf85783ebb0b4d5c';
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      // Don't trust any certificate just because their root cert is trusted.
      final HttpClient client = HttpClient(context: SecurityContext(withTrustedRoots: false));
      // You can test the intermediate / root cert here. We just ignore it.
      client.badCertificateCallback = (cert, host, port) => true;
      return client;
    },
    validateCertificate: (cert, host, port) {
      // Check that the cert fingerprint matches the one we expect.
      // We definitely require _some_ certificate.
      if (cert == null) {
        return false;
      }
      // Validate it any way you want. Here we only check that
      // the fingerprint matches the OpenSSL SHA256.
      return fingerprint == sha256.convert(cert.der).toString();
    },
  );
}

You can use openssl to read the SHA256 value of a certificate:

sh
openssl s_client -servername pinning-test.badssl.com -connect pinning-test.badssl.com:443 < /dev/null 2>/dev/null \
  | openssl x509 -noout -fingerprint -sha256

# SHA256 Fingerprint=EE:5C:E1:DF:A7:A5:36:57:C5:45:C6:2B:65:80:2E:42:72:87:8D:AB:D6:5C:0A:AD:CF:85:78:3E:BB:0B:4D:5C
# (remove the formatting, keep only lower case hex characters to match the `sha256` above)

Certificate Authority Verification

These methods work well when your server has a self-signed certificate,
but they don't work for certificates issued by a 3rd party like AWS or Let's Encrypt.

There are two ways to verify the root of the https certificate chain provided by the server.
Suppose the certificate format is PEM, the code like:

dart
void initAdapter() {
  String PEM = 'XXXXX'; // root certificate content
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      final client = HttpClient();
      client.badCertificateCallback = (X509Certificate cert, String host, int port) {
        return cert.pem == PEM; // Verify the certificate.
      };
      return client;
    },
  );
}

Another way is creating a SecurityContext when create the HttpClient:

dart
void initAdapter() {
  String PEM = 'XXXXX'; // root certificate content
  dio.httpClientAdapter = IOHttpClientAdapter(
    onHttpClientCreate: (_) {
      final SecurityContext sc = SecurityContext();
      sc.setTrustedCertificates(File(pathToTheCertificate));
      final HttpClient client = HttpClient(context: sc);
      return client;
    },
  );
}

In this way, the format of setTrustedCertificates() must be PEM or PKCS12.
PKCS12 requires password to use, which will expose the password in the code,
so it's not recommended to use in common cases.

HTTP/2 support

dio_http2_adapter is a Dio HttpClientAdapter
which supports HTTP/2.

Cancellation

You can cancel a request using a CancelToken.
One token can be shared with multiple requests.
When a token's cancel() is invoked, all requests with this token will be cancelled.

dart
final cancelToken = CancelToken();
dio.get(url, cancelToken: cancelToken).catchError((DioException error) {
  if (CancelToken.isCancel(error)) {
    print('Request canceled: ${error.message}');
  } else {
    // handle error.
  }
});
// Cancel the requests with "cancelled" message.
token.cancel('cancelled');

There is a complete example here.

Extends Dio class

Dio is an abstract class with factory constructor,
so we don't extend Dio class direct.
We can extend DioForNative or DioForBrowser instead, for example:

dart
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
// If in browser, import 'package:dio/browser.dart'.

class Http extends DioForNative {
  Http([BaseOptions options]) : super(options) {
    // do something
  }
}

We can also implement a custom Dio client:

dart
class MyDio with DioMixin implements Dio {
  // ...
}

Cross-Origin Resource Sharing on Web (CORS)

If a request is not a simple request,
the Web browser will send a CORS preflight request
that checks to see if the CORS protocol is understood
and a server is aware using specific methods and headers.

You can modify your requests to match the definition of simple request,
or add a CORS middleware for your service to handle CORS requests.


File: dio/README-ZH.md

dio


文档语言: 简体中文 | English

dio 是一个强大的 HTTP 网络请求库,支持全局配置、Restful API、FormData、拦截器、
请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。

别忘了为你发布的与 dio 相关的 package 添加
#dio 分类标签!
了解更多:https://dart.cn/tools/pub/pubspec#topics

内容列表

开始使用

添加依赖

依照文档将 dio 包添加为
pubspec 的依赖。

在你更新之前:大版本和次要版本可能会包含不兼容的重大改动。
请阅读 迁移指南 了解完整的重大变更内容。

一个极简的示例

dart
import 'package:dio/dio.dart';

final dio = Dio();

void getHttp() async {
  final response = await dio.get('https://dart.dev');
  print(response);
}

Awesome dio

🎉 以下是一个与 Dio 相关的精选清单。

相关插件

仓库 最新版本 描述
dio_cookie_manager Cookie 管理
dio_http2_adapter 支持 HTTP/2 的自定义适配器
native_dio_adapter 使用 cupertino_http 和 cronet_http 以适配器代理实现的原生网络请求功能。
dio_smart_retry 支持灵活地请求重试
http_certificate_pinning 用于 Flutter 的 HTTPS 证书锁定
dio_intercept_to_curl 用于 Flutter 的 CURL 命令生成器
dio_cache_interceptor 具有多个符合 HTTP 指令的 HTTP 缓存拦截器,
dio_http_cache 类似 Android 中的 RxCache 的缓存管理
pretty_dio_logger 基于拦截器的简明易读的请求日志打印
dio_image_provider 基于 Dio 的图片加载
flutter_ume_kit_dio flutter_ume 上的 dio 调试插件
talker_dio_logger 基于 talker 的轻量级和可定制的 dio 记录器

相关的项目

如果您也想提供第三方组件,请移步
这里。

示例

发起一个 `GET` 请求 :

dart
import 'package:dio/dio.dart';

final dio = Dio();

void request() async {
  Response response;
  response = await dio.get('/test?id=12&name=dio');
  print(response.data.toString());
  // The below request is the same as above.
  response = await dio.get(
    '/test',
    queryParameters: {'id': 12, 'name': 'dio'},
  );
  print(response.data.toString());
}

发起一个 `POST` 请求:

dart
response = await dio.post('/test', data: {'id': 12, 'name': 'dio'});

发起一个 `QUERY` 请求:

QUERY 方法(RFC 10008)与 GET 一样是安全且幂等的,但允许携带请求体,
适合用于参数过多或结构复杂、不适合放在 URL 中的查询。

dart
response = await dio.query('/search', data: {'filter': {'name': 'dio'}});

发起多个并发请求

dart
List<Response> responses = await Future.wait([dio.post('/info'), dio.get('/token')]);

下载文件

dart
response = await dio.download(
  'https://www.google.com/',
  '${(await getTemporaryDirectory()).path}google.html',
);

在 Web 平台上,第二个参数会被当作浏览器下载的建议文件名,而不是本地文件系统路径。
浏览器会决定实际保存位置;下载内容会先完整载入内存,并且仍受 CORS 限制。
FileAccessMode.append 不支持,deleteOnError 没有可删除的本地文件,自定义
lengthHeader 也不会用于 Web 下载进度总量。

以流的方式接收响应数据

dart
final rs = await dio.get(
  url,
  options: Options(responseType: ResponseType.stream), // 设置接收类型为 `stream`
);
print(rs.data.stream); // 响应流

以二进制数组的方式接收响应数据

dart
final rs = await dio.get(
  url,
  options: Options(responseType: ResponseType.bytes), // 设置接收类型为 `bytes`
);
print(rs.data); // 类型: List

发送 `FormData`

dart
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
});
final response = await dio.post('/info', data: formData);

通过 `FormData` 上传多个文件

dart
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
  'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
  'files': [
    await MultipartFile.fromFile('./text1.txt', filename: 'text1.txt'),
    await MultipartFile.fromFile('./text2.txt', filename: 'text2.txt'),
  ]
});
final response = await dio.post('/info', data: formData);

监听发送(上传)数据进度

dart
final response = await dio.post(
  'https://www.dtworkroom.com/doris/1/2.0.0/test',
  data: {'aa': 'bb' * 22},
  onSendProgress: (int sent, int total) {
    print('$sent $total');
  },
);

以流的形式提交二进制数据

dart
// Binary data
final postData = [0, 1, 2];
await dio.post(
  url,
  data: Stream.fromIterable(postData.map((e) => [e])), // 构建 Stream<List>
  options: Options(
    headers: {
      Headers.contentLengthHeader: postData.length, // 设置 content-length.
    },
  ),
);

注意:如果要监听提交进度,则必须设置content-length,否则是可选的。

你可以在这里查看 全部示例。

Dio APIs

创建一个Dio实例,并配置它

建议在项目中使用Dio单例,这样便可对同一个dio实例发起的所有请求进行一些统一的配置,
比如设置公共header、请求基地址、超时时间等。
这里有一个在Flutter工程中使用Dio单例
(定义为top level变量)的示例供开发者参考。

你可以使用默认配置或传递一个可选 BaseOptions参数来创建一个Dio实例 :

dart
final dio = Dio(); // With default `Options`.

void configureDio() {
  // Update default configs.
  dio.options.baseUrl = 'https://api.pub.dev';
  dio.options.connectTimeout = Duration(seconds: 5);
  dio.options.receiveTimeout = Duration(seconds: 3);

  // Or create `Dio` with a `BaseOptions` instance.
  final options = BaseOptions(
    baseUrl: 'https://api.pub.dev',
    connectTimeout: Duration(seconds: 5),
    receiveTimeout: Duration(seconds: 3),
  );
  final anotherDio = Dio(options);

  // Or clone the existing `Dio` instance with all fields.
  final clonedDio = dio.clone();
}

Dio 的核心 API 是:

dart
Future<Response<T>> request<T>(
  String path, {
  Object? data,
  Map<String, dynamic>? queryParameters,
  CancelToken? cancelToken,
  Options? options,
  ProgressCallback? onSendProgress,
  ProgressCallback? onReceiveProgress,
});
dart
final response = await dio.request(
  '/test',
  data: {'id': 12, 'name': 'dio'},
  options: Options(method: 'GET'),
);

请求配置

在 Dio 中有两种配置概念:BaseOptions 和 Options。
BaseOptions 描述的是 Dio 实例的一套基本配置,而 Options 描述了单独请求的配置信息。
以上的配置会在发起请求时进行合并。
下面是 Options 的配置项:

text
/* Detailed source-code truncated for AI context efficiency. */

此处为 完整的代码示例。

响应数据

当请求成功时会返回一个Response对象,它包含如下字段:

dart
/// 响应数据。可能已经被转换了类型, 详情请参考 [ResponseType]。
T? data;

/// 响应对应的请求配置。
RequestOptions requestOptions;

/// 响应的 HTTP 状态码。
int? statusCode;

/// 响应对应状态码的详情信息。
String? statusMessage;

/// 响应是否被重定向
bool isRedirect;

/// 请求连接经过的重定向列表。如果请求未经过重定向,则列表为空。
List<RedirectRecord> redirects;

/// 在 [RequestOptions] 中构造的自定义字段。
Map<String, dynamic> extra;

/// 响应对应的头数据。
Headers headers;

请求成功后,你可以访问到下列字段:

dart
final response = await dio.get('https://pub.dev');
print(response.data);
print(response.headers);
print(response.requestOptions);
print(response.statusCode);

注意,Response.extra 与 RequestOptions.extra 是不同的实例,互相之间无关。

拦截器

每个 Dio 实例都可以添加任意多个拦截器,他们会组成一个队列,拦截器队列的执行顺序是先进先出。
通过使用拦截器,你可以在请求之前、响应之后和发生异常时(未被 then 或 catchError 处理)
做一些统一的预处理操作。

dart
dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
      // 如果你想完成请求并返回一些自定义数据,你可以使用 `handler.resolve(response)`。
      // 如果你想终止请求并触发一个错误,你可以使用 `handler.reject(error)`。
      return handler.next(options);
    },
    onResponse: (Response response, ResponseInterceptorHandler handler) {
      // 如果你想终止请求并触发一个错误,你可以使用 `handler.reject(error)`。
      return handler.next(response);
    },
    onError: (DioException error, ErrorInterceptorHandler handler) {
      // 如果你想完成请求并返回一些自定义数据,你可以使用 `handler.resolve(response)`。
      return handler.next(error);
    },
  ),
);

一个简单的自定义拦截器示例:

dart
import 'package:dio/dio.dart';
class CustomInterceptors extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('REQUEST[${options.method}] => PATH: ${options.path}');
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
    super.onResponse(response, handler);
  }

  @override
  Future onError(DioException err, ErrorInterceptorHandler handler) async {
    print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
    super.onError(err, handler);
  }
}

完成和终止请求/响应

在所有拦截器中,你都可以改变请求执行流,
如果你想完成请求/响应并返回自定义数据,你可以 resolve 一个 Response 对象
或返回 handler.resolve(data) 的结果。
如果你想终止(触发一个错误,上层 catchError 会被调用)一个请求/响应,
那么可以 reject 一个DioException 对象或返回 handler.reject(errMsg) 的结果。

dart
dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      return handler.resolve(
        Response(requestOptions: options, data: 'fake data'),
      );
    },
  ),
);
final response = await dio.get('/test');
print(response.data); // 'fake data'

QueuedInterceptor

如果同时发起多个网络请求,则它们是可以同时进入Interceptor 的(并行的),
而 QueuedInterceptor 提供了一种串行机制:
它可以保证请求进入拦截器时是串行的(前面的执行完后后面的才会进入拦截器)。

例子

假设这么一个场景:出于安全原因,我们需要给所有的请求头中添加一个 csrfToken,
如果 csrfToken 不存在,我们先去请求 csrfToken,获取到 csrfToken 后再重试。
假设刚开始的时候 csrfToken 为 null,如果允许请求并发,则这些并发请求并行进入拦截器时
csrfToken 都为 null,所以它们都需要去请求 csrfToken,这会导致 csrfToken 被请求多次。
为了避免不必要的重复请求,可以使用 QueuedInterceptor, 这样只需要第一个请求处理一次即可。

完整的示例代码请点击 这里.

日志拦截器

我们可以添加 LogInterceptor 拦截器来自动打印请求和响应等日志:

注意: LogInterceptor 应该保持最后一个被添加到拦截器中,
否则在它之后进行处理的拦截器修改的内容将无法体现。

Dart

dart
dio.interceptors.add(LogInterceptor(responseBody: false)); // 不输出响应内容体

注意: 默认的 logPrint 只会在 DEBUG 模式(启用了断言)
的情况下输出日志。

你也可以使用 dart:developer 中的 log 来输出日志(在 Flutter 中也可以使用)。

Flutter

在 Flutter 中你应该使用 debugPrint 来打印日志。

这样也会让调试日志能够通过 flutter logs 获取到。

注意: debugPrint 的意义 不是只在 DEBUG 模式下打印,
而是对输出内容进行节流,从而保证输出完整。
请不要在生产模式使用,除非你有意输出相关日志。

dart
dio.interceptors.add(
  LogInterceptor(
    logPrint: (o) => debugPrint(o.toString()),
  ),
);

自定义拦截器

开发者可以通过继承 Interceptor/QueuedInterceptor 类来实现自定义拦截器。
这是一个简单的 缓存拦截器。

错误处理

当请求过程中发生错误时, Dio 会将 Error/Exception 包装成一个 DioException:

dart
try {
  // 404
  await dio.get('https://api.pub.dev/not-exist');
} on DioException catch (e) {
  // The request was made and the server responded with a status code
  // that falls out of the range of 2xx and is also not 304.
  if (e.response != null) {
    print(e.response.data)
    print(e.response.headers)
    print(e.response.requestOptions)
  } else {
    // Something happened in setting up or sending the request that triggered an Error
    print(e.requestOptions)
    print(e.message)
  }
}

DioException

dart
/// 错误的请求对应的配置。
RequestOptions requestOptions;

/// 错误的请求对应的响应内容。如果请求未完成,响应内容可能为空。
Response? response;

/// 错误的类型。
DioExceptionType type;

/// 实际错误的内容。
Object? error;

/// 实际错误的堆栈。
StackTrace? stackTrace;

/// 错误信息。
String? message;

DioExceptionType

见 源码。

使用 application/x-www-form-urlencoded 编码

默认情况下, Dio 会将请求数据(除了 String 类型)序列化为 JSON。
如果想要以 application/x-www-form-urlencoded 格式编码, 你可以设置 contentType :

dart
// Instance level
dio.options.contentType = Headers.formUrlEncodedContentType;
// or only works once
dio.post(
  '/info',
  data: {'id': 5},
  options: Options(contentType: Headers.formUrlEncodedContentType),
);

发送 FormData

Dio 支持发送 FormData, 请求数据将会以 multipart/form-data 方式编码,
FormData 中可以包含一个或多个文件。

dart
final formData = FormData.fromMap({
  'name': 'dio',
  'date': DateTime.now().toIso8601String(),
  'file': await MultipartFile.fromFile('./text.txt',filename: 'upload.txt')
});
final response = await dio.post('/info', data: formData);

你也可以指定封边 (boundary) 的名称,
封边名称会与额外的前缀和后缀一并组装成 FormData 的封边。

dart
final formDataWithBoundaryName = FormData(
  boundaryName: 'my-boundary-name',
);

通常情况下只有 POST 方法支持发送 FormData。

这里有一个完整的 示例。

多文件上传

多文件上传时,通过给 key 加中括号 [] 方式作为文件数组的标记,大多数后台也会通过 key[] 来读取多个文件。
然而 RFC 标准中并没有规定多文件上传必须要使用 [],关键在于后台与客户端之间保持一致。

dart
final formData = FormData.fromMap({
  'files': [
    MultipartFile.fromFileSync('path/to/upload1.txt', filename: 'upload1.txt'),
    MultipartFile.fromFileSync('path/to/upload2.txt', filename: 'upload2.txt'),
  ],
});

最终编码时会 key 会为 files[],
如果不想添加 [],可以通过 Formdata 的 files 来构建:

dart
final formData = FormData();
formData.files.addAll([
  MapEntry(
   'files',
    MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
  ),
  MapEntry(
    'files',
    MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
  ),
]);

复用 `FormData` 和 `MultipartFile`

如果你在重复调用的请求中使用 FormData 或者 MultipartFile,确保你每次使用的都是新实例。
常见的错误做法是将 FormData 赋值给一个共享变量,在每次请求中都使用这个变量。
这样的操作会加大 无法序列化 的错误出现的可能性。
你可以像以下的代码一样编写你的请求以避免出现这样的错误:

dart
Future<void> _repeatedlyRequest() async {
  Future<FormData> createFormData() async {
    return FormData.fromMap({
      'name': 'dio',
      'date': DateTime.now().toIso8601String(),
      'file': await MultipartFile.fromFile('./text.txt',filename: 'upload.txt'),
    });
  }
  
  await dio.post('some-url', data: await createFormData());
}

转换器

转换器 Transformer 用于对请求数据和响应数据进行编解码处理。
Dio 实现了一个默认转换器 DefaultTransformer。
如果你想对请求和响应数据进行自定义编解码处理,可以提供自定义转换器并通过 dio.transformer 设置。

Transformer.transformRequest 只在 PUT/POST/PATCH 方法中生效,
只有这些方法可以使用请求内容体 (request body)。
但是 Transformer.transformResponse 可以用于所有请求方法的返回数据。

在 Flutter 中进行设置

如果你在开发 Flutter 应用,强烈建议通过 compute 在单独的 isolate 中进行 JSON 解码,
从而避免在解析复杂 JSON 时导致的 UI 卡顿。

dart
/// 
Map<String, dynamic> _parseAndDecode(String response) {
  return jsonDecode(response) as Map<String, dynamic>;
}

Future<Map<String, dynamic>> parseJson(String text) {
  return compute(_parseAndDecode, text);
}

void main() {
  // 自定义 `jsonDecodeCallback`
  dio.transformer = DefaultTransformer()..jsonDecodeCallback = parseJson;
  runApp(MyApp());
}

其它示例

这里有一个 自定义 Transformer 的示例。

HttpClientAdapter

HttpClientAdapter 是 Dio 和 HttpClient 之间的桥梁。

Dio 实现了一套标准且强大的 API,而 HttpClient 则是真正发起 HTTP 请求的对象。

我们通过 HttpClientAdapter 将 Dio 和 HttpClient 解耦,
这样一来便可以自由定制 HTTP 请求的底层实现。
Dio 使用 IOHttpClientAdapter 作为原生平台默认的桥梁,
BrowserHttpClientAdapter 作为 Web 平台的桥梁。
你可以通过 HttpClientAdapter() 来根据平台创建它们。

dart
dio.httpClientAdapter = HttpClientAdapter();

如果你需要单独使用对应平台的适配器:

  • 对于 Web 平台
    dart
    import 'package:dio/browser.dart';
    // ...
    dio.httpClientAdapter = BrowserHttpClientAdapter();
  • 对于原生平台:
    dart
    import 'package:dio/io.dart';
    // ...
    dio.httpClientAdapter = IOHttpClientAdapter();

示例 中包含了一个简单的自定义桥接。

设置代理

IOHttpClientAdapter 提供了一个 createHttpClient 回调来设置底层 HttpClient 的代理:

dart
import 'package:dio/io.dart';

void initAdapter() {
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      final client = HttpClient();
      client.findProxy = (uri) {
        // 将请求代理至 localhost:8888。
        // 请注意,代理会在你正在运行应用的设备上生效,而不是在宿主平台生效。
        return 'PROXY localhost:8888';
      };
      return client;
    },
  );
}

完整的示例请查看 这里。

Web 平台不支持设置代理。

HTTPS 证书校验

HTTPS 证书验证(或公钥固定)是指确保端侧与服务器的 TLS 连接的证书是期望的证书,从而减少中间人攻击的机会。
OWASP 中解释了该理论。

服务器响应证书

与其他方法不同,此方法使用服务器本身的证书。

dart
void initAdapter() {
  const String fingerprint = 'ee5ce1dfa7a53657c545c62b65802e4272878dabd65c0aadcf85783ebb0b4d5c';
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      // Don't trust any certificate just because their root cert is trusted.
      final HttpClient client = HttpClient(context: SecurityContext(withTrustedRoots: false));
      // You can test the intermediate / root cert here. We just ignore it.
      client.badCertificateCallback = (cert, host, port) => true;
      return client;
    },
    validateCertificate: (cert, host, port) {
      // Check that the cert fingerprint matches the one we expect.
      // We definitely require _some_ certificate.
      if (cert == null) {
        return false;
      }
      // Validate it any way you want. Here we only check that
      // the fingerprint matches the OpenSSL SHA256.
      return fingerprint == sha256.convert(cert.der).toString();
    },
  );
}

你可以使用 OpenSSL 读取密钥的 SHA-256:

sh
openssl s_client -servername pinning-test.badssl.com -connect pinning-test.badssl.com:443 < /dev/null 2>/dev/null \
  | openssl x509 -noout -fingerprint -sha256

# SHA256 Fingerprint=EE:5C:E1:DF:A7:A5:36:57:C5:45:C6:2B:65:80:2E:42:72:87:8D:AB:D6:5C:0A:AD:CF:85:78:3E:BB:0B:4D:5C
# (remove the formatting, keep only lower case hex characters to match the `sha256` above)

证书颁发机构验证

当您的服务器具有自签名证书时,可以用下面的方法,但它们不适用于 AWS 或 Let's Encrypt 等第三方颁发的证书。

有两种方法可以校验证书,假设我们的后台服务使用的是自签名证书,证书格式是 PEM 格式,我们将证书的内容保存在本地字符串中,
那么我们的校验逻辑如下:

dart
void initAdapter() {
  String PEM = 'XXXXX'; // root certificate content
  dio.httpClientAdapter = IOHttpClientAdapter(
    createHttpClient: () {
      final client = HttpClient();
      client.badCertificateCallback = (X509Certificate cert, String host, int port) {
        return cert.pem == PEM; // Verify the certificate.
      };
      return client;
    },
  );
}

对于自签名的证书,我们也可以将其添加到本地证书信任链中,
这样证书验证时就会自动通过,而不会再走到 badCertificateCallback 回调中:

dart
void initAdapter() {
  String PEM = 'XXXXX'; // root certificate content
  dio.httpClientAdapter = IOHttpClientAdapter(
    onHttpClientCreate: (_) {
      final SecurityContext sc = SecurityContext();
      sc.setTrustedCertificates(File(pathToTheCertificate));
      final HttpClient client = HttpClient(context: sc);
      return client;
    },
  );
}

注意,通过 setTrustedCertificates() 设置的证书格式必须为 PEM 或 PKCS12,
如果证书格式为 PKCS12,则需将证书密码传入,
这样则会在代码中暴露证书密码,所以客户端证书校验不建议使用 PKCS12 格式的证书。

HTTP/2 支持

dio_http2_adapter 提供了一个支持 HTTP/2 的桥接 。

请求取消

你可以通过 CancelToken 来取消发起的请求。
一个 CancelToken 可以给多个请求共用,
在共用时调用 cancel() 会取消对应的所有请求:

dart
final cancelToken = CancelToken();
dio.get(url, cancelToken: cancelToken).catchError((DioException error) {
  if (CancelToken.isCancel(error)) {
    print('Request canceled: ${error.message}');
  } else {
    // handle error.
  }
});
// Cancel the requests with "cancelled" message.
token.cancel('cancelled');

完整的示例请参考 取消示例.

继承 Dio class

Dio 是一个拥有工厂构造函数的接口类,因此不能直接继承 Dio,
但是可以继承 DioForNative 或 DioForBrowser:

dart
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
// 在浏览器中,导入 'package:dio/browser.dart'。

class Http extends DioForNative {
  Http([BaseOptions options]) : super(options) {
    // 构造函数执行
  }
}

我们也可以直接实现 Dio 接口类 :

dart
class MyDio with DioMixin implements Dio {
  // ...
}

Web 平台跨域资源共享 (CORS)

在 Web 平台上发送网络请求时,如果请求不是一个 简单请求,
浏览器会自动向服务器发送 CORS 预检 (Pre-flight requests),
用于检查服务器是否支持跨域资源共享。

你可以参考简单请求的定义修改你的请求,或者为你的服务加上 CORS 中间件进行跨域处理。


File: dio_test/README.md

Dio shared test package

The package gathered shared tests between Dio's packages,
such as dio and http2_adapter.

The package was meant to not be published to pub.dev.
To use the package in tests, make the library depend like this:

yaml
dev_dependencies:
  # Shared test package.
  dio_test:
    git:
      url: https://github.com/cfug/dio
      path: dio_test

Then, use dependency_overrides or pubspec_overrides.yaml
to override the package to a local path or somewhere else.

The project is authored by
Chinese Flutter User Group (@cfug)
since 2024.

The project consents the MIT license.


File: example_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md

Launch Screen Assets

You can customize the launch screen with your own desired assets by replacing the image files in this directory.

You can also do it by opening your Flutter project's Xcode project with open ios/Runner.xcworkspace, selecting Runner/Assets.xcassets in the Project Navigator and dropping in the desired images.


File: example_flutter_app/README.md

flutter_app

A new Flutter application for dio test.

Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

For help getting started with Flutter, view our
online documentation, which offers tutorials,
samples, guidance on mobile development, and a full API reference.


File: plugins/web_adapter/README.md

dio_web_adapter




If you encounter bugs, consider fixing it by opening a PR or at least contribute a failing test case.

This package contains adapters for Dio
which enables you to use the library on the Web platform.

Versions compatibility

Version Dart (min) Flutter (min)
1.x 2.18.0 3.3.0
2.x 3.3.0 3.19.0

Note: the resolvable version will be determined by the SDK you are using.
Run dart pub upgrade or flutter pub upgrade to get the latest resolvable version.

Get started

The package is embedded into the package:dio.
You don't need to explicitly install the package unless you have other concerns.

Install

Add the dio_web_adapter package to your
pubspec dependencies.

Example

dart
import 'package:dio/dio.dart';
// The import is not required and could produce lints.
// import 'package:dio_web_adapter/dio_web_adapter.dart';

void main() async {
  final dio = Dio();
  dio.httpClientAdapter = BrowserHttpClientAdapter(withCredentials: true);

  // Make a request.
  final response = await dio.get('https://dart.dev');
  print(response);
}

Downloading files

Dio.download is supported on Web by fetching the response bytes and triggering
a browser download with a Blob URL. The savePath argument is treated as the
suggested filename, not as a local filesystem path. The browser decides the
actual save location.

The browser schedules the download from a blob: URL created in the current
page. A returned Response means Dio fetched the response and dispatched the
browser download click; it does not guarantee that the browser wrote the file to
disk, kept the suggested filename, or skipped a user prompt. Those decisions are
controlled by the browser, user settings, and page security policies.

Web downloads have platform limitations:

  • The request is still subject to CORS because it is fetched through Dio.
  • The network request is handled by the browser through XHR, so HTTP protocol
    details such as HTTP/1.1, HTTP/2, or HTTP/3 are browser-controlled.
  • The whole response is loaded into memory before the browser download starts.
  • The download trigger relies on standard browser support for Blob,
    URL.createObjectURL, and HTMLAnchorElement.download.
  • FileAccessMode.append is not supported.
  • deleteOnError has no local file to delete on Web.
  • Custom lengthHeader values are not used; progress totals come from the
    browser response progress event.

File: plugins/native_dio_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md

Launch Screen Assets

You can customize the launch screen with your own desired assets by replacing the image files in this directory.

You can also do it by opening your Flutter project's Xcode project with open ios/Runner.xcworkspace, selecting Runner/Assets.xcassets in the Project Navigator and dropping in the desired images.