Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 : import 'dart:math' as math;
4 : import 'dart:typed_data';
5 : import 'adapter.dart';
6 : import 'form_data.dart';
7 : import 'options.dart';
8 : import 'interceptor.dart';
9 : import 'headers.dart';
10 : import 'cancel_token.dart';
11 : import 'transformer.dart';
12 : import 'response.dart';
13 : import 'dio_error.dart';
14 : import 'entry_stub.dart'
15 : // ignore: uri_does_not_exist
16 : if (dart.library.html) 'entry/dio_for_browser.dart'
17 : // ignore: uri_does_not_exist
18 : if (dart.library.io) 'entry/dio_for_native.dart';
19 :
20 : /// A powerful Http client for Dart, which supports Interceptors,
21 : /// Global configuration, FormData, File downloading etc. and Dio is
22 : /// very easy to use.
23 : ///
24 : /// You can create a dio instance and config it by two ways:
25 : /// 1. create first , then config it
26 : ///
27 : /// ```dart
28 : /// var dio = Dio();
29 : /// dio.options.baseUrl = "http://www.dtworkroom.com/doris/1/2.0.0/";
30 : /// dio.options.connectTimeout = 5000; //5s
31 : /// dio.options.receiveTimeout = 5000;
32 : /// dio.options.headers = {HttpHeaders.userAgentHeader: 'dio', 'common-header': 'xx'};
33 : /// ```
34 : /// 2. create and config it:
35 : ///
36 : /// ```dart
37 : /// var dio = Dio(BaseOptions(
38 : /// baseUrl: "http://www.dtworkroom.com/doris/1/2.0.0/",
39 : /// connectTimeout: 5000,
40 : /// receiveTimeout: 5000,
41 : /// headers: {HttpHeaders.userAgentHeader: 'dio', 'common-header': 'xx'},
42 : /// ));
43 : /// ```
44 :
45 : abstract class Dio {
46 10 : factory Dio([BaseOptions options]) => createDio(options);
47 :
48 : /// Default Request config. More see [BaseOptions] .
49 : BaseOptions options;
50 :
51 : Interceptors get interceptors;
52 :
53 : HttpClientAdapter httpClientAdapter;
54 :
55 : /// [transformer] allows changes to the request/response data before it is sent/received to/from the server
56 : /// This is only applicable for request methods 'PUT', 'POST', and 'PATCH'.
57 : Transformer transformer;
58 :
59 : /// Shuts down the dio client.
60 : ///
61 : /// If [force] is `false` (the default) the [Dio] will be kept alive
62 : /// until all active connections are done. If [force] is `true` any active
63 : /// connections will be closed to immediately release all resources. These
64 : /// closed connections will receive an error event to indicate that the client
65 : /// was shut down. In both cases trying to establish a new connection after
66 : /// calling [close] will throw an exception.
67 : void close({bool force = false});
68 :
69 : /// Handy method to make http GET request, which is a alias of [BaseDio.request].
70 : Future<Response<T>> get<T>(
71 : String path, {
72 : Map<String, dynamic> queryParameters,
73 : Options options,
74 : CancelToken cancelToken,
75 : ProgressCallback onReceiveProgress,
76 : });
77 :
78 : /// Handy method to make http GET request, which is a alias of [BaseDio.request].
79 : Future<Response<T>> getUri<T>(
80 : Uri uri, {
81 : Options options,
82 : CancelToken cancelToken,
83 : ProgressCallback onReceiveProgress,
84 : });
85 :
86 : /// Handy method to make http POST request, which is a alias of [BaseDio.request].
87 : Future<Response<T>> post<T>(
88 : String path, {
89 : data,
90 : Map<String, dynamic> queryParameters,
91 : Options options,
92 : CancelToken cancelToken,
93 : ProgressCallback onSendProgress,
94 : ProgressCallback onReceiveProgress,
95 : });
96 :
97 : /// Handy method to make http POST request, which is a alias of [BaseDio.request].
98 : Future<Response<T>> postUri<T>(
99 : Uri uri, {
100 : data,
101 : Options options,
102 : CancelToken cancelToken,
103 : ProgressCallback onSendProgress,
104 : ProgressCallback onReceiveProgress,
105 : });
106 :
107 : /// Handy method to make http PUT request, which is a alias of [BaseDio.request].
108 : Future<Response<T>> put<T>(
109 : String path, {
110 : data,
111 : Map<String, dynamic> queryParameters,
112 : Options options,
113 : CancelToken cancelToken,
114 : ProgressCallback onSendProgress,
115 : ProgressCallback onReceiveProgress,
116 : });
117 :
118 : /// Handy method to make http PUT request, which is a alias of [BaseDio.request].
119 : Future<Response<T>> putUri<T>(
120 : Uri uri, {
121 : data,
122 : Options options,
123 : CancelToken cancelToken,
124 : ProgressCallback onSendProgress,
125 : ProgressCallback onReceiveProgress,
126 : });
127 :
128 : /// Handy method to make http HEAD request, which is a alias of [BaseDio.request].
129 : Future<Response<T>> head<T>(
130 : String path, {
131 : data,
132 : Map<String, dynamic> queryParameters,
133 : Options options,
134 : CancelToken cancelToken,
135 : });
136 :
137 : /// Handy method to make http HEAD request, which is a alias of [BaseDio.request].
138 : Future<Response<T>> headUri<T>(
139 : Uri uri, {
140 : data,
141 : Options options,
142 : CancelToken cancelToken,
143 : });
144 :
145 : /// Handy method to make http DELETE request, which is a alias of [BaseDio.request].
146 : Future<Response<T>> delete<T>(
147 : String path, {
148 : data,
149 : Map<String, dynamic> queryParameters,
150 : Options options,
151 : CancelToken cancelToken,
152 : });
153 :
154 : /// Handy method to make http DELETE request, which is a alias of [BaseDio.request].
155 : Future<Response<T>> deleteUri<T>(
156 : Uri uri, {
157 : data,
158 : Options options,
159 : CancelToken cancelToken,
160 : });
161 :
162 : /// Handy method to make http PATCH request, which is a alias of [BaseDio.request].
163 : Future<Response<T>> patch<T>(
164 : String path, {
165 : data,
166 : Map<String, dynamic> queryParameters,
167 : Options options,
168 : CancelToken cancelToken,
169 : ProgressCallback onSendProgress,
170 : ProgressCallback onReceiveProgress,
171 : });
172 :
173 : /// Handy method to make http PATCH request, which is a alias of [BaseDio.request].
174 : Future<Response<T>> patchUri<T>(
175 : Uri uri, {
176 : data,
177 : Options options,
178 : CancelToken cancelToken,
179 : ProgressCallback onSendProgress,
180 : ProgressCallback onReceiveProgress,
181 : });
182 :
183 : /// Assure the final future state is succeed!
184 : Future<Response<T>> resolve<T>(response);
185 :
186 : /// Assure the final future state is failed!
187 : Future<Response<T>> reject<T>(err);
188 :
189 : /// Lock the current Dio instance.
190 : ///
191 : /// Dio will enqueue the incoming request tasks instead
192 : /// send them directly when [interceptor.request] is locked.
193 :
194 : void lock();
195 :
196 : /// Unlock the current Dio instance.
197 : ///
198 : /// Dio instance dequeue the request task。
199 : void unlock();
200 :
201 : ///Clear the current Dio instance waiting queue.
202 :
203 : void clear();
204 :
205 : /// Download the file and save it in local. The default http method is "GET",
206 : /// you can custom it by [Options.method].
207 : ///
208 : /// [urlPath]: The file url.
209 : ///
210 : /// [savePath]: The path to save the downloading file later. it can be a String or
211 : /// a callback:
212 : /// 1. A path with String type, eg "xs.jpg"
213 : /// 2. A callback `String Function(HttpHeaders responseHeaders)`; for example:
214 : /// ```dart
215 : /// await dio.download(url,(HttpHeaders responseHeaders){
216 : /// ...
217 : /// return "...";
218 : /// });
219 : /// ```
220 : ///
221 : /// [onReceiveProgress]: The callback to listen downloading progress.
222 : /// please refer to [ProgressCallback].
223 : ///
224 : /// [deleteOnError] Whether delete the file when error occurs. The default value is [true].
225 : ///
226 : /// [lengthHeader] : The real size of original file (not compressed).
227 : /// When file is compressed:
228 : /// 1. If this value is 'content-length', the `total` argument of `onProgress` will be -1
229 : /// 2. If this value is not 'content-length', maybe a custom header indicates the original
230 : /// file size , the `total` argument of `onProgress` will be this header value.
231 : ///
232 : /// you can also disable the compression by specifying the 'accept-encoding' header value as '*'
233 : /// to assure the value of `total` argument of `onProgress` is not -1. for example:
234 : ///
235 : /// await dio.download(url, "./example/flutter.svg",
236 : /// options: Options(headers: {HttpHeaders.acceptEncodingHeader: "*"}), // disable gzip
237 : /// onProgress: (received, total) {
238 : /// if (total != -1) {
239 : /// print((received / total * 100).toStringAsFixed(0) + "%");
240 : /// }
241 : /// });
242 :
243 : Future<Response> download(
244 : String urlPath,
245 : savePath, {
246 : ProgressCallback onReceiveProgress,
247 : Map<String, dynamic> queryParameters,
248 : CancelToken cancelToken,
249 : bool deleteOnError = true,
250 : String lengthHeader = Headers.contentLengthHeader,
251 : data,
252 : Options options,
253 : });
254 :
255 : /// Download the file and save it in local. The default http method is "GET",
256 : /// you can custom it by [Options.method].
257 : ///
258 : /// [uri]: The file url.
259 : ///
260 : /// [savePath]: The path to save the downloading file later. it can be a String or
261 : /// a callback:
262 : /// 1. A path with String type, eg "xs.jpg"
263 : /// 2. A callback `String Function(HttpHeaders responseHeaders)`; for example:
264 : /// ```dart
265 : /// await dio.downloadUri(uri,(HttpHeaders responseHeaders){
266 : /// ...
267 : /// return "...";
268 : /// });
269 : /// ```
270 : ///
271 : /// [onReceiveProgress]: The callback to listen downloading progress.
272 : /// please refer to [ProgressCallback].
273 : ///
274 : /// [lengthHeader] : The real size of original file (not compressed).
275 : /// When file is compressed:
276 : /// 1. If this value is 'content-length', the `total` argument of `onProgress` will be -1
277 : /// 2. If this value is not 'content-length', maybe a custom header indicates the original
278 : /// file size , the `total` argument of `onProgress` will be this header value.
279 : ///
280 : /// you can also disable the compression by specifying the 'accept-encoding' header value as '*'
281 : /// to assure the value of `total` argument of `onProgress` is not -1. for example:
282 : ///
283 : /// await dio.downloadUri(uri, "./example/flutter.svg",
284 : /// options: Options(headers: {HttpHeaders.acceptEncodingHeader: "*"}), // disable gzip
285 : /// onProgress: (received, total) {
286 : /// if (total != -1) {
287 : /// print((received / total * 100).toStringAsFixed(0) + "%");
288 : /// }
289 : /// });
290 : Future<Response> downloadUri(
291 : Uri uri,
292 : savePath, {
293 : ProgressCallback onReceiveProgress,
294 : CancelToken cancelToken,
295 : bool deleteOnError = true,
296 : String lengthHeader = Headers.contentLengthHeader,
297 : data,
298 : Options options,
299 : });
300 :
301 : /// Make http request with options.
302 : ///
303 : /// [path] The url path.
304 : /// [data] The request data
305 : /// [options] The request options.
306 :
307 : Future<Response<T>> request<T>(
308 : String path, {
309 : data,
310 : Map<String, dynamic> queryParameters,
311 : CancelToken cancelToken,
312 : Options options,
313 : ProgressCallback onSendProgress,
314 : ProgressCallback onReceiveProgress,
315 : });
316 :
317 : /// Make http request with options.
318 : ///
319 : /// [uri] The uri.
320 : /// [data] The request data
321 : /// [options] The request options.
322 : Future<Response<T>> requestUri<T>(
323 : Uri uri, {
324 : data,
325 : CancelToken cancelToken,
326 : Options options,
327 : ProgressCallback onSendProgress,
328 : ProgressCallback onReceiveProgress,
329 : });
330 : }
331 :
332 : abstract class DioMixin implements Dio {
333 : /// Default Request config. More see [BaseOptions].
334 : @override
335 : BaseOptions options;
336 :
337 : /// Each Dio instance has a interceptor by which you can intercept requests or responses before they are
338 : /// handled by `then` or `catchError`. the [interceptor] field
339 : /// contains a [RequestInterceptor] and a [ResponseInterceptor] instance.
340 : final Interceptors _interceptors = Interceptors();
341 :
342 5 : @override
343 5 : Interceptors get interceptors => _interceptors;
344 :
345 : @override
346 : HttpClientAdapter httpClientAdapter;
347 :
348 : @override
349 : Transformer transformer = DefaultTransformer();
350 :
351 : bool _closed = false;
352 :
353 0 : @override
354 : void close({bool force = false}) {
355 0 : _closed = true;
356 0 : httpClientAdapter.close(force: force);
357 : }
358 :
359 : /// Handy method to make http GET request, which is a alias of [BaseDio.request].
360 4 : @override
361 : Future<Response<T>> get<T>(
362 : String path, {
363 : Map<String, dynamic> queryParameters,
364 : Options options,
365 : CancelToken cancelToken,
366 : ProgressCallback onReceiveProgress,
367 : }) {
368 4 : return request<T>(
369 : path,
370 : queryParameters: queryParameters,
371 4 : options: checkOptions('GET', options),
372 : onReceiveProgress: onReceiveProgress,
373 : cancelToken: cancelToken,
374 : );
375 : }
376 :
377 : /// Handy method to make http GET request, which is a alias of [BaseDio.request].
378 1 : @override
379 : Future<Response<T>> getUri<T>(
380 : Uri uri, {
381 : Options options,
382 : CancelToken cancelToken,
383 : ProgressCallback onReceiveProgress,
384 : }) {
385 1 : return requestUri<T>(
386 : uri,
387 1 : options: checkOptions('GET', options),
388 : onReceiveProgress: onReceiveProgress,
389 : cancelToken: cancelToken,
390 : );
391 : }
392 :
393 : /// Handy method to make http POST request, which is a alias of [BaseDio.request].
394 1 : @override
395 : Future<Response<T>> post<T>(
396 : String path, {
397 : data,
398 : Map<String, dynamic> queryParameters,
399 : Options options,
400 : CancelToken cancelToken,
401 : ProgressCallback onSendProgress,
402 : ProgressCallback onReceiveProgress,
403 : }) {
404 1 : return request<T>(
405 : path,
406 : data: data,
407 1 : options: checkOptions('POST', options),
408 : queryParameters: queryParameters,
409 : cancelToken: cancelToken,
410 : onSendProgress: onSendProgress,
411 : onReceiveProgress: onReceiveProgress,
412 : );
413 : }
414 :
415 : /// Handy method to make http POST request, which is a alias of [BaseDio.request].
416 1 : @override
417 : Future<Response<T>> postUri<T>(
418 : Uri uri, {
419 : data,
420 : Options options,
421 : CancelToken cancelToken,
422 : ProgressCallback onSendProgress,
423 : ProgressCallback onReceiveProgress,
424 : }) {
425 1 : return requestUri<T>(
426 : uri,
427 : data: data,
428 1 : options: checkOptions('POST', options),
429 : cancelToken: cancelToken,
430 : onSendProgress: onSendProgress,
431 : onReceiveProgress: onReceiveProgress,
432 : );
433 : }
434 :
435 : /// Handy method to make http PUT request, which is a alias of [BaseDio.request].
436 1 : @override
437 : Future<Response<T>> put<T>(
438 : String path, {
439 : data,
440 : Map<String, dynamic> queryParameters,
441 : Options options,
442 : CancelToken cancelToken,
443 : ProgressCallback onSendProgress,
444 : ProgressCallback onReceiveProgress,
445 : }) {
446 1 : return request<T>(
447 : path,
448 : data: data,
449 : queryParameters: queryParameters,
450 1 : options: checkOptions('PUT', options),
451 : cancelToken: cancelToken,
452 : onSendProgress: onSendProgress,
453 : onReceiveProgress: onReceiveProgress,
454 : );
455 : }
456 :
457 : /// Handy method to make http PUT request, which is a alias of [BaseDio.request].
458 1 : @override
459 : Future<Response<T>> putUri<T>(
460 : Uri uri, {
461 : data,
462 : Options options,
463 : CancelToken cancelToken,
464 : ProgressCallback onSendProgress,
465 : ProgressCallback onReceiveProgress,
466 : }) {
467 1 : return requestUri<T>(
468 : uri,
469 : data: data,
470 1 : options: checkOptions('PUT', options),
471 : cancelToken: cancelToken,
472 : onSendProgress: onSendProgress,
473 : onReceiveProgress: onReceiveProgress,
474 : );
475 : }
476 :
477 : /// Handy method to make http HEAD request, which is a alias of [BaseDio.request].
478 0 : @override
479 : Future<Response<T>> head<T>(
480 : String path, {
481 : data,
482 : Map<String, dynamic> queryParameters,
483 : Options options,
484 : CancelToken cancelToken,
485 : }) {
486 0 : return request<T>(
487 : path,
488 : data: data,
489 : queryParameters: queryParameters,
490 0 : options: checkOptions('HEAD', options),
491 : cancelToken: cancelToken,
492 : );
493 : }
494 :
495 : /// Handy method to make http HEAD request, which is a alias of [BaseDio.request].
496 0 : @override
497 : Future<Response<T>> headUri<T>(
498 : Uri uri, {
499 : data,
500 : Options options,
501 : CancelToken cancelToken,
502 : }) {
503 0 : return requestUri<T>(
504 : uri,
505 : data: data,
506 0 : options: checkOptions('HEAD', options),
507 : cancelToken: cancelToken,
508 : );
509 : }
510 :
511 : /// Handy method to make http DELETE request, which is a alias of [BaseDio.request].
512 1 : @override
513 : Future<Response<T>> delete<T>(
514 : String path, {
515 : data,
516 : Map<String, dynamic> queryParameters,
517 : Options options,
518 : CancelToken cancelToken,
519 : }) {
520 1 : return request<T>(
521 : path,
522 : data: data,
523 : queryParameters: queryParameters,
524 1 : options: checkOptions('DELETE', options),
525 : cancelToken: cancelToken,
526 : );
527 : }
528 :
529 : /// Handy method to make http DELETE request, which is a alias of [BaseDio.request].
530 1 : @override
531 : Future<Response<T>> deleteUri<T>(
532 : Uri uri, {
533 : data,
534 : Options options,
535 : CancelToken cancelToken,
536 : }) {
537 1 : return requestUri<T>(
538 : uri,
539 : data: data,
540 1 : options: checkOptions('DELETE', options),
541 : cancelToken: cancelToken,
542 : );
543 : }
544 :
545 : /// Handy method to make http PATCH request, which is a alias of [BaseDio.request].
546 1 : @override
547 : Future<Response<T>> patch<T>(
548 : String path, {
549 : data,
550 : Map<String, dynamic> queryParameters,
551 : Options options,
552 : CancelToken cancelToken,
553 : ProgressCallback onSendProgress,
554 : ProgressCallback onReceiveProgress,
555 : }) {
556 1 : return request<T>(
557 : path,
558 : data: data,
559 : queryParameters: queryParameters,
560 1 : options: checkOptions('PATCH', options),
561 : cancelToken: cancelToken,
562 : onSendProgress: onSendProgress,
563 : onReceiveProgress: onReceiveProgress,
564 : );
565 : }
566 :
567 : /// Handy method to make http PATCH request, which is a alias of [BaseDio.request].
568 1 : @override
569 : Future<Response<T>> patchUri<T>(
570 : Uri uri, {
571 : data,
572 : Options options,
573 : CancelToken cancelToken,
574 : ProgressCallback onSendProgress,
575 : ProgressCallback onReceiveProgress,
576 : }) {
577 1 : return requestUri<T>(
578 : uri,
579 : data: data,
580 1 : options: checkOptions('PATCH', options),
581 : cancelToken: cancelToken,
582 : onSendProgress: onSendProgress,
583 : onReceiveProgress: onReceiveProgress,
584 : );
585 : }
586 :
587 : /// Assure the final future state is succeed!
588 1 : @override
589 : Future<Response<T>> resolve<T>(response) {
590 1 : if (response is! Future) {
591 1 : response = Future.value(response);
592 : }
593 2 : return response.then<Response<T>>((data) {
594 1 : return assureResponse<T>(data);
595 0 : }, onError: (err) {
596 : // transform 'error' to 'success'
597 0 : return assureResponse<T>(err);
598 : });
599 : }
600 :
601 : /// Assure the final future state is failed!
602 1 : @override
603 : Future<Response<T>> reject<T>(err) {
604 1 : if (err is! Future) {
605 1 : err = Future.error(err);
606 : }
607 1 : return err.then<Response<T>>((v) {
608 : // transform 'success' to 'error'
609 0 : throw assureDioError(v);
610 1 : }, onError: (e) {
611 1 : throw assureDioError(e);
612 : });
613 : }
614 :
615 : /// Lock the current Dio instance.
616 : ///
617 : /// Dio will enqueue the incoming request tasks instead
618 : /// send them directly when [interceptor.request] is locked.
619 1 : @override
620 : void lock() {
621 3 : interceptors.requestLock.lock();
622 : }
623 :
624 : /// Unlock the current Dio instance.
625 : ///
626 : /// Dio instance dequeue the request task。
627 1 : @override
628 : void unlock() {
629 3 : interceptors.requestLock.unlock();
630 : }
631 :
632 : ///Clear the current Dio instance waiting queue.
633 0 : @override
634 : void clear() {
635 0 : interceptors.requestLock.clear();
636 : }
637 :
638 : /// Download the file and save it in local. The default http method is 'GET',
639 : /// you can custom it by [Options.method].
640 : ///
641 : /// [urlPath]: The file url.
642 : ///
643 : /// [savePath]: The path to save the downloading file later. it can be a String or
644 : /// a callback:
645 : /// 1. A path with String type, eg 'xs.jpg'
646 : /// 2. A callback `String Function(HttpHeaders responseHeaders)`; for example:
647 : /// ```dart
648 : /// await dio.download(url,(HttpHeaders responseHeaders){
649 : /// ...
650 : /// return '...';
651 : /// });
652 : /// ```
653 : ///
654 : /// [onReceiveProgress]: The callback to listen downloading progress.
655 : /// please refer to [ProgressCallback].
656 : ///
657 : /// [deleteOnError] Whether delete the file when error occurs. The default value is [true].
658 : ///
659 : /// [lengthHeader] : The real size of original file (not compressed).
660 : /// When file is compressed:
661 : /// 1. If this value is 'content-length', the `total` argument of `onProgress` will be -1
662 : /// 2. If this value is not 'content-length', maybe a custom header indicates the original
663 : /// file size , the `total` argument of `onProgress` will be this header value.
664 : ///
665 : /// you can also disable the compression by specifying the 'accept-encoding' header value as '*'
666 : /// to assure the value of `total` argument of `onProgress` is not -1. for example:
667 : ///
668 : /// await dio.download(url, './example/flutter.svg',
669 : /// options: Options(headers: {HttpHeaders.acceptEncodingHeader: '*'}), // disable gzip
670 : /// onProgress: (received, total) {
671 : /// if (total != -1) {
672 : /// print((received / total * 100).toStringAsFixed(0) + '%');
673 : /// }
674 : /// });
675 :
676 : @override
677 0 : Future<Response> download(
678 : String urlPath,
679 : savePath, {
680 : ProgressCallback onReceiveProgress,
681 : Map<String, dynamic> queryParameters,
682 : CancelToken cancelToken,
683 : bool deleteOnError = true,
684 : String lengthHeader = Headers.contentLengthHeader,
685 : data,
686 : Options options,
687 : }) async {
688 0 : throw UnsupportedError('Unsupport download API in browser');
689 : }
690 :
691 : /// Download the file and save it in local. The default http method is 'GET',
692 : /// you can custom it by [Options.method].
693 : ///
694 : /// [uri]: The file url.
695 : ///
696 : /// [savePath]: The path to save the downloading file later. it can be a String or
697 : /// a callback:
698 : /// 1. A path with String type, eg 'xs.jpg'
699 : /// 2. A callback `String Function(HttpHeaders responseHeaders)`; for example:
700 : /// ```dart
701 : /// await dio.downloadUri(uri,(HttpHeaders responseHeaders){
702 : /// ...
703 : /// return '...';
704 : /// });
705 : /// ```
706 : ///
707 : /// [onReceiveProgress]: The callback to listen downloading progress.
708 : /// please refer to [ProgressCallback].
709 : ///
710 : /// [lengthHeader] : The real size of original file (not compressed).
711 : /// When file is compressed:
712 : /// 1. If this value is 'content-length', the `total` argument of `onProgress` will be -1
713 : /// 2. If this value is not 'content-length', maybe a custom header indicates the original
714 : /// file size , the `total` argument of `onProgress` will be this header value.
715 : ///
716 : /// you can also disable the compression by specifying the 'accept-encoding' header value as '*'
717 : /// to assure the value of `total` argument of `onProgress` is not -1. for example:
718 : ///
719 : /// await dio.downloadUri(uri, './example/flutter.svg',
720 : /// options: Options(headers: {HttpHeaders.acceptEncodingHeader: '*'}), // disable gzip
721 : /// onProgress: (received, total) {
722 : /// if (total != -1) {
723 : /// print((received / total * 100).toStringAsFixed(0) + '%');
724 : /// }
725 : /// });
726 0 : @override
727 : Future<Response> downloadUri(
728 : Uri uri,
729 : savePath, {
730 : ProgressCallback onReceiveProgress,
731 : CancelToken cancelToken,
732 : bool deleteOnError = true,
733 : String lengthHeader = Headers.contentLengthHeader,
734 : data,
735 : Options options,
736 : }) {
737 0 : return download(
738 0 : uri.toString(),
739 : savePath,
740 : onReceiveProgress: onReceiveProgress,
741 : lengthHeader: lengthHeader,
742 : deleteOnError: deleteOnError,
743 : cancelToken: cancelToken,
744 : data: data,
745 : options: options,
746 : );
747 : }
748 :
749 : /// Make http request with options.
750 : ///
751 : /// [path] The url path.
752 : /// [data] The request data
753 : /// [options] The request options.
754 : @override
755 5 : Future<Response<T>> request<T>(
756 : String path, {
757 : data,
758 : Map<String, dynamic> queryParameters,
759 : CancelToken cancelToken,
760 : Options options,
761 : ProgressCallback onSendProgress,
762 : ProgressCallback onReceiveProgress,
763 : }) async {
764 5 : return _request<T>(
765 : path,
766 : data: data,
767 : queryParameters: queryParameters,
768 : cancelToken: cancelToken,
769 : options: options,
770 : onSendProgress: onSendProgress,
771 : onReceiveProgress: onReceiveProgress,
772 : );
773 : }
774 :
775 : /// Make http request with options.
776 : ///
777 : /// [uri] The uri.
778 : /// [data] The request data
779 : /// [options] The request options.
780 1 : @override
781 : Future<Response<T>> requestUri<T>(
782 : Uri uri, {
783 : data,
784 : CancelToken cancelToken,
785 : Options options,
786 : ProgressCallback onSendProgress,
787 : ProgressCallback onReceiveProgress,
788 : }) {
789 1 : return request(
790 1 : uri.toString(),
791 : data: data,
792 : cancelToken: cancelToken,
793 : options: options,
794 : onSendProgress: onSendProgress,
795 : onReceiveProgress: onReceiveProgress,
796 : );
797 : }
798 :
799 5 : Future<Response<T>> _request<T>(
800 : String path, {
801 : data,
802 : Map<String, dynamic> queryParameters,
803 : CancelToken cancelToken,
804 : Options options,
805 : ProgressCallback onSendProgress,
806 : ProgressCallback onReceiveProgress,
807 : }) async {
808 5 : if (_closed) {
809 0 : throw DioError(error: "Dio can't establish new connection after closed.");
810 : }
811 0 : options ??= Options();
812 5 : if (options is RequestOptions) {
813 0 : data = data ?? options.data;
814 0 : queryParameters = queryParameters ?? options.queryParameters;
815 0 : cancelToken = cancelToken ?? options.cancelToken;
816 0 : onSendProgress = onSendProgress ?? options.onSendProgress;
817 0 : onReceiveProgress = onReceiveProgress ?? options.onReceiveProgress;
818 : }
819 5 : var requestOptions = mergeOptions(options, path, data, queryParameters);
820 5 : requestOptions.onReceiveProgress = onReceiveProgress;
821 5 : requestOptions.onSendProgress = onSendProgress;
822 5 : requestOptions.cancelToken = cancelToken;
823 5 : if (T != dynamic &&
824 4 : !(requestOptions.responseType == ResponseType.bytes ||
825 4 : requestOptions.responseType == ResponseType.stream)) {
826 1 : if (T == String) {
827 1 : requestOptions.responseType = ResponseType.plain;
828 : } else {
829 1 : requestOptions.responseType = ResponseType.json;
830 : }
831 : }
832 :
833 15 : bool _isErrorOrException(t) => t is Exception || t is Error;
834 :
835 : // Convert the request/response interceptor to a functional callback in which
836 : // we can handle the return value of interceptor callback.
837 5 : Function _interceptorWrapper(interceptor, bool request) {
838 5 : return (data) async {
839 7 : var type = request ? (data is RequestOptions) : (data is Response);
840 : var lock =
841 14 : request ? interceptors.requestLock : interceptors.responseLock;
842 5 : if (_isErrorOrException(data) || type) {
843 5 : return listenCancelForAsyncTask(
844 : cancelToken,
845 10 : Future(() {
846 10 : return checkIfNeedEnqueue(lock, () {
847 : if (type) {
848 4 : if (!request) data.request = data.request ?? requestOptions;
849 13 : return interceptor(data).then((e) => e ?? data);
850 : } else {
851 1 : throw assureDioError(data, requestOptions);
852 : }
853 : });
854 : }),
855 : );
856 : } else {
857 1 : return assureResponse(data, requestOptions);
858 : }
859 : };
860 : }
861 :
862 : // Convert the error interceptor to a functional callback in which
863 : // we can handle the return value of interceptor callback.
864 2 : Function _errorInterceptorWrapper(errInterceptor) {
865 2 : return (err) async {
866 2 : if (err is! Response) {
867 6 : var _e = await errInterceptor(assureDioError(err, requestOptions));
868 2 : if (_e is! Response) {
869 2 : throw assureDioError(_e ?? err, requestOptions);
870 : }
871 : err = _e;
872 : }
873 : // err is a Response instance
874 : return err;
875 : };
876 : }
877 :
878 : // Build a request flow in which the processors(interceptors)
879 : // execute in FIFO order.
880 :
881 : // Start the request flow
882 : Future future;
883 5 : future = Future.value(requestOptions);
884 : // Add request interceptors to request flow
885 12 : interceptors.forEach((Interceptor interceptor) {
886 6 : future = future.then(_interceptorWrapper(interceptor.onRequest, true));
887 : });
888 :
889 : // Add dispatching callback to request flow
890 15 : future = future.then(_interceptorWrapper(_dispatchRequest, true));
891 :
892 : // Add response interceptors to request flow
893 12 : interceptors.forEach((Interceptor interceptor) {
894 6 : future = future.then(_interceptorWrapper(interceptor.onResponse, false));
895 : });
896 :
897 : // Add error handlers to request flow
898 12 : interceptors.forEach((Interceptor interceptor) {
899 6 : future = future.catchError(_errorInterceptorWrapper(interceptor.onError));
900 : });
901 :
902 : // Normalize errors, we convert error to the DioError
903 8 : return future.then<Response<T>>((data) {
904 3 : return assureResponse<T>(data);
905 10 : }).catchError((err) {
906 5 : if (err == null || _isErrorOrException(err)) {
907 5 : throw assureDioError(err, requestOptions);
908 : }
909 0 : return assureResponse<T>(err, requestOptions);
910 : });
911 : }
912 :
913 : // Initiate Http requests
914 5 : Future<Response<T>> _dispatchRequest<T>(RequestOptions options) async {
915 5 : var cancelToken = options.cancelToken;
916 : ResponseBody responseBody;
917 : try {
918 10 : var stream = await _transformData(options);
919 15 : responseBody = await httpClientAdapter.fetch(
920 : options,
921 : stream,
922 2 : cancelToken?.whenCancel,
923 : );
924 7 : responseBody.headers = responseBody.headers ?? {};
925 6 : var headers = Headers.fromMap(responseBody.headers ?? {});
926 3 : var ret = Response(
927 : headers: headers,
928 : request: options,
929 4 : redirects: responseBody.redirects ?? [],
930 3 : isRedirect: responseBody.isRedirect,
931 3 : statusCode: responseBody.statusCode,
932 3 : statusMessage: responseBody.statusMessage,
933 3 : extra: responseBody.extra,
934 : );
935 6 : var statusOk = options.validateStatus(responseBody.statusCode);
936 3 : if (statusOk || options.receiveDataWhenStatusError) {
937 3 : var forceConvert = !(T == dynamic || T == String) &&
938 0 : !(options.responseType == ResponseType.bytes ||
939 0 : options.responseType == ResponseType.stream);
940 : String contentType;
941 : if (forceConvert) {
942 0 : contentType = headers.value(Headers.contentTypeHeader);
943 0 : headers.set(Headers.contentTypeHeader, Headers.jsonContentType);
944 : }
945 12 : ret.data = await transformer.transformResponse(options, responseBody);
946 : if (forceConvert) {
947 0 : headers.set(Headers.contentTypeHeader, contentType);
948 : }
949 : } else {
950 4 : await responseBody.stream.listen(null).cancel();
951 : }
952 3 : checkCancelled(cancelToken);
953 : if (statusOk) {
954 12 : return checkIfNeedEnqueue(interceptors.responseLock, () => ret);
955 : } else {
956 3 : throw DioError(
957 : response: ret,
958 6 : error: 'Http status error [${responseBody.statusCode}]',
959 : type: DioErrorType.RESPONSE,
960 : );
961 : }
962 : } catch (e) {
963 5 : throw assureDioError(e, options);
964 : }
965 : }
966 :
967 : // If the request has been cancelled, stop request and throw error.
968 3 : void checkCancelled(CancelToken cancelToken) {
969 1 : if (cancelToken != null && cancelToken.cancelError != null) {
970 0 : throw cancelToken.cancelError;
971 : }
972 : }
973 :
974 5 : Future<T> listenCancelForAsyncTask<T>(
975 : CancelToken cancelToken, Future<T> future) {
976 10 : return Future.any([
977 : if (cancelToken != null)
978 8 : cancelToken.whenCancel.then((e) => throw cancelToken.cancelError),
979 5 : future,
980 : ]);
981 : }
982 :
983 5 : Future<Stream<Uint8List>> _transformData(RequestOptions options) async {
984 5 : var data = options.data;
985 : List<int> bytes;
986 : Stream<List<int>> stream;
987 : if (data != null &&
988 3 : ['POST', 'PUT', 'PATCH', 'DELETE'].contains(options.method)) {
989 : // Handle the FormData
990 : int length;
991 1 : if (data is Stream) {
992 0 : assert(data is Stream<List>,
993 0 : 'Stream type must be `Stream<List>`, but ${data.runtimeType} is found.');
994 : stream = data;
995 0 : options.headers.keys.any((String key) {
996 0 : if (key.toLowerCase() == Headers.contentLengthHeader) {
997 0 : length = int.parse(options.headers[key].toString());
998 : return true;
999 : }
1000 : return false;
1001 : });
1002 1 : } else if (data is FormData) {
1003 0 : if (data is FormData) {
1004 0 : options.headers[Headers.contentTypeHeader] =
1005 0 : 'multipart/form-data; boundary=${data.boundary}';
1006 : }
1007 0 : stream = data.finalize();
1008 0 : length = data.length;
1009 : } else {
1010 : // Call request transformer.
1011 3 : var _data = await transformer.transformRequest(options);
1012 1 : if (options.requestEncoder != null) {
1013 0 : bytes = options.requestEncoder(_data, options);
1014 : } else {
1015 : //Default convert to utf8
1016 1 : bytes = utf8.encode(_data);
1017 : }
1018 : // support data sending progress
1019 1 : length = bytes.length;
1020 :
1021 1 : var group = <List<int>>[];
1022 : const size = 1024;
1023 3 : var groupCount = (bytes.length / size).ceil();
1024 2 : for (var i = 0; i < groupCount; ++i) {
1025 1 : var start = i * size;
1026 16 : group.add(bytes.sublist(start, math.min(start + size, bytes.length)));
1027 : }
1028 1 : stream = Stream.fromIterable(group);
1029 : }
1030 :
1031 : if (length != null) {
1032 3 : options.headers[Headers.contentLengthHeader] = length.toString();
1033 : }
1034 : var complete = 0;
1035 2 : var byteStream = stream.transform<Uint8List>(StreamTransformer.fromHandlers(
1036 1 : handleData: (data, sink) {
1037 1 : if (options.cancelToken != null && options.cancelToken.isCancelled) {
1038 : sink
1039 0 : ..addError(options.cancelToken.cancelError)
1040 0 : ..close();
1041 : } else {
1042 2 : sink.add(Uint8List.fromList(data));
1043 : if (length != null) {
1044 2 : complete += data.length;
1045 1 : if (options.onSendProgress != null) {
1046 0 : options.onSendProgress(complete, length);
1047 : }
1048 : }
1049 : }
1050 : },
1051 : ));
1052 2 : if (options.sendTimeout > 0) {
1053 0 : byteStream.timeout(Duration(milliseconds: options.sendTimeout),
1054 0 : onTimeout: (sink) {
1055 0 : sink.addError(DioError(
1056 : request: options,
1057 0 : error: 'Sending timeout[${options.connectTimeout}ms]',
1058 : type: DioErrorType.SEND_TIMEOUT,
1059 : ));
1060 0 : sink.close();
1061 : });
1062 : }
1063 : return byteStream;
1064 : } else {
1065 10 : options.headers.remove(Headers.contentTypeHeader);
1066 : }
1067 : return null;
1068 : }
1069 :
1070 5 : RequestOptions mergeOptions(
1071 : Options opt, String url, data, Map<String, dynamic> queryParameters) {
1072 20 : var query = (Map<String, dynamic>.from(options.queryParameters ?? {}))
1073 10 : ..addAll(queryParameters ?? {});
1074 5 : final optBaseUrl = (opt is RequestOptions) ? opt.baseUrl : null;
1075 : final optConnectTimeout =
1076 5 : (opt is RequestOptions) ? opt.connectTimeout : null;
1077 5 : return RequestOptions(
1078 10 : method: (opt.method ?? options.method)?.toUpperCase() ?? 'GET',
1079 25 : headers: (Map.from(options.headers))..addAll(opt.headers),
1080 10 : baseUrl: optBaseUrl ?? options.baseUrl ?? '',
1081 : path: url,
1082 : data: data,
1083 10 : connectTimeout: optConnectTimeout ?? options.connectTimeout ?? 0,
1084 15 : sendTimeout: opt.sendTimeout ?? options.sendTimeout ?? 0,
1085 15 : receiveTimeout: opt.receiveTimeout ?? options.receiveTimeout ?? 0,
1086 : responseType:
1087 13 : opt.responseType ?? options.responseType ?? ResponseType.json,
1088 25 : extra: (Map.from(options.extra))..addAll(opt.extra),
1089 : contentType:
1090 15 : opt.contentType ?? options.contentType ?? Headers.jsonContentType,
1091 5 : validateStatus: opt.validateStatus ??
1092 10 : options.validateStatus ??
1093 3 : (int status) {
1094 9 : return status >= 200 && status < 300 || status == 304;
1095 : },
1096 5 : receiveDataWhenStatusError: opt.receiveDataWhenStatusError ??
1097 10 : options.receiveDataWhenStatusError ??
1098 : true,
1099 15 : followRedirects: opt.followRedirects ?? options.followRedirects ?? true,
1100 15 : maxRedirects: opt.maxRedirects ?? options.maxRedirects ?? 5,
1101 : queryParameters: query,
1102 15 : requestEncoder: opt.requestEncoder ?? options.requestEncoder,
1103 15 : responseDecoder: opt.responseDecoder ?? options.responseDecoder,
1104 : );
1105 : }
1106 :
1107 5 : Options checkOptions(method, options) {
1108 5 : options ??= Options();
1109 5 : options.method = method;
1110 : return options;
1111 : }
1112 :
1113 5 : FutureOr checkIfNeedEnqueue(Lock lock, EnqueueCallback callback) {
1114 5 : if (lock.locked) {
1115 1 : return lock.enqueue(callback);
1116 : } else {
1117 5 : return callback();
1118 : }
1119 : }
1120 :
1121 5 : DioError assureDioError(err, [RequestOptions requestOptions]) {
1122 : DioError dioError;
1123 5 : if (err is DioError) {
1124 : dioError = err;
1125 : } else {
1126 3 : dioError = DioError(error: err);
1127 : }
1128 10 : dioError.request = dioError.request ?? requestOptions;
1129 : return dioError;
1130 : }
1131 :
1132 3 : Response<T> assureResponse<T>(response, [RequestOptions requestOptions]) {
1133 3 : if (response is Response<T>) {
1134 4 : response.request = response.request ?? requestOptions;
1135 3 : } else if (response is! Response) {
1136 1 : response = Response<T>(data: response, request: requestOptions);
1137 : } else {
1138 2 : T data = response.data;
1139 2 : response = Response<T>(
1140 : data: data,
1141 2 : headers: response.headers,
1142 2 : request: response.request,
1143 2 : statusCode: response.statusCode,
1144 2 : isRedirect: response.isRedirect,
1145 2 : redirects: response.redirects,
1146 2 : statusMessage: response.statusMessage,
1147 : );
1148 : }
1149 : return response;
1150 : }
1151 : }
|