├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── FORK.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── dio_http ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example │ └── dio_http.dart ├── lib │ ├── adapter.dart │ ├── adapter_browser.dart │ ├── browser_imp.dart │ ├── dio_http.dart │ ├── native_imp.dart │ └── src │ │ ├── adapter.dart │ │ ├── adapters │ │ ├── browser_adapter.dart │ │ └── io_adapter.dart │ │ ├── cancel_token.dart │ │ ├── dio.dart │ │ ├── dio_error.dart │ │ ├── dio_mixin.dart │ │ ├── entry │ │ ├── dio_for_browser.dart │ │ └── dio_for_native.dart │ │ ├── entry_stub.dart │ │ ├── form_data.dart │ │ ├── headers.dart │ │ ├── interceptor.dart │ │ ├── interceptors │ │ └── log.dart │ │ ├── multipart_file.dart │ │ ├── multipart_file_io.dart │ │ ├── multipart_file_stub.dart │ │ ├── options.dart │ │ ├── parameter.dart │ │ ├── redirect_record.dart │ │ ├── response.dart │ │ ├── transformer.dart │ │ └── utils.dart ├── pubspec.yaml └── test │ ├── _formdata │ ├── _testfile │ ├── basic_test.dart │ ├── download_test.dart │ ├── echo_adapter.dart │ ├── encoding_test.dart │ ├── exception_test.dart │ ├── formdata_test.dart │ ├── interceptor_test.dart │ ├── mock_adapter.dart │ ├── options_test.dart │ ├── request_test.dart │ ├── test.jpg │ ├── upload_stream_test.dart │ └── utils.dart ├── doc └── custom.html ├── example ├── adapter.dart ├── bee.mp4 ├── cancel_request.dart ├── cookie_mgr.dart ├── custom_cache_interceptor.dart ├── dio.dart ├── download.dart ├── download_with_trunks.dart ├── extend_dio.dart ├── flutter_app │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java │ │ │ │ │ └── club │ │ │ │ │ │ └── flutterchina │ │ │ │ │ │ └── flutter_app │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── res │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── values-night │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ └── settings.gradle │ ├── ios │ │ ├── .gitignore │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Runner.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── Runner │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── main.m │ ├── lib │ │ ├── http.dart │ │ ├── main.dart │ │ └── routes │ │ │ └── request.dart │ ├── pubspec.yaml │ └── test │ │ └── widget_test.dart ├── formdata.dart ├── generic.dart ├── http2_adapter.dart ├── interceptor_lock.dart ├── options.dart ├── post_stream_and_bytes.dart ├── proxy.dart ├── request_interceptors.dart ├── response_interceptor.dart ├── test.dart ├── transfomer.dart ├── upload.txt └── xx.png ├── issue_template.md ├── migration_to_4.x.md ├── plugins ├── cookie_manager │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── example │ │ └── example.dart │ ├── lib │ │ ├── dio_http_cookie_manager.dart │ │ └── src │ │ │ └── cookie_mgr.dart │ ├── pubspec.yaml │ └── test │ │ └── basic_test.dart └── http2_adapter │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── example │ └── example.dart │ ├── lib │ ├── dio_http2_adapter.dart │ └── src │ │ ├── client_setting.dart │ │ ├── connection_manager.dart │ │ ├── connection_manager_imp.dart │ │ └── http2_adapter.dart │ ├── pubspec.yaml │ └── test │ └── http2_test.dart ├── pubspec.yaml ├── test.sh └── test └── readme.md /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### New Issue Checklist 2 | 3 | - [ ] I have searched for a similar issue in the [project](https://github.com/dart-tools/dio_http/issues) and found none 4 | 5 | ### Issue Info 6 | 7 | | Info | Value | | 8 | | ------------------------------ | ----------------------------------------------------- | ---- | 9 | | Platform Name | e.g. flutter / ios / android | | 10 | | Platform Version | e.g. 1.5.0 / 12.0 / 9.0 | | 11 | | Dio Version | e.g. 2.1.0 / 1.0.17 | | 12 | | Android Studio / Xcode Version | e.g. Android Studio 3.3.2 / Xcode 10.2.1 | | 13 | | Repro rate | e.g. all the time (100%) / sometimes x% / only once | | 14 | | Repro with our demo prj | e.g. does it happen with our demo project? | | 15 | | Demo project link | e.g. link to a demo project that highlights the issue | | 16 | 17 | ### Issue Description and Steps 18 | 19 | Please fill in the detailed description of the issue (full output of any stack trace, compiler error, ...) and the steps to reproduce the issue. 20 | 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### New Pull Request Checklist 2 | 3 | - [ ] I have read the [Documentation](https://pub.dartlang.org/packages/dio_http) 4 | - [ ] I have searched for a similar pull request in the [project](https://github.com/dart-tools/dio_http/pulls) and found none 5 | - [ ] I have updated this branch with the latest master to avoid conflicts (via merge from master or rebase) 6 | - [ ] I have added the required tests to prove the fix/feature I am adding 7 | - [ ] I have updated the documentation (if necessary) 8 | - [ ] I have run the tests and they pass 9 | 10 | This merge request fixes / refers to the following issues: ... 11 | 12 | ### Pull Request Description 13 | 14 | ... 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .packages 3 | .dart_tool/ 4 | .pub/ 5 | .idea/ 6 | .example/flutter.png 7 | build/ 8 | # Remove the following pattern if you wish to check in your lock file 9 | pubspec.lock 10 | 11 | # Directory created by dartdoc 12 | doc/api/ 13 | .cookies/ 14 | 15 | dio/.packages 16 | dio/.dart_tool/ 17 | dio/.pub/ 18 | dio/.idea/ 19 | dio/.exampl 20 | dio/coverage 21 | 22 | # plugins 23 | plugins/cookie_manager/.packages 24 | plugins/cookie_manager/.dart_tool/ 25 | plugins/cookie_manager/.pub/ 26 | plugins/cookie_manager/.idea/ 27 | plugins/cookie_manager/.exampl 28 | 29 | plugins/http2_adapter/.packages 30 | plugins/http2_adapter/.dart_tool/ 31 | plugins/http2_adapter/.pub/ 32 | plugins/http2_adapter/.idea/ 33 | plugins/http2_adapter/.exampl 34 | 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | 3 | dart: 4 | # Install the latest stable release 5 | - stable 6 | 7 | before_install: 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | script: 11 | - pwd 12 | - sh test.sh -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log: 2 | 3 | See https://github.com/dart-tools/dio_http/blob/master/dio_http/CHANGELOG.md 4 | -------------------------------------------------------------------------------- /FORK.md: -------------------------------------------------------------------------------- 1 | # Fork 2 | This package has been forked from [Dio](https://github.com/flutterchina/dio). 3 | 4 | Unfortunately, the original contributor of Dio ([Wendux](https://github.com/wendux)) went inactive for several months, and even though Dio is used in many locations, it's not stable enough yet so it needs active contributors. 5 | 6 | We reached out to Wendux through GitHub issues on the repository, sending an e-mail to his contact information on his GitHub profile, and even tried to contact one of his organization members of flutterchina. 7 | 8 | Unfortunately, we didn't manage to get in contact with him, so we decided to fork the package as dio_http. 9 | 10 | The goal is to be a drop in replacement for dio, while still fixing bugs and following the evolution of the dart language. 11 | 12 | We are still in the progress of evaluating stale issues and Pull Requests on the original dio repository, to hopefully fix them in the near future. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 wendux 4 | 5 | Copyright (c) 2021 dart-tools.dev 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | analyzer: 3 | enable-experiment: 4 | - non-nullable 5 | -------------------------------------------------------------------------------- /dio_http/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 5.0.4 2 | 3 | - Fix upload performance on Web 4 | 5 | # 5.0.3 6 | 7 | - Fix content type being overwritten when set in certain cases 8 | - Fix content-type assert being triggered when setting the content-type in certain cases 9 | 10 | # 5.0.2 11 | 12 | - Fix pub.dev not recognizing platform and example file 13 | 14 | # 5.0.1 15 | 16 | - LICENSE fix 17 | - Fix version badge in readme 18 | 19 | # 5.0.0 20 | 21 | - Forked package as dio_http. 22 | - Greatly improved download performance on Web. 23 | 24 | # 4.0.0 25 | 26 | stable version 27 | 28 | # 4.0.0-prev3 29 | - fix #1091 , #1089 , #1087 30 | 31 | # 4.0.0-prev2 32 | 33 | - fix #1082 and # 1076 34 | 35 | # 4.0.0-prev1 36 | 37 | **Interceptors:** Add `handler` for Interceptor APIs which can specify the subsequent interceptors processing logic more finely(whether to skip them or not) 38 | 39 | # 4.0.0-beta7 40 | 41 | - fix #1074 42 | 43 | # 4.0.0-beta6 44 | 45 | - fix #1070 46 | 47 | # 4.0.0-beta5 48 | 49 | - support ListParam 50 | 51 | # 4.0.0-beta4 52 | 53 | - fix #1060 54 | 55 | # 4.0.0-beta3 56 | 57 | - rename CollectionFormat to ListFormat 58 | - change default value of Options.listFormat from `mutiComptible` to `multi` 59 | - add upload_stream_test.dart 60 | 61 | # 4.0.0-beta2 62 | 63 | - support null-safety 64 | - add `CollectionFormat` configuration in Options 65 | - add `fetch` API for Dio 66 | - rename DioErrorType enums from uppercase to camel style 67 | - rename 'Options.merge' to 'Options.copyWith' 68 | 69 | # 3.0.10 2020.8.7 70 | 71 | 1. fix #877 'dio.interceptors.errorLock.lock()' 72 | 2. fix #851 73 | 3. fix #641 74 | 75 | 76 | # 3.0.9 2020.2.24 77 | 78 | - Add test cases 79 | 80 | # 3.0.8 2019.12.29 81 | 82 | - Code style improvement 83 | 84 | # 3.0.7 2019.11.25 85 | 86 | - Merge #574 : fix upload image header error, support both oss and other server 87 | 88 | # 3.0.6 2019.11.22 89 | 90 | - revert #562, and fixed #566 91 | 92 | # 3.0.5 2019.11.19 93 | 94 | - merge #557 #531 95 | 96 | # 3.0.4 2019.10.29 97 | 98 | - fix #502 #515 #523 99 | 100 | # 3.0.3 2019.10.1 101 | 102 | - fix encode bug 103 | 104 | # 3.0.2 2019.9.26 105 | 106 | - fix #474 #480 107 | 108 | # 3.0.2-dev.1 2019.9.20 109 | 110 | - fix #470 #471 111 | 112 | # 3.0.1 2019.9.20 113 | 114 | - Fix #467 115 | - Export `DioForNative` and `DioForBrowser` classes. 116 | 117 | # 3.0.0 118 | 119 | ### New features 120 | 121 | - Support Flutter Web. 122 | - Extract [CookieManager](https://github.com/flutterchina/dio/tree/master/plugins/cookie_manager) into a separate package(No need for Flutter Web). 123 | - Provides a [HTTP/2.0 HttpClientAdapter](https://github.com/flutterchina/dio/tree/master/plugins/http2_adapter). 124 | 125 | ### Change List 126 | 127 | - ~~Options.cookies~~ 128 | 129 | - ~~Options.connectionTimeout~~ ;We should config connection timed out in `BaseOptions`. For keep-alive reasons, not every request requires a separate connection。 130 | 131 | - `Options.followRedirects`、`Options.maxRedirects`、`Response.redirects` don't make sense in Flutter Web,because redirection can be automatically handled by browsers. 132 | 133 | - ~~FormData.from~~,use `FormData.fromMap` instead. 134 | 135 | - Delete ~~Formdata.asBytes()~~、~~Formdata.asBytesAsync()~~ , use `Formdata.readAsBytes()` instead. 136 | 137 | - Delete ~~`UploadFileInfo`~~ class, `MultipartFile` instead. 138 | 139 | - The return type of Interceptor's callback changes from `FutureOr` to `Future`. The reason is [here](https://dart.dev/guides/language/effective-dart/design#avoid-using-futureort-as-a-return-type) . 140 | 141 | - The type of `Response.headers` changes from `HttpHeaders` to `Headers`, because `HttpHeaders` is in "dart:io" library which is not supported in Flutter Web. 142 | 143 | 144 | 145 | 146 | 147 | 148 | # 2.1.16 149 | 150 | Add `deleteOnError` parameter to `downloadUri` 151 | 152 | # 2.1.14 153 | 154 | - fix #402 #385 #422 155 | 156 | # 2.1.13 157 | 158 | - fix #369 159 | 160 | # 2.1.12 161 | 162 | - fix #367 #365 163 | 164 | # 2.1.10 165 | 166 | - fix #360 167 | 168 | # 2.1.9 169 | 170 | - support flutter version>=1.8 (fix #357) 171 | 172 | 173 | # 2.1.8 174 | 175 | - fix #354 #312 176 | - Allow "delete" method with request body(#223) 177 | 178 | # 2.1.7 179 | 180 | - fix #321 #318 181 | 182 | # 2.1.6 183 | 184 | - fix https://github.com/flutterchina/dio/issues/316 185 | 186 | # 2.1.5 187 | 188 | - fix https://github.com/flutterchina/dio/issues/309 189 | 190 | # 2.1.4 191 | 192 | - Add `options.responseDecoder` 193 | - Make DioError catchable by implementing Exception instead of Error 194 | 195 | # 2.1.3 196 | 197 | Add `statusMessage` attribute for `Response` and `ResponseBody` 198 | 199 | # 2.1.2 200 | 201 | First Stable version for 2.x 202 | 203 | # 2.0 204 | 205 | **Refactor the Interceptors** 206 | - Support add Multiple Interceptors. 207 | - Add Log Interceptor 208 | - Add CookieManager Interceptor 209 | 210 | **API** 211 | - Support Uri 212 | - Support `queryParameters` for all request API 213 | - Modify the `get` API 214 | 215 | **Options** 216 | - Separate Options to three class: Options、BaseOptions、RequestOptions 217 | - Add `queryParameters` and `cookies` for BaseOptions 218 | 219 | **Adapter** 220 | - Abstract HttpClientAdapter layer. 221 | - Provide a DefaultHttpClientAdapter which make http requests by `dart:io:HttpClient` 222 | 223 | ## 0.1.8 224 | 225 | - change file name "TransFormer" to "Transformer" 226 | - change "dio.transFormer" to "dio.transformer" 227 | - change deprecated "UTF8" to "utf8" 228 | 229 | ## 0.1.5 230 | 231 | - add `clear` method for dio instance 232 | 233 | ## 0.1.4 234 | 235 | - fix `download` bugs 236 | 237 | ## 0.1.3 238 | 239 | - support upload files with Array 240 | - support create `HttpClient` by user self in `onHttpClientCreate` 241 | - support generic 242 | - bug fix 243 | 244 | ## 0.0.1 245 | 246 | - Initial version, created by Stagehand 247 | -------------------------------------------------------------------------------- /dio_http/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 wendux 4 | 5 | Copyright (c) 2021 dart-tools.dev 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. -------------------------------------------------------------------------------- /dio_http/example/dio_http.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | /// More examples see https://github.com/dart-tools/dio_http/tree/master/example 4 | void main() async { 5 | var dio = Dio(); 6 | final response = await dio.get('https://google.com'); 7 | print(response.data); 8 | } 9 | -------------------------------------------------------------------------------- /dio_http/lib/adapter.dart: -------------------------------------------------------------------------------- 1 | export 'src/adapters/io_adapter.dart' show DefaultHttpClientAdapter; 2 | -------------------------------------------------------------------------------- /dio_http/lib/adapter_browser.dart: -------------------------------------------------------------------------------- 1 | export 'src/adapters/browser_adapter.dart' show BrowserHttpClientAdapter; 2 | -------------------------------------------------------------------------------- /dio_http/lib/browser_imp.dart: -------------------------------------------------------------------------------- 1 | export 'src/entry/dio_for_browser.dart' show DioForBrowser; 2 | -------------------------------------------------------------------------------- /dio_http/lib/dio_http.dart: -------------------------------------------------------------------------------- 1 | /// A powerful Http client for Dart, which supports Interceptors, 2 | /// Global configuration, FormData, File downloading etc. and Dio is 3 | /// very easy to use. 4 | /// 5 | library dio_http; 6 | 7 | export 'src/dio.dart'; 8 | export 'src/dio_mixin.dart'; 9 | export 'src/form_data.dart'; 10 | export 'src/dio_error.dart'; 11 | export 'src/transformer.dart'; 12 | export 'src/interceptor.dart' hide InterceptorState, InterceptorResultType; 13 | export 'src/options.dart'; 14 | export 'src/response.dart'; 15 | export 'src/cancel_token.dart'; 16 | export 'src/multipart_file.dart'; 17 | export 'src/adapter.dart'; 18 | export 'src/headers.dart'; 19 | export 'src/interceptors/log.dart'; 20 | export 'src/redirect_record.dart'; 21 | -------------------------------------------------------------------------------- /dio_http/lib/native_imp.dart: -------------------------------------------------------------------------------- 1 | export 'src/entry/dio_for_native.dart' show DioForNative; 2 | -------------------------------------------------------------------------------- /dio_http/lib/src/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | import 'options.dart'; 4 | import 'redirect_record.dart'; 5 | 6 | typedef CancelWrapper = Future Function(Future); 7 | typedef VoidCallback = dynamic Function(); 8 | 9 | /// HttpAdapter is a bridge between Dio and HttpClient. 10 | /// 11 | /// Dio: Implements standard and friendly API for developer. 12 | /// 13 | /// HttpClient: It is the real object that makes Http 14 | /// requests. 15 | /// 16 | /// We can use any HttpClient not just "dart:io:HttpClient" to 17 | /// make the Http request. All we need is providing a [HttpClientAdapter]. 18 | /// 19 | /// The default HttpClientAdapter for Dio is [DefaultHttpClientAdapter]. 20 | /// 21 | /// ```dart 22 | /// dio.httpClientAdapter = DefaultHttpClientAdapter(); 23 | /// ``` 24 | abstract class HttpClientAdapter { 25 | /// We should implement this method to make real http requests. 26 | /// 27 | /// [options]: The request options 28 | /// 29 | /// [requestStream] The request stream, It will not be null 30 | /// only when http method is one of "POST","PUT","PATCH" 31 | /// and the request body is not empty. 32 | /// 33 | /// We should give priority to using requestStream(not options.data) as request data. 34 | /// because supporting stream ensures the `onSendProgress` works. 35 | /// 36 | /// [cancelFuture]: When cancelled the request, [cancelFuture] will be resolved! 37 | /// you can listen cancel event by it, for example: 38 | /// 39 | /// ```dart 40 | /// cancelFuture?.then((_)=>print("request cancelled!")) 41 | /// ``` 42 | /// [cancelFuture]: will be null when the request is not set [CancelToken]. 43 | 44 | Future fetch( 45 | RequestOptions options, 46 | Stream? requestStream, 47 | Future? cancelFuture, 48 | ); 49 | 50 | void close({bool force = false}); 51 | } 52 | 53 | class ResponseBody { 54 | ResponseBody( 55 | this.stream, 56 | this.statusCode, { 57 | this.headers = const {}, 58 | this.statusMessage, 59 | this.isRedirect = false, 60 | this.redirects, 61 | }); 62 | 63 | /// The response stream 64 | Stream stream; 65 | 66 | /// the response headers 67 | late Map> headers; 68 | 69 | /// Http status code 70 | int? statusCode; 71 | 72 | /// Returns the reason phrase associated with the status code. 73 | /// The reason phrase must be set before the body is written 74 | /// to. Setting the reason phrase after writing to the body. 75 | String? statusMessage; 76 | 77 | /// Whether this response is a redirect. 78 | final bool isRedirect; 79 | 80 | List? redirects; 81 | 82 | Map extra = {}; 83 | 84 | ResponseBody.fromString( 85 | String text, 86 | this.statusCode, { 87 | this.headers = const {}, 88 | this.statusMessage, 89 | this.isRedirect = false, 90 | }) : stream = Stream.fromIterable( 91 | utf8.encode(text).map((e) => Uint8List.fromList([e])).toList()); 92 | 93 | ResponseBody.fromBytes( 94 | List bytes, 95 | this.statusCode, { 96 | this.headers = const {}, 97 | this.statusMessage, 98 | this.isRedirect = false, 99 | }) : stream = Stream.value(Uint8List.fromList(bytes)); 100 | } 101 | -------------------------------------------------------------------------------- /dio_http/lib/src/adapters/browser_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | import '../dio_error.dart'; 4 | import '../options.dart'; 5 | import '../adapter.dart'; 6 | import 'dart:html'; 7 | import '../headers.dart'; 8 | 9 | HttpClientAdapter createAdapter() => BrowserHttpClientAdapter(); 10 | 11 | class BrowserHttpClientAdapter implements HttpClientAdapter { 12 | /// These are aborted if the client is closed. 13 | final _xhrs = []; 14 | 15 | /// Whether to send credentials such as cookies or authorization headers for 16 | /// cross-site requests. 17 | /// 18 | /// Defaults to `false`. 19 | /// 20 | /// You can also override this value in Options.extra['withCredentials'] for each request 21 | bool withCredentials = false; 22 | 23 | @override 24 | Future fetch(RequestOptions options, 25 | Stream? requestStream, Future? cancelFuture) { 26 | var xhr = HttpRequest(); 27 | _xhrs.add(xhr); 28 | 29 | xhr 30 | ..open(options.method, options.uri.toString(), async: true) 31 | ..responseType = 'blob' 32 | ..withCredentials = options.extra['withCredentials'] ?? withCredentials; 33 | options.headers.remove(Headers.contentLengthHeader); 34 | options.headers.forEach((key, v) => xhr.setRequestHeader(key, '$v')); 35 | 36 | var completer = Completer(); 37 | 38 | xhr.onLoad.first.then((_) { 39 | // TODO: Set the response type to "arraybuffer" when issue 18542 is fixed. 40 | var blob = xhr.response ?? Blob([]); 41 | var reader = FileReader(); 42 | 43 | reader.onLoad.first.then((_) { 44 | var body = reader.result as Uint8List; 45 | completer.complete( 46 | ResponseBody.fromBytes( 47 | body, 48 | xhr.status, 49 | headers: 50 | xhr.responseHeaders.map((k, v) => MapEntry(k, v.split(','))), 51 | statusMessage: xhr.statusText, 52 | isRedirect: xhr.status == 302 || xhr.status == 301, 53 | ), 54 | ); 55 | }); 56 | 57 | reader.onError.first.then((error) { 58 | completer.completeError( 59 | DioError( 60 | type: DioErrorType.response, 61 | error: error, 62 | requestOptions: options, 63 | ), 64 | StackTrace.current, 65 | ); 66 | }); 67 | reader.readAsArrayBuffer(blob); 68 | }); 69 | 70 | xhr.onError.first.then((_) { 71 | // Unfortunately, the underlying XMLHttpRequest API doesn't expose any 72 | // specific information about the error itself. 73 | completer.completeError( 74 | DioError( 75 | type: DioErrorType.response, 76 | error: 'XMLHttpRequest error.', 77 | requestOptions: options, 78 | ), 79 | StackTrace.current, 80 | ); 81 | }); 82 | 83 | cancelFuture?.then((_) { 84 | if (xhr.readyState < 4 && xhr.readyState > 0) { 85 | try { 86 | xhr.abort(); 87 | } catch (e) { 88 | // ignore 89 | } 90 | } 91 | }); 92 | 93 | if (requestStream != null) { 94 | requestStream.toList().then((value) { 95 | var length = value.fold( 96 | 0, (previousValue, element) => previousValue + element.length); 97 | var res = Uint8List(length); 98 | var start = 0; 99 | for (var list in value) { 100 | res.setRange(start, start += list.length, list); 101 | } 102 | return res; 103 | }).then(xhr.send); 104 | } else { 105 | xhr.send(); 106 | } 107 | 108 | return completer.future.whenComplete(() { 109 | _xhrs.remove(xhr); 110 | }); 111 | } 112 | 113 | /// Closes the client. 114 | /// 115 | /// This terminates all active requests. 116 | @override 117 | void close({bool force = false}) { 118 | if (force) { 119 | for (var xhr in _xhrs) { 120 | xhr.abort(); 121 | } 122 | } 123 | _xhrs.clear(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /dio_http/lib/src/adapters/io_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | import '../adapter.dart'; 5 | import '../options.dart'; 6 | import '../dio_error.dart'; 7 | import '../redirect_record.dart'; 8 | 9 | typedef OnHttpClientCreate = dynamic Function(HttpClient client); 10 | 11 | HttpClientAdapter createAdapter() => DefaultHttpClientAdapter(); 12 | 13 | /// The default HttpClientAdapter for Dio. 14 | class DefaultHttpClientAdapter implements HttpClientAdapter { 15 | /// [Dio] will create HttpClient when it is needed. 16 | /// If [onHttpClientCreate] is provided, [Dio] will call 17 | /// it when a HttpClient created. 18 | OnHttpClientCreate? onHttpClientCreate; 19 | 20 | HttpClient? _defaultHttpClient; 21 | 22 | bool _closed = false; 23 | 24 | @override 25 | Future fetch( 26 | RequestOptions options, 27 | Stream? requestStream, 28 | Future? cancelFuture, 29 | ) async { 30 | if (_closed) { 31 | throw Exception( 32 | "Can't establish connection after [HttpClientAdapter] closed!", 33 | ); 34 | } 35 | var _httpClient = _configHttpClient(cancelFuture, options.connectTimeout); 36 | var reqFuture = _httpClient.openUrl(options.method, options.uri); 37 | 38 | void _throwConnectingTimeout() { 39 | throw DioError( 40 | requestOptions: options, 41 | error: 'Connecting timed out [${options.connectTimeout}ms]', 42 | type: DioErrorType.connectTimeout, 43 | ); 44 | } 45 | 46 | late HttpClientRequest request; 47 | try { 48 | if (options.connectTimeout > 0) { 49 | request = await reqFuture.timeout( 50 | Duration(milliseconds: options.connectTimeout), 51 | ); 52 | } else { 53 | request = await reqFuture; 54 | } 55 | 56 | //Set Headers 57 | options.headers.forEach((k, v) => request.headers.set(k, v)); 58 | } on SocketException catch (e) { 59 | if (e.message.contains('timed out')) { 60 | _throwConnectingTimeout(); 61 | } 62 | rethrow; 63 | } on TimeoutException { 64 | _throwConnectingTimeout(); 65 | } 66 | 67 | request.followRedirects = options.followRedirects; 68 | request.maxRedirects = options.maxRedirects; 69 | 70 | if (requestStream != null) { 71 | // Transform the request data 72 | await request.addStream(requestStream); 73 | } 74 | var future = request.close(); 75 | if (options.connectTimeout > 0) { 76 | future = future.timeout(Duration(milliseconds: options.connectTimeout)); 77 | } 78 | late HttpClientResponse responseStream; 79 | try { 80 | responseStream = await future; 81 | } on TimeoutException { 82 | _throwConnectingTimeout(); 83 | } 84 | 85 | var stream = responseStream.transform( 86 | StreamTransformer.fromHandlers( 87 | handleData: (data, sink) => sink.add( 88 | Uint8List.fromList(data), 89 | ), 90 | ), 91 | ); 92 | 93 | var headers = >{}; 94 | responseStream.headers.forEach((key, values) { 95 | headers[key] = values; 96 | }); 97 | return ResponseBody( 98 | stream, 99 | responseStream.statusCode, 100 | headers: headers, 101 | isRedirect: 102 | responseStream.isRedirect || responseStream.redirects.isNotEmpty, 103 | redirects: responseStream.redirects 104 | .map( 105 | (e) => RedirectRecord(e.statusCode, e.method, e.location), 106 | ) 107 | .toList(), 108 | statusMessage: responseStream.reasonPhrase, 109 | ); 110 | } 111 | 112 | HttpClient _configHttpClient(Future? cancelFuture, int connectionTimeout) { 113 | var _connectionTimeout = connectionTimeout > 0 114 | ? Duration(milliseconds: connectionTimeout) 115 | : null; 116 | 117 | if (cancelFuture != null) { 118 | var _httpClient = HttpClient(); 119 | _httpClient.userAgent = null; 120 | if (onHttpClientCreate != null) { 121 | //user can return a HttpClient instance 122 | _httpClient = onHttpClientCreate!(_httpClient) ?? _httpClient; 123 | } 124 | _httpClient.idleTimeout = Duration(seconds: 0); 125 | cancelFuture.whenComplete(() { 126 | Future.delayed(Duration(seconds: 0)).then((e) { 127 | try { 128 | _httpClient.close(force: true); 129 | } catch (e) { 130 | //... 131 | } 132 | }); 133 | }); 134 | return _httpClient..connectionTimeout = _connectionTimeout; 135 | } 136 | if (_defaultHttpClient == null) { 137 | _defaultHttpClient = HttpClient(); 138 | _defaultHttpClient!.idleTimeout = Duration(seconds: 3); 139 | if (onHttpClientCreate != null) { 140 | //user can return a HttpClient instance 141 | _defaultHttpClient = 142 | onHttpClientCreate!(_defaultHttpClient!) ?? _defaultHttpClient; 143 | } 144 | _defaultHttpClient!.connectionTimeout = _connectionTimeout; 145 | } 146 | return _defaultHttpClient!; 147 | } 148 | 149 | @override 150 | void close({bool force = false}) { 151 | _closed = _closed; 152 | _defaultHttpClient?.close(force: force); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /dio_http/lib/src/cancel_token.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dio_error.dart'; 3 | import 'options.dart'; 4 | 5 | /// You can cancel a request by using a cancel token. 6 | /// One token can be shared with different requests. 7 | /// when a token's [cancel] method invoked, all requests 8 | /// with this token will be cancelled. 9 | class CancelToken { 10 | CancelToken() { 11 | _completer = Completer(); 12 | } 13 | 14 | /// Whether is throw by [cancel] 15 | static bool isCancel(DioError e) { 16 | return e.type == DioErrorType.cancel; 17 | } 18 | 19 | /// If request have been canceled, save the cancel Error. 20 | DioError? _cancelError; 21 | 22 | /// If request have been canceled, save the cancel Error. 23 | DioError? get cancelError => _cancelError; 24 | 25 | late Completer _completer; 26 | 27 | RequestOptions? requestOptions; 28 | 29 | /// whether cancelled 30 | bool get isCancelled => _cancelError != null; 31 | 32 | /// When cancelled, this future will be resolved. 33 | Future get whenCancel => _completer.future; 34 | 35 | /// Cancel the request 36 | void cancel([dynamic reason]) { 37 | _cancelError = DioError( 38 | type: DioErrorType.cancel, 39 | error: reason, 40 | requestOptions: requestOptions ?? RequestOptions(path: ''), 41 | ); 42 | _cancelError!.stackTrace = StackTrace.current; 43 | _completer.complete(_cancelError); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dio_http/lib/src/dio_error.dart: -------------------------------------------------------------------------------- 1 | import 'options.dart'; 2 | import 'response.dart'; 3 | 4 | enum DioErrorType { 5 | /// It occurs when url is opened timeout. 6 | connectTimeout, 7 | 8 | /// It occurs when url is sent timeout. 9 | sendTimeout, 10 | 11 | ///It occurs when receiving timeout. 12 | receiveTimeout, 13 | 14 | /// When the server response, but with a incorrect status, such as 404, 503... 15 | response, 16 | 17 | /// When the request is cancelled, dio will throw a error with this type. 18 | cancel, 19 | 20 | /// Default error type, Some other Error. In this case, you can 21 | /// use the DioError.error if it is not null. 22 | other, 23 | } 24 | 25 | /// DioError describes the error info when request failed. 26 | class DioError implements Exception { 27 | DioError({ 28 | required this.requestOptions, 29 | this.response, 30 | this.type = DioErrorType.other, 31 | this.error, 32 | }); 33 | 34 | /// Request info. 35 | RequestOptions requestOptions; 36 | 37 | /// Response info, it may be `null` if the request can't reach to 38 | /// the http server, for example, occurring a dns error, network is not available. 39 | Response? response; 40 | 41 | DioErrorType type; 42 | 43 | /// The original error/exception object; It's usually not null when `type` 44 | /// is DioErrorType.DEFAULT 45 | dynamic error; 46 | 47 | StackTrace? stackTrace; 48 | 49 | 50 | 51 | String get message => (error?.toString() ?? ''); 52 | 53 | @override 54 | String toString() { 55 | var msg = 'DioError [$type]: $message'; 56 | if (stackTrace != null) { 57 | msg += '\n$stackTrace'; 58 | } 59 | return msg; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /dio_http/lib/src/entry/dio_for_browser.dart: -------------------------------------------------------------------------------- 1 | import '../dio_mixin.dart'; 2 | import '../options.dart'; 3 | import '../dio.dart'; 4 | import '../adapters/browser_adapter.dart'; 5 | 6 | Dio createDio([BaseOptions? options]) => DioForBrowser(options); 7 | 8 | class DioForBrowser with DioMixin implements Dio { 9 | /// Create Dio instance with default [Options]. 10 | /// It's mostly just one Dio instance in your application. 11 | DioForBrowser([BaseOptions? options]) { 12 | this.options = options ?? BaseOptions(); 13 | httpClientAdapter = BrowserHttpClientAdapter(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dio_http/lib/src/entry_stub.dart: -------------------------------------------------------------------------------- 1 | import '../dio_http.dart'; 2 | 3 | Dio createDio([BaseOptions? options]) => throw UnsupportedError(''); 4 | -------------------------------------------------------------------------------- /dio_http/lib/src/form_data.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:math'; 4 | 5 | import 'multipart_file.dart'; 6 | import 'options.dart'; 7 | import 'utils.dart'; 8 | 9 | /// A class to create readable "multipart/form-data" streams. 10 | /// It can be used to submit forms and file uploads to http server. 11 | class FormData { 12 | static const String _BOUNDARY_PRE_TAG = '--dio-boundary-'; 13 | static const _BOUNDARY_LENGTH = _BOUNDARY_PRE_TAG.length + 10; 14 | 15 | late String _boundary; 16 | 17 | /// The boundary of FormData, it consists of a constant prefix and a random 18 | /// postfix to assure the the boundary unpredictable and unique, each FormData 19 | /// instance will be different. 20 | String get boundary => _boundary; 21 | 22 | final _newlineRegExp = RegExp(r'\r\n|\r|\n'); 23 | 24 | /// The form fields to send for this request. 25 | final fields = >[]; 26 | 27 | /// The [files]. 28 | final files = >[]; 29 | 30 | /// Whether [finalize] has been called. 31 | bool get isFinalized => _isFinalized; 32 | bool _isFinalized = false; 33 | 34 | FormData() { 35 | _init(); 36 | } 37 | 38 | /// Create FormData instance with a Map. 39 | FormData.fromMap( 40 | Map map, [ 41 | ListFormat collectionFormat = ListFormat.multi, 42 | ]) { 43 | _init(); 44 | encodeMap( 45 | map, 46 | (key, value) { 47 | if (value == null) return null; 48 | if (value is MultipartFile) { 49 | files.add(MapEntry(key, value)); 50 | } else { 51 | fields.add(MapEntry(key, value.toString())); 52 | } 53 | return null; 54 | }, 55 | listFormat: collectionFormat, 56 | encode: false, 57 | ); 58 | } 59 | 60 | void _init() { 61 | // Assure the boundary unpredictable and unique 62 | var random = Random(); 63 | _boundary = _BOUNDARY_PRE_TAG + 64 | random.nextInt(4294967296).toString().padLeft(10, '0'); 65 | } 66 | 67 | /// Returns the header string for a field. The return value is guaranteed to 68 | /// contain only ASCII characters. 69 | String _headerForField(String name, String value) { 70 | var header = 71 | 'content-disposition: form-data; name="${_browserEncode(name)}"'; 72 | if (!isPlainAscii(value)) { 73 | header = '$header\r\n' 74 | 'content-type: text/plain; charset=utf-8\r\n' 75 | 'content-transfer-encoding: binary'; 76 | } 77 | return '$header\r\n\r\n'; 78 | } 79 | 80 | /// Returns the header string for a file. The return value is guaranteed to 81 | /// contain only ASCII characters. 82 | String _headerForFile(MapEntry entry) { 83 | var file = entry.value; 84 | var header = 85 | 'content-disposition: form-data; name="${_browserEncode(entry.key)}"'; 86 | if (file.filename != null) { 87 | header = '$header; filename="${_browserEncode(file.filename)}"'; 88 | } 89 | header = '$header\r\n' 90 | 'content-type: ${file.contentType}'; 91 | return '$header\r\n\r\n'; 92 | } 93 | 94 | /// Encode [value] in the same way browsers do. 95 | String? _browserEncode(String? value) { 96 | // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for 97 | // field names and file names, but in practice user agents seem not to 98 | // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as 99 | // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII 100 | // characters). We follow their behavior. 101 | if (value == null) { 102 | return null; 103 | } 104 | return value.replaceAll(_newlineRegExp, '%0D%0A').replaceAll('"', '%22'); 105 | } 106 | 107 | /// The total length of the request body, in bytes. This is calculated from 108 | /// [fields] and [files] and cannot be set manually. 109 | int get length { 110 | var length = 0; 111 | fields.forEach((entry) { 112 | length += '--'.length + 113 | _BOUNDARY_LENGTH + 114 | '\r\n'.length + 115 | utf8.encode(_headerForField(entry.key, entry.value)).length + 116 | utf8.encode(entry.value).length + 117 | '\r\n'.length; 118 | }); 119 | 120 | for (var file in files) { 121 | length += '--'.length + 122 | _BOUNDARY_LENGTH + 123 | '\r\n'.length + 124 | utf8.encode(_headerForFile(file)).length + 125 | file.value.length + 126 | '\r\n'.length; 127 | } 128 | 129 | return length + '--'.length + _BOUNDARY_LENGTH + '--\r\n'.length; 130 | } 131 | 132 | Stream> finalize() { 133 | if (isFinalized) { 134 | throw StateError("Can't finalize a finalized MultipartFile."); 135 | } 136 | _isFinalized = true; 137 | var controller = StreamController>(sync: false); 138 | void writeAscii(String string) { 139 | controller.add(utf8.encode(string)); 140 | } 141 | 142 | void writeUtf8(String string) => controller.add(utf8.encode(string)); 143 | void writeLine() => controller.add([13, 10]); // \r\n 144 | 145 | fields.forEach((entry) { 146 | writeAscii('--$boundary\r\n'); 147 | writeAscii(_headerForField(entry.key, entry.value)); 148 | writeUtf8(entry.value); 149 | writeLine(); 150 | }); 151 | 152 | Future.forEach>(files, (file) { 153 | writeAscii('--$boundary\r\n'); 154 | writeAscii(_headerForFile(file)); 155 | return writeStreamToSink(file.value.finalize(), controller) 156 | .then((_) => writeLine()); 157 | }).then((_) { 158 | writeAscii('--$boundary--\r\n'); 159 | controller.close(); 160 | }); 161 | return controller.stream; 162 | } 163 | 164 | ///Transform the entire FormData contents as a list of bytes asynchronously. 165 | Future> readAsBytes() { 166 | return Future(() => finalize().reduce((a, b) => [...a, ...b])); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /dio_http/lib/src/headers.dart: -------------------------------------------------------------------------------- 1 | import 'package:http_parser/http_parser.dart'; 2 | 3 | import 'utils.dart'; 4 | 5 | typedef HeaderForEachCallback = void Function(String name, List values); 6 | 7 | class Headers { 8 | // Header field name 9 | static const acceptHeader = 'accept'; 10 | static const contentEncodingHeader = 'content-encoding'; 11 | static const contentLengthHeader = 'content-length'; 12 | static const contentTypeHeader = 'content-type'; 13 | static const wwwAuthenticateHeader = 'www-authenticate'; 14 | 15 | // Header field value 16 | static const jsonContentType = 'application/json; charset=utf-8'; 17 | static const formUrlEncodedContentType = 'application/x-www-form-urlencoded'; 18 | static const textPlainContentType = 'text/plain'; 19 | 20 | static final jsonMimeType = MediaType.parse(jsonContentType); 21 | 22 | final Map> _map; 23 | 24 | Map> get map => _map; 25 | 26 | Headers() : _map = caseInsensitiveKeyMap>(); 27 | 28 | Headers.fromMap(Map> map) 29 | : _map = caseInsensitiveKeyMap>( 30 | map.map((k, v) => MapEntry(k.trim().toLowerCase(), v)), 31 | ); 32 | 33 | /// Returns the list of values for the header named [name]. If there 34 | /// is no header with the provided name, [:null:] will be returned. 35 | List? operator [](String name) { 36 | return _map[name.trim().toLowerCase()]; 37 | } 38 | 39 | /// Convenience method for the value for a single valued header. If 40 | /// there is no header with the provided name, [:null:] will be 41 | /// returned. If the header has more than one value an exception is 42 | /// thrown. 43 | String? value(String name) { 44 | var arr = this[name]; 45 | if (arr == null) return null; 46 | if (arr.length == 1) return arr.first; 47 | throw Exception( 48 | '"$name" header has more than one value, please use Headers[name]'); 49 | } 50 | 51 | /// Adds a header value. The header named [name] will have the value 52 | /// [value] added to its list of values. 53 | void add(String name, String value) { 54 | var arr = this[name]; 55 | if (arr == null) return set(name, value); 56 | arr.add(value); 57 | } 58 | 59 | /// Sets a header. The header named [name] will have all its values 60 | /// cleared before the value [value] is added as its value. 61 | void set(String name, dynamic value) { 62 | name = name.trim().toLowerCase(); 63 | if (value is List) { 64 | _map[name] = value.map((e) => e.toString()).toList(); 65 | } else { 66 | _map[name] = [value.trim()]; 67 | } 68 | } 69 | 70 | /// Removes a specific value for a header name. 71 | void remove(String name, String value) { 72 | var arr = this[name]; 73 | if (arr == null) return; 74 | arr.removeWhere((v) => v == value); 75 | } 76 | 77 | /// Removes all values for the specified header name. 78 | void removeAll(String name) { 79 | _map.remove(name); 80 | } 81 | 82 | void clear() { 83 | _map.clear(); 84 | } 85 | 86 | bool get isEmpty => _map.isEmpty; 87 | 88 | /// Enumerates the headers, applying the function [f] to each 89 | /// header. The header name passed in [:name:] will be all lower 90 | /// case. 91 | void forEach(HeaderForEachCallback f) { 92 | _map.keys.forEach((key) => f(key, this[key]!)); 93 | } 94 | 95 | @override 96 | String toString() { 97 | var stringBuffer = StringBuffer(); 98 | _map.forEach((key, value) { 99 | value.forEach((e) => stringBuffer.writeln('$key: $e')); 100 | }); 101 | return stringBuffer.toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /dio_http/lib/src/interceptors/log.dart: -------------------------------------------------------------------------------- 1 | import '../dio_error.dart'; 2 | import '../interceptor.dart'; 3 | import '../options.dart'; 4 | import '../response.dart'; 5 | 6 | /// [LogInterceptor] is used to print logs during network requests. 7 | /// It's better to add [LogInterceptor] to the tail of the interceptor queue, 8 | /// otherwise the changes made in the interceptor behind A will not be printed out. 9 | /// This is because the execution of interceptors is in the order of addition. 10 | class LogInterceptor extends Interceptor { 11 | LogInterceptor({ 12 | this.request = true, 13 | this.requestHeader = true, 14 | this.requestBody = false, 15 | this.responseHeader = true, 16 | this.responseBody = false, 17 | this.error = true, 18 | this.logPrint = print, 19 | }); 20 | 21 | /// Print request [Options] 22 | bool request; 23 | 24 | /// Print request header [Options.headers] 25 | bool requestHeader; 26 | 27 | /// Print request data [Options.data] 28 | bool requestBody; 29 | 30 | /// Print [Response.data] 31 | bool responseBody; 32 | 33 | /// Print [Response.headers] 34 | bool responseHeader; 35 | 36 | /// Print error message 37 | bool error; 38 | 39 | /// Log printer; defaults print log to console. 40 | /// In flutter, you'd better use debugPrint. 41 | /// you can also write log in a file, for example: 42 | ///```dart 43 | /// var file=File("./log.txt"); 44 | /// var sink=file.openWrite(); 45 | /// dio.interceptors.add(LogInterceptor(logPrint: sink.writeln)); 46 | /// ... 47 | /// await sink.close(); 48 | ///``` 49 | void Function(Object object) logPrint; 50 | 51 | @override 52 | void onRequest( 53 | RequestOptions options, RequestInterceptorHandler handler) async { 54 | logPrint('*** Request ***'); 55 | _printKV('uri', options.uri); 56 | //options.headers; 57 | 58 | if (request) { 59 | _printKV('method', options.method); 60 | _printKV('responseType', options.responseType.toString()); 61 | _printKV('followRedirects', options.followRedirects); 62 | _printKV('connectTimeout', options.connectTimeout); 63 | _printKV('sendTimeout', options.sendTimeout); 64 | _printKV('receiveTimeout', options.receiveTimeout); 65 | _printKV( 66 | 'receiveDataWhenStatusError', options.receiveDataWhenStatusError); 67 | _printKV('extra', options.extra); 68 | } 69 | if (requestHeader) { 70 | logPrint('headers:'); 71 | options.headers.forEach((key, v) => _printKV(' $key', v)); 72 | } 73 | if (requestBody) { 74 | logPrint('data:'); 75 | _printAll(options.data); 76 | } 77 | logPrint(''); 78 | 79 | handler.next(options); 80 | } 81 | 82 | @override 83 | void onResponse(Response response, ResponseInterceptorHandler handler) async { 84 | logPrint('*** Response ***'); 85 | _printResponse(response); 86 | handler.next(response); 87 | } 88 | 89 | @override 90 | void onError(DioError err, ErrorInterceptorHandler handler) async { 91 | if (error) { 92 | logPrint('*** DioError ***:'); 93 | logPrint('uri: ${err.requestOptions.uri}'); 94 | logPrint('$err'); 95 | if (err.response != null) { 96 | _printResponse(err.response!); 97 | } 98 | logPrint(''); 99 | } 100 | 101 | handler.next(err); 102 | } 103 | 104 | void _printResponse(Response response) { 105 | _printKV('uri', response.requestOptions.uri); 106 | if (responseHeader) { 107 | _printKV('statusCode', response.statusCode); 108 | if (response.isRedirect == true) { 109 | _printKV('redirect', response.realUri); 110 | } 111 | 112 | logPrint('headers:'); 113 | response.headers.forEach((key, v) => _printKV(' $key', v.join('\r\n\t'))); 114 | } 115 | if (responseBody) { 116 | logPrint('Response Text:'); 117 | _printAll(response.toString()); 118 | } 119 | logPrint(''); 120 | } 121 | 122 | void _printKV(String key, Object? v) { 123 | logPrint('$key: $v'); 124 | } 125 | 126 | void _printAll(msg) { 127 | msg.toString().split('\n').forEach(logPrint); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /dio_http/lib/src/multipart_file.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'package:http_parser/http_parser.dart'; 4 | import 'utils.dart'; 5 | 6 | // ignore: uri_does_not_exist 7 | import 'multipart_file_stub.dart' 8 | // ignore: uri_does_not_exist 9 | if (dart.library.io) 'multipart_file_io.dart'; 10 | 11 | /// A file to be uploaded as part of a [MultipartRequest]. This doesn't need to 12 | /// correspond to a physical file. 13 | /// 14 | /// MultipartFile is based on stream, and a stream can be read only once, 15 | /// so the same MultipartFile can't be read multiple times. 16 | class MultipartFile { 17 | /// The size of the file in bytes. This must be known in advance, even if this 18 | /// file is created from a [ByteStream]. 19 | final int length; 20 | 21 | /// The basename of the file. May be null. 22 | final String? filename; 23 | 24 | /// The content-type of the file. Defaults to `application/octet-stream`. 25 | final MediaType? contentType; 26 | 27 | /// The stream that will emit the file's contents. 28 | final Stream> _stream; 29 | 30 | /// Whether [finalize] has been called. 31 | bool get isFinalized => _isFinalized; 32 | bool _isFinalized = false; 33 | 34 | /// Creates a new [MultipartFile] from a chunked [Stream] of bytes. The length 35 | /// of the file in bytes must be known in advance. If it's not, read the data 36 | /// from the stream and use [MultipartFile.fromBytes] instead. 37 | /// 38 | /// [contentType] currently defaults to `application/octet-stream`, but in the 39 | /// future may be inferred from [filename]. 40 | MultipartFile( 41 | Stream> stream, 42 | this.length, { 43 | this.filename, 44 | MediaType? contentType, 45 | }) : _stream = stream, 46 | contentType = contentType ?? MediaType('application', 'octet-stream'); 47 | 48 | /// Creates a new [MultipartFile] from a byte array. 49 | /// 50 | /// [contentType] currently defaults to `application/octet-stream`, but in the 51 | /// future may be inferred from [filename]. 52 | factory MultipartFile.fromBytes( 53 | List value, { 54 | String? filename, 55 | MediaType? contentType, 56 | }) { 57 | var stream = Stream.fromIterable([value]); 58 | return MultipartFile( 59 | stream, 60 | value.length, 61 | filename: filename, 62 | contentType: contentType, 63 | ); 64 | } 65 | 66 | /// Creates a new [MultipartFile] from a string. 67 | /// 68 | /// The encoding to use when translating [value] into bytes is taken from 69 | /// [contentType] if it has a charset set. Otherwise, it defaults to UTF-8. 70 | /// [contentType] currently defaults to `text/plain; charset=utf-8`, but in 71 | /// the future may be inferred from [filename]. 72 | factory MultipartFile.fromString( 73 | String value, { 74 | String? filename, 75 | MediaType? contentType, 76 | }) { 77 | contentType ??= MediaType('text', 'plain'); 78 | var encoding = encodingForCharset(contentType.parameters['charset'], utf8); 79 | contentType = contentType.change(parameters: {'charset': encoding.name}); 80 | 81 | return MultipartFile.fromBytes( 82 | encoding.encode(value), 83 | filename: filename, 84 | contentType: contentType, 85 | ); 86 | } 87 | 88 | /// Creates a new [MultipartFile] from a path to a file on disk. 89 | /// 90 | /// [filename] defaults to the basename of [filePath]. [contentType] currently 91 | /// defaults to `application/octet-stream`, but in the future may be inferred 92 | /// from [filename]. 93 | /// 94 | /// Throws an [UnsupportedError] if `dart:io` isn't supported in this 95 | /// environment. 96 | static Future fromFile( 97 | String filePath, { 98 | String? filename, 99 | MediaType? contentType, 100 | }) => 101 | multipartFileFromPath( 102 | filePath, 103 | filename: filename, 104 | contentType: contentType, 105 | ); 106 | 107 | static MultipartFile fromFileSync( 108 | String filePath, { 109 | String? filename, 110 | MediaType? contentType, 111 | }) => 112 | multipartFileFromPathSync( 113 | filePath, 114 | filename: filename, 115 | contentType: contentType, 116 | ); 117 | 118 | Stream> finalize() { 119 | if (isFinalized) { 120 | throw StateError("Can't finalize a finalized MultipartFile."); 121 | } 122 | _isFinalized = true; 123 | return _stream; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /dio_http/lib/src/multipart_file_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:http_parser/http_parser.dart'; 4 | import 'package:path/path.dart' as p; 5 | import 'multipart_file.dart'; 6 | 7 | Future multipartFileFromPath( 8 | String filePath, { 9 | String? filename, 10 | MediaType? contentType, 11 | }) async { 12 | filename ??= p.basename(filePath); 13 | var file = File(filePath); 14 | var length = await file.length(); 15 | var stream = file.openRead(); 16 | return MultipartFile( 17 | stream, 18 | length, 19 | filename: filename, 20 | contentType: contentType, 21 | ); 22 | } 23 | 24 | MultipartFile multipartFileFromPathSync( 25 | String filePath, { 26 | String? filename, 27 | MediaType? contentType, 28 | }) { 29 | filename ??= p.basename(filePath); 30 | var file = File(filePath); 31 | var length = file.lengthSync(); 32 | var stream = file.openRead(); 33 | return MultipartFile( 34 | stream, 35 | length, 36 | filename: filename, 37 | contentType: contentType, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /dio_http/lib/src/multipart_file_stub.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:http_parser/http_parser.dart'; 3 | import 'multipart_file.dart'; 4 | 5 | final _err = UnsupportedError( 6 | 'MultipartFile is only supported where dart:io is available.'); 7 | 8 | Future multipartFileFromPath(String filePath, 9 | {String? filename, MediaType? contentType}) => 10 | throw _err; 11 | 12 | MultipartFile multipartFileFromPathSync(String filePath, 13 | {String? filename, MediaType? contentType}) => 14 | throw _err; 15 | -------------------------------------------------------------------------------- /dio_http/lib/src/parameter.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | class ListParam { 4 | final ListFormat format; 5 | List value; 6 | 7 | ListParam(this.value, this.format); 8 | } 9 | -------------------------------------------------------------------------------- /dio_http/lib/src/redirect_record.dart: -------------------------------------------------------------------------------- 1 | class RedirectRecord { 2 | RedirectRecord(this.statusCode, this.method, this.location); 3 | 4 | /// Returns the status code used for the redirect. 5 | final int statusCode; 6 | 7 | /// Returns the method used for the redirect. 8 | final String method; 9 | 10 | ///Returns the location for the redirect. 11 | final Uri location; 12 | } 13 | -------------------------------------------------------------------------------- /dio_http/lib/src/response.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'options.dart'; 3 | import 'headers.dart'; 4 | import 'redirect_record.dart'; 5 | 6 | /// Response describes the http Response info. 7 | class Response { 8 | Response({ 9 | this.data, 10 | Headers? headers, 11 | required this.requestOptions, 12 | this.isRedirect, 13 | this.statusCode, 14 | this.statusMessage, 15 | List? redirects, 16 | Map? extra, 17 | }) { 18 | this.headers = headers ?? Headers(); 19 | this.extra = extra ?? {}; 20 | this.redirects = redirects ?? []; 21 | } 22 | 23 | /// Response body. may have been transformed, please refer to [ResponseType]. 24 | T? data; 25 | 26 | /// Response headers. 27 | late Headers headers; 28 | 29 | /// The corresponding request info. 30 | late RequestOptions requestOptions; 31 | 32 | /// Http status code. 33 | int? statusCode; 34 | 35 | /// Returns the reason phrase associated with the status code. 36 | /// The reason phrase must be set before the body is written 37 | /// to. Setting the reason phrase after writing to the body. 38 | String? statusMessage; 39 | 40 | /// Custom field that you can retrieve it later in `then`. 41 | late Map extra; 42 | 43 | /// Returns the series of redirects this connection has been through. The 44 | /// list will be empty if no redirects were followed. [redirects] will be 45 | /// updated both in the case of an automatic and a manual redirect. 46 | /// 47 | /// ** Attention **: Whether this field is available depends on whether the 48 | /// implementation of the adapter supports it or not. 49 | late List redirects; 50 | 51 | /// Whether this response is a redirect. 52 | /// ** Attention **: Whether this field is available depends on whether the 53 | /// implementation of the adapter supports it or not. 54 | bool? isRedirect; 55 | 56 | /// Return the final real request uri (maybe redirect). 57 | /// 58 | /// ** Attention **: Whether this field is available depends on whether the 59 | /// implementation of the adapter supports it or not. 60 | Uri get realUri => 61 | (redirects.isNotEmpty) ? redirects.last.location : requestOptions.uri; 62 | 63 | /// We are more concerned about `data` field. 64 | @override 65 | String toString() { 66 | if (data is Map) { 67 | return json.encode(data); 68 | } 69 | return data.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dio_http/lib/src/transformer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:http_parser/http_parser.dart'; 6 | 7 | import 'adapter.dart'; 8 | import 'dio_error.dart'; 9 | import 'headers.dart'; 10 | import 'options.dart'; 11 | import 'utils.dart'; 12 | 13 | /// [Transformer] allows changes to the request/response data before 14 | /// it is sent/received to/from the server. 15 | /// 16 | /// Dio has already implemented a [DefaultTransformer], and as the default 17 | /// [Transformer]. If you want to custom the transformation of 18 | /// request/response data, you can provide a [Transformer] by your self, and 19 | /// replace the [DefaultTransformer] by setting the [dio.Transformer]. 20 | 21 | abstract class Transformer { 22 | /// `transformRequest` allows changes to the request data before it is 23 | /// sent to the server, but **after** the [RequestInterceptor]. 24 | /// 25 | /// This is only applicable for request methods 'PUT', 'POST', and 'PATCH' 26 | Future transformRequest(RequestOptions options); 27 | 28 | /// `transformResponse` allows changes to the response data before 29 | /// it is passed to [ResponseInterceptor]. 30 | /// 31 | /// **Note**: As an agreement, you must return the [response] 32 | /// when the Options.responseType is [ResponseType.stream]. 33 | Future transformResponse(RequestOptions options, ResponseBody response); 34 | 35 | /// Deep encode the [Map] to percent-encoding. 36 | /// It is mostly used with the "application/x-www-form-urlencoded" content-type. 37 | static String urlEncodeMap( 38 | Map map, [ 39 | ListFormat listFormat = ListFormat.multi, 40 | ]) { 41 | return encodeMap( 42 | map, 43 | (key, value) { 44 | if (value == null) return key; 45 | return '$key=${Uri.encodeQueryComponent(value.toString())}'; 46 | }, 47 | listFormat: listFormat, 48 | ); 49 | } 50 | } 51 | 52 | /// The default [Transformer] for [Dio]. If you want to custom the transformation of 53 | /// request/response data, you can provide a [Transformer] by your self, and 54 | /// replace the [DefaultTransformer] by setting the [dio.Transformer]. 55 | 56 | typedef JsonDecodeCallback = dynamic Function(String); 57 | 58 | class DefaultTransformer extends Transformer { 59 | DefaultTransformer({this.jsonDecodeCallback}); 60 | 61 | JsonDecodeCallback? jsonDecodeCallback; 62 | 63 | @override 64 | Future transformRequest(RequestOptions options) async { 65 | var data = options.data ?? ''; 66 | if (data is! String) { 67 | if (_isJsonMime(options.contentType)) { 68 | return json.encode(options.data); 69 | } else if (data is Map) { 70 | options.contentType = 71 | options.contentType ?? Headers.formUrlEncodedContentType; 72 | return Transformer.urlEncodeMap(data); 73 | } 74 | } 75 | return data.toString(); 76 | } 77 | 78 | /// As an agreement, we return the [response] when the 79 | /// Options.responseType is [ResponseType.stream]. 80 | @override 81 | Future transformResponse( 82 | RequestOptions options, ResponseBody response) async { 83 | if (options.responseType == ResponseType.stream) { 84 | return response; 85 | } 86 | var length = 0; 87 | var received = 0; 88 | var showDownloadProgress = options.onReceiveProgress != null; 89 | if (showDownloadProgress) { 90 | length = int.parse( 91 | response.headers[Headers.contentLengthHeader]?.first ?? '-1'); 92 | } 93 | var completer = Completer(); 94 | var stream = 95 | response.stream.transform(StreamTransformer.fromHandlers( 96 | handleData: (data, sink) { 97 | sink.add(data); 98 | if (showDownloadProgress) { 99 | received += data.length; 100 | if (options.onReceiveProgress != null) { 101 | options.onReceiveProgress!(received, length); 102 | } 103 | } 104 | }, 105 | )); 106 | // let's keep references to the data chunks and concatenate them later 107 | final chunks = []; 108 | var finalSize = 0; 109 | StreamSubscription subscription = stream.listen( 110 | (chunk) { 111 | finalSize += chunk.length; 112 | chunks.add(chunk); 113 | }, 114 | onError: (e, stackTrace) { 115 | completer.completeError(e, stackTrace); 116 | }, 117 | onDone: () { 118 | completer.complete(); 119 | }, 120 | cancelOnError: true, 121 | ); 122 | // ignore: unawaited_futures 123 | options.cancelToken?.whenCancel.then((_) { 124 | return subscription.cancel(); 125 | }); 126 | if (options.receiveTimeout > 0) { 127 | try { 128 | await completer.future 129 | .timeout(Duration(milliseconds: options.receiveTimeout)); 130 | } on TimeoutException { 131 | await subscription.cancel(); 132 | throw DioError( 133 | requestOptions: options, 134 | error: 'Receiving data timeout[${options.receiveTimeout}ms]', 135 | type: DioErrorType.receiveTimeout, 136 | ); 137 | } 138 | } else { 139 | await completer.future; 140 | } 141 | // we create a final Uint8List and copy all chunks into it 142 | final responseBytes = Uint8List(finalSize); 143 | var chunkOffset = 0; 144 | for (var chunk in chunks) { 145 | responseBytes.setAll(chunkOffset, chunk); 146 | chunkOffset += chunk.length; 147 | } 148 | 149 | if (options.responseType == ResponseType.bytes) return responseBytes; 150 | 151 | String? responseBody; 152 | if (options.responseDecoder != null) { 153 | responseBody = options.responseDecoder!( 154 | responseBytes, 155 | options, 156 | response..stream = Stream.empty(), 157 | ); 158 | } else { 159 | responseBody = utf8.decode(responseBytes, allowMalformed: true); 160 | } 161 | if (responseBody.isNotEmpty && 162 | options.responseType == ResponseType.json && 163 | _isJsonMime(response.headers[Headers.contentTypeHeader]?.first)) { 164 | final callback = jsonDecodeCallback; 165 | if (callback != null) { 166 | return callback(responseBody); 167 | } else { 168 | return json.decode(responseBody); 169 | } 170 | } 171 | return responseBody; 172 | } 173 | 174 | bool _isJsonMime(String? contentType) { 175 | if (contentType == null) return false; 176 | return MediaType.parse(contentType).mimeType == 177 | Headers.jsonMimeType.mimeType; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /dio_http/lib/src/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | import 'dart:convert'; 4 | 5 | import 'options.dart'; 6 | import 'parameter.dart'; 7 | 8 | /// A regular expression that matches strings that are composed entirely of 9 | /// ASCII-compatible characters. 10 | final RegExp _asciiOnly = RegExp(r'^[\x00-\x7F]+$'); 11 | 12 | /// Returns whether [string] is composed entirely of ASCII-compatible 13 | /// characters. 14 | bool isPlainAscii(String string) => _asciiOnly.hasMatch(string); 15 | 16 | /// Pipes all data and errors from [stream] into [sink]. Completes [Future] once 17 | /// [stream] is done. Unlike [store], [sink] remains open after [stream] is 18 | /// done. 19 | Future writeStreamToSink(Stream stream, EventSink sink) { 20 | var completer = Completer(); 21 | stream.listen(sink.add, 22 | onError: sink.addError, onDone: () => completer.complete()); 23 | return completer.future; 24 | } 25 | 26 | /// Returns the [Encoding] that corresponds to [charset]. Returns [fallback] if 27 | /// [charset] is null or if no [Encoding] was found that corresponds to 28 | /// [charset]. 29 | Encoding encodingForCharset(String? charset, [Encoding fallback = latin1]) { 30 | if (charset == null) return fallback; 31 | var encoding = Encoding.getByName(charset); 32 | return encoding ?? fallback; 33 | } 34 | 35 | typedef DioEncodeHandler = Function(String key, Object? value); 36 | 37 | String encodeMap( 38 | data, 39 | DioEncodeHandler handler, { 40 | bool encode = true, 41 | ListFormat listFormat = ListFormat.multi, 42 | }) { 43 | var urlData = StringBuffer(''); 44 | var first = true; 45 | var leftBracket = encode ? '%5B' : '['; 46 | var rightBracket = encode ? '%5D' : ']'; 47 | var encodeComponent = encode ? Uri.encodeQueryComponent : (e) => e; 48 | void urlEncode(dynamic sub, String path) { 49 | // detect if the list format for this parameter derivates from default 50 | final format = sub is ListParam ? sub.format : listFormat; 51 | final separatorChar = _getSeparatorChar(format); 52 | 53 | if (sub is ListParam) { 54 | // Need to unwrap all param objects here 55 | sub = sub.value; 56 | } 57 | 58 | if (sub is List) { 59 | if (format == ListFormat.multi || format == ListFormat.multiCompatible) { 60 | for (var i = 0; i < sub.length; i++) { 61 | final isCollection = 62 | sub[i] is Map || sub[i] is List || sub[i] is ListParam; 63 | if (listFormat == ListFormat.multi) { 64 | urlEncode( 65 | sub[i], 66 | '$path${isCollection ? leftBracket + '$i' + rightBracket : ''}', 67 | ); 68 | } else { 69 | // Forward compatibility 70 | urlEncode( 71 | sub[i], 72 | '$path$leftBracket${isCollection ? i : ''}$rightBracket', 73 | ); 74 | } 75 | } 76 | } else { 77 | urlEncode(sub.join(separatorChar), path); 78 | } 79 | } else if (sub is Map) { 80 | sub.forEach((k, v) { 81 | if (path == '') { 82 | urlEncode(v, '${encodeComponent(k)}'); 83 | } else { 84 | urlEncode(v, '$path$leftBracket${encodeComponent(k)}$rightBracket'); 85 | } 86 | }); 87 | } else { 88 | var str = handler(path, sub); 89 | var isNotEmpty = str != null && str.trim().isNotEmpty; 90 | if (!first && isNotEmpty) { 91 | urlData.write('&'); 92 | } 93 | first = false; 94 | if (isNotEmpty) { 95 | urlData.write(str); 96 | } 97 | } 98 | } 99 | 100 | urlEncode(data, ''); 101 | return urlData.toString(); 102 | } 103 | 104 | String _getSeparatorChar(ListFormat collectionFormat) { 105 | switch (collectionFormat) { 106 | case ListFormat.csv: 107 | return ','; 108 | case ListFormat.ssv: 109 | return ' '; 110 | case ListFormat.tsv: 111 | return r'\t'; 112 | case ListFormat.pipes: 113 | return '|'; 114 | default: 115 | return ''; 116 | } 117 | } 118 | 119 | Map caseInsensitiveKeyMap([Map? value]) { 120 | final map = LinkedHashMap( 121 | equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(), 122 | hashCode: (key) => key.toLowerCase().hashCode, 123 | ); 124 | if (value != null && value.isNotEmpty) map.addAll(value); 125 | return map; 126 | } 127 | -------------------------------------------------------------------------------- /dio_http/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dio_http 2 | description: A powerful Http client for Dart, which supports Interceptors, FormData, Request Cancellation, File Downloading, Timeout etc. 3 | version: 5.0.4 4 | homepage: https://github.com/dart-tools/dio_http 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | http_parser: ^4.0.0 11 | path: ^1.8.0 12 | 13 | dev_dependencies: 14 | test: ^1.17.11 15 | pedantic: ^1.10.0 16 | -------------------------------------------------------------------------------- /dio_http/test/_formdata: -------------------------------------------------------------------------------- 1 | ----dio-boundary-3788753558 2 | content-disposition: form-data; name="name" 3 | 4 | wendux 5 | ----dio-boundary-3788753558 6 | content-disposition: form-data; name="age" 7 | 8 | 25 9 | ----dio-boundary-3788753558 10 | content-disposition: form-data; name="file" 11 | content-type: text/plain; charset=utf-8 12 | 13 | hello world. 14 | ----dio-boundary-3788753558 15 | content-disposition: form-data; name="files"; filename="1.txt" 16 | content-type: application/octet-stream 17 | 18 | 你好世界, 19 | 我很好人类 20 | 哈哈哈哈哈😆 21 | 22 | ----dio-boundary-3788753558 23 | content-disposition: form-data; name="files"; filename="2.txt" 24 | content-type: application/octet-stream 25 | 26 | 你好世界, 27 | 我很好人类 28 | 哈哈哈哈哈😆 29 | 30 | ----dio-boundary-3788753558-- 31 | -------------------------------------------------------------------------------- /dio_http/test/_testfile: -------------------------------------------------------------------------------- 1 | 你好世界, 2 | 我很好人类 3 | 哈哈哈哈哈😆 4 | -------------------------------------------------------------------------------- /dio_http/test/basic_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | @TestOn('vm') 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | import 'package:dio_http/dio_http.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | test('#test headers', () { 12 | var headers = Headers.fromMap({ 13 | 'set-cookie': ['k=v', 'k1=v1'], 14 | 'content-length': ['200'], 15 | 'test': ['1', '2'], 16 | }); 17 | headers.add('SET-COOKIE', 'k2=v2'); 18 | assert(headers.value('content-length') == '200'); 19 | expect(Future(() => headers.value('test')), throwsException); 20 | assert(headers['set-cookie']?.length == 3); 21 | headers.remove('set-cookie', 'k=v'); 22 | assert(headers['set-cookie']?.length == 2); 23 | headers.removeAll('set-cookie'); 24 | assert(headers['set-cookie'] == null); 25 | var ls = []; 26 | headers.forEach((k, list) { 27 | ls.addAll(list); 28 | }); 29 | assert(ls.length == 3); 30 | assert(headers.toString() == 'content-length: 200\ntest: 1\ntest: 2\n'); 31 | headers.set('content-length', '300'); 32 | assert(headers.value('content-length') == '300'); 33 | headers.set('content-length', ['400']); 34 | assert(headers.value('content-length') == '400'); 35 | 36 | var headers1 = Headers(); 37 | headers1.set('xx', 'v'); 38 | assert(headers1.value('xx') == 'v'); 39 | headers1.clear(); 40 | assert(headers1.map.isEmpty == true); 41 | }); 42 | 43 | test('#send with an invalid URL', () { 44 | expect( 45 | Dio().get('http://http.invalid').catchError((e) => throw e.error), 46 | throwsA(const TypeMatcher()), 47 | ); 48 | }); 49 | 50 | test('#cancellation', () async { 51 | var dio = Dio(); 52 | final token = CancelToken(); 53 | Timer(Duration(milliseconds: 10), () { 54 | token.cancel('cancelled'); 55 | dio.httpClientAdapter.close(force: true); 56 | }); 57 | 58 | var url = 'https://accounts.google.com'; 59 | expect( 60 | dio 61 | .get(url, cancelToken: token) 62 | .catchError((e) => throw CancelToken.isCancel(e)), 63 | throwsA(isTrue), 64 | ); 65 | }); 66 | 67 | test('#status error', () async { 68 | var dio = Dio()..options.baseUrl = 'http://httpbin.org/status/'; 69 | 70 | expect( 71 | dio.get('401').catchError((e) => throw e.response.statusCode), 72 | throwsA(401), 73 | ); 74 | 75 | var r = await dio.get( 76 | '401', 77 | options: Options(validateStatus: (status) => true), 78 | ); 79 | expect(r.statusCode, 401); 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /dio_http/test/download_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn('vm') 6 | import 'dart:io'; 7 | import 'package:dio_http/dio_http.dart'; 8 | import 'package:test/test.dart'; 9 | import 'utils.dart'; 10 | 11 | void main() { 12 | setUp(startServer); 13 | tearDown(stopServer); 14 | test('#test download1', () async { 15 | const savePath = '../_download_test.md'; 16 | var dio = Dio(); 17 | dio.options.baseUrl = serverUrl.toString(); 18 | await dio.download( 19 | '/download', savePath, // disable gzip 20 | onReceiveProgress: (received, total) { 21 | // ignore progress 22 | }, 23 | ); 24 | 25 | var f = File(savePath); 26 | expect(f.readAsStringSync(), equals('I am a text file')); 27 | f.deleteSync(recursive: false); 28 | }); 29 | 30 | test('#test download2', () async { 31 | const savePath = '../_download_test.md'; 32 | var dio = Dio(); 33 | dio.options.baseUrl = serverUrl.toString(); 34 | await dio.downloadUri( 35 | serverUrl.replace(path: '/download'), 36 | (header) => savePath, // disable gzip 37 | ); 38 | 39 | var f = File(savePath); 40 | expect(f.readAsStringSync(), equals('I am a text file')); 41 | f.deleteSync(recursive: false); 42 | }); 43 | 44 | test('#test download error', () async { 45 | const savePath = '../_download_test.md'; 46 | var dio = Dio(); 47 | dio.options.baseUrl = serverUrl.toString(); 48 | var r = 49 | await dio.download('/error', savePath).catchError((e) => e.response); 50 | assert(r.data == 'error'); 51 | r = await dio 52 | .download( 53 | '/error', 54 | savePath, 55 | options: Options(receiveDataWhenStatusError: false), 56 | ) 57 | .catchError((e) => e.response); 58 | assert(r.data == null); 59 | }); 60 | 61 | test('#test download timeout', () async { 62 | const savePath = '../_download_test.md'; 63 | var dio = Dio(BaseOptions( 64 | receiveTimeout: 1, 65 | baseUrl: serverUrl.toString(), 66 | )); 67 | expect(dio.download('/download', savePath).catchError((e) => throw e.type), 68 | throwsA(DioErrorType.receiveTimeout)); 69 | //print(r); 70 | }); 71 | 72 | test('#test download cancellation', () async { 73 | const savePath = '../_download_test.md'; 74 | var cancelToken = CancelToken(); 75 | Future.delayed(Duration(milliseconds: 100), () { 76 | cancelToken.cancel(); 77 | }); 78 | expect( 79 | Dio() 80 | .download( 81 | serverUrl.toString() + '/download', 82 | savePath, 83 | cancelToken: cancelToken, 84 | ) 85 | .catchError((e) => throw e.type), 86 | throwsA(DioErrorType.cancel), 87 | ); 88 | //print(r); 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /dio_http/test/echo_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:dio_http/adapter.dart'; 5 | import 'package:dio_http/dio_http.dart'; 6 | 7 | class EchoAdapter extends HttpClientAdapter { 8 | static const mockHost = 'mockserver'; 9 | static const mockBase = 'http://$mockHost'; 10 | final _adapter = DefaultHttpClientAdapter(); 11 | 12 | @override 13 | Future fetch(RequestOptions options, 14 | Stream? requestStream, Future? cancelFuture) async { 15 | final uri = options.uri; 16 | 17 | if (uri.host == mockHost) { 18 | if (requestStream != null) { 19 | return ResponseBody(requestStream, 200); 20 | } else { 21 | return ResponseBody.fromString(uri.path, 200); 22 | } 23 | } 24 | 25 | return _adapter.fetch(options, requestStream, cancelFuture); 26 | } 27 | 28 | @override 29 | void close({bool force = false}) { 30 | _adapter.close(force: force); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dio_http/test/encoding_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:dio_http/src/parameter.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | var data = { 7 | 'a': '你好', 8 | 'b': [5, '6'], 9 | 'c': { 10 | 'd': 8, 11 | 'e': { 12 | 'a': 5, 13 | 'b': [66, 8] 14 | } 15 | } 16 | }; 17 | test('#url encode default ', () { 18 | // a=你好&b=5&b=6&c[d]=8&c[e][a]=5&c[e][b]=66&c[e][b]=8 19 | var result = 20 | 'a=%E4%BD%A0%E5%A5%BD&b=5&b=6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66&c%5Be%5D%5Bb%5D=8'; 21 | expect(Transformer.urlEncodeMap(data), result); 22 | }); 23 | test('#url encode csv', () { 24 | // a=你好&b=5,6&c[d]=8&c[e][a]=5&c[e][b]=66,8 25 | var result = 26 | 'a=%E4%BD%A0%E5%A5%BD&b=5%2C6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66%2C8'; 27 | expect(Transformer.urlEncodeMap(data, ListFormat.csv), result); 28 | }); 29 | test('#url encode ssv', () { 30 | // a=你好&b=5+6&c[d]=8&c[e][a]=5&c[e][b]=66+8 31 | var result = 32 | 'a=%E4%BD%A0%E5%A5%BD&b=5+6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66+8'; 33 | expect(Transformer.urlEncodeMap(data, ListFormat.ssv), result); 34 | }); 35 | test('#url encode tsv', () { 36 | // a=你好&b=5\t6&c[d]=8&c[e][a]=5&c[e][b]=66\t8 37 | var result = 38 | 'a=%E4%BD%A0%E5%A5%BD&b=5%5Ct6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66%5Ct8'; 39 | expect(Transformer.urlEncodeMap(data, ListFormat.tsv), result); 40 | }); 41 | test('#url encode pipe', () { 42 | //a=你好&b=5|6&c[d]=8&c[e][a]=5&c[e][b]=66|8 43 | var result = 44 | 'a=%E4%BD%A0%E5%A5%BD&b=5%7C6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=66%7C8'; 45 | expect(Transformer.urlEncodeMap(data, ListFormat.pipes), result); 46 | }); 47 | 48 | test('#url encode multi', () { 49 | //a=你好&b[]=5&b[]=6&c[d]=8&c[e][a]=5&c[e][b][]=66&c[e][b][]=8 50 | var result = 51 | 'a=%E4%BD%A0%E5%A5%BD&b%5B%5D=5&b%5B%5D=6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D%5B%5D=66&c%5Be%5D%5Bb%5D%5B%5D=8'; 52 | expect(Transformer.urlEncodeMap(data, ListFormat.multiCompatible), result); 53 | }); 54 | 55 | test('#url encode multi2', () { 56 | var data = { 57 | 'a': 'string', 58 | 'b': 'another_string', 59 | 'z': ['string'], 60 | }; 61 | // a=string&b=another_string&z[]=string 62 | var result = 'a=string&b=another_string&z%5B%5D=string'; 63 | expect(Transformer.urlEncodeMap(data, ListFormat.multiCompatible), result); 64 | }); 65 | 66 | test('#url encode custom', () { 67 | //a=你好&b=5|6&c[d]=8&c[e][a]=5&c[e][b]=foo,bar&c[e][c]=foo+bar&c[e][d][]=foo&c[e][d][]=bar&c[e][e]=foo\tbar 68 | var result = 69 | 'a=%E4%BD%A0%E5%A5%BD&b=5%7C6&c%5Bd%5D=8&c%5Be%5D%5Ba%5D=5&c%5Be%5D%5Bb%5D=foo%2Cbar&c%5Be%5D%5Bc%5D=foo+bar&c%5Be%5D%5Bd%5D%5B%5D=foo&c%5Be%5D%5Bd%5D%5B%5D=bar&c%5Be%5D%5Be%5D=foo%5Ctbar'; 70 | expect( 71 | Transformer.urlEncodeMap( 72 | { 73 | 'a': '你好', 74 | 'b': ListParam([5, 6], ListFormat.pipes), 75 | 'c': { 76 | 'd': 8, 77 | 'e': { 78 | 'a': 5, 79 | 'b': ListParam(['foo', 'bar'], ListFormat.csv), 80 | 'c': ListParam(['foo', 'bar'], ListFormat.ssv), 81 | 'd': ListParam(['foo', 'bar'], ListFormat.multi), 82 | 'e': ListParam(['foo', 'bar'], ListFormat.tsv), 83 | }, 84 | }, 85 | }, 86 | ListFormat.multiCompatible, 87 | ), 88 | result, 89 | ); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /dio_http/test/exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('catch DioError', () async { 6 | dynamic error; 7 | 8 | try { 9 | await Dio().get('https://does.not.exist'); 10 | fail('did not throw'); 11 | } on DioError catch (e) { 12 | error = e; 13 | } 14 | 15 | expect(error, isNotNull); 16 | expect(error is Exception, isTrue); 17 | }); 18 | 19 | test('catch DioError as Exception', () async { 20 | dynamic error; 21 | 22 | try { 23 | await Dio().get('https://does.not.exist'); 24 | fail('did not throw'); 25 | } on Exception catch (e) { 26 | error = e; 27 | } 28 | 29 | expect(error, isNotNull); 30 | expect(error is Exception, isTrue); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /dio_http/test/formdata_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | @TestOn('vm') 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | import 'package:dio_http/dio_http.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() async { 11 | test('#test FormData', () async { 12 | var fm = FormData.fromMap({ 13 | 'name': 'wendux', 14 | 'age': 25, 15 | 'file': MultipartFile.fromString('hello world.'), 16 | 'files': [ 17 | await MultipartFile.fromFile( 18 | '../dio/test/_testfile', 19 | filename: '1.txt', 20 | ), 21 | MultipartFile.fromFileSync( 22 | '../dio/test/_testfile', 23 | filename: '2.txt', 24 | ), 25 | ] 26 | }); 27 | var fmStr = await fm.readAsBytes(); 28 | var f = File('../dio/test/_formdata'); 29 | var content = f.readAsStringSync(); 30 | content = content.replaceAll('--dio-boundary-3788753558', fm.boundary); 31 | var actual = utf8.decode(fmStr, allowMalformed: true); 32 | 33 | actual = actual.replaceAll('\r\n', '\n'); 34 | content = content.replaceAll('\r\n', '\n'); 35 | 36 | expect(actual, content); 37 | expect(fm.readAsBytes(), throwsA(const TypeMatcher())); 38 | 39 | var fm1 = FormData(); 40 | fm1.fields.add(MapEntry('name', 'wendux')); 41 | fm1.fields.add(MapEntry('age', '25')); 42 | fm1.files.add(MapEntry( 43 | 'file', 44 | MultipartFile.fromString('hello world.'), 45 | )); 46 | fm1.files.add(MapEntry( 47 | 'files', 48 | await MultipartFile.fromFile( 49 | '../dio/test/_testfile', 50 | filename: '1.txt', 51 | ), 52 | )); 53 | fm1.files.add(MapEntry( 54 | 'files', 55 | await MultipartFile.fromFile( 56 | '../dio/test/_testfile', 57 | filename: '2.txt', 58 | ), 59 | )); 60 | assert(fmStr.length == fm1.length); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /dio_http/test/mock_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'dart:typed_data'; 5 | import 'package:dio_http/dio_http.dart'; 6 | import 'package:dio_http/adapter.dart'; 7 | 8 | class MockAdapter extends HttpClientAdapter { 9 | static const mockHost = 'mockserver'; 10 | static const mockBase = 'http://$mockHost'; 11 | final _adapter = DefaultHttpClientAdapter(); 12 | 13 | @override 14 | Future fetch(RequestOptions options, 15 | Stream? requestStream, Future? cancelFuture) async { 16 | final uri = options.uri; 17 | if (uri.host == mockHost) { 18 | switch (uri.path) { 19 | case '/test': 20 | return ResponseBody.fromString( 21 | jsonEncode({ 22 | 'errCode': 0, 23 | 'data': {'path': uri.path} 24 | }), 25 | 200, 26 | headers: { 27 | Headers.contentTypeHeader: [Headers.jsonContentType], 28 | }, 29 | ); 30 | case '/test-auth': 31 | { 32 | return Future.delayed(Duration(milliseconds: 300), () { 33 | if (options.headers['csrfToken'] == null) { 34 | return ResponseBody.fromString( 35 | jsonEncode({ 36 | 'errCode': -1, 37 | 'data': {'path': uri.path} 38 | }), 39 | 401, 40 | headers: { 41 | Headers.contentTypeHeader: [Headers.jsonContentType], 42 | }, 43 | ); 44 | } 45 | return ResponseBody.fromString( 46 | jsonEncode({ 47 | 'errCode': 0, 48 | 'data': {'path': uri.path} 49 | }), 50 | 200, 51 | headers: { 52 | Headers.contentTypeHeader: [Headers.jsonContentType], 53 | }, 54 | ); 55 | }); 56 | } 57 | case '/download': 58 | return Future.delayed(Duration(milliseconds: 300), () { 59 | return ResponseBody( 60 | File('./README.md').openRead().cast(), 61 | 200, 62 | headers: { 63 | Headers.contentLengthHeader: [ 64 | File('./README.md').lengthSync().toString() 65 | ], 66 | }, 67 | ); 68 | }); 69 | 70 | case '/token': 71 | { 72 | var t = 'ABCDEFGHIJKLMN'.split('')..shuffle(); 73 | return ResponseBody.fromBytes( 74 | utf8.encode(jsonEncode({ 75 | 'errCode': 0, 76 | 'data': {'token': t.join()} 77 | })), 78 | 200, 79 | headers: { 80 | Headers.contentTypeHeader: [Headers.jsonContentType], 81 | }, 82 | ); 83 | } 84 | default: 85 | return ResponseBody.fromString('', 404); 86 | } 87 | } 88 | return _adapter.fetch(options, requestStream, cancelFuture); 89 | } 90 | 91 | @override 92 | void close({bool force = false}) { 93 | _adapter.close(force: force); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /dio_http/test/request_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn('vm') 6 | import 'dart:convert'; 7 | import 'package:dio_http/dio_http.dart'; 8 | import 'package:test/test.dart'; 9 | import 'utils.dart'; 10 | 11 | void main() { 12 | setUp(startServer); 13 | 14 | tearDown(stopServer); 15 | 16 | group('#test requests', () { 17 | late Dio dio; 18 | setUp(() { 19 | dio = Dio(); 20 | dio.options 21 | ..baseUrl = serverUrl.toString() 22 | ..connectTimeout = 1000 23 | ..receiveTimeout = 5000 24 | ..headers = {'User-Agent': 'dartisan'}; 25 | dio.interceptors.add(LogInterceptor( 26 | responseBody: true, 27 | requestBody: true, 28 | logPrint: (log) => { 29 | // ignore log 30 | }, 31 | )); 32 | }); 33 | test('#test restful APIs', () async { 34 | Response response; 35 | 36 | // test get 37 | response = await dio.get( 38 | '/test', 39 | queryParameters: {'id': '12', 'name': 'wendu'}, 40 | ); 41 | expect(response.statusCode, 200); 42 | expect(response.isRedirect, false); 43 | expect(response.data['query'], equals('id=12&name=wendu')); 44 | expect(response.headers.value('single'), equals('value')); 45 | 46 | const map = {'content': 'I am playload'}; 47 | 48 | // test post 49 | response = await dio.post('/test', data: map); 50 | expect(response.data['method'], 'POST'); 51 | expect(response.data['body'], jsonEncode(map)); 52 | 53 | // test put 54 | response = await dio.put('/test', data: map); 55 | expect(response.data['method'], 'PUT'); 56 | expect(response.data['body'], jsonEncode(map)); 57 | 58 | // test patch 59 | response = await dio.patch('/test', data: map); 60 | expect(response.data['method'], 'PATCH'); 61 | expect(response.data['body'], jsonEncode(map)); 62 | 63 | // test head 64 | response = await dio.delete('/test', data: map); 65 | expect(response.data['method'], 'DELETE'); 66 | expect(response.data['path'], '/test'); 67 | 68 | // error test 69 | expect(dio.get('/error').catchError((e) => throw e.response.statusCode), 70 | throwsA(equals(400))); 71 | 72 | // redirect test 73 | response = await dio.get( 74 | '/redirect', 75 | onReceiveProgress: (received, total) { 76 | // ignore progress 77 | }, 78 | ); 79 | assert(response.isRedirect == true); 80 | assert(response.redirects.length == 1); 81 | var ri = response.redirects.first; 82 | assert(ri.statusCode == 302); 83 | assert(ri.method == 'GET'); 84 | }); 85 | 86 | test('#test request with URI', () async { 87 | Response response; 88 | 89 | // test get 90 | response = await dio.getUri( 91 | Uri(path: '/test', queryParameters: {'id': '12', 'name': 'wendu'}), 92 | ); 93 | expect(response.statusCode, 200); 94 | expect(response.isRedirect, false); 95 | expect(response.data['query'], equals('id=12&name=wendu')); 96 | expect(response.headers.value('single'), equals('value')); 97 | 98 | const map = {'content': 'I am playload'}; 99 | 100 | // test post 101 | response = await dio.postUri(Uri(path: '/test'), data: map); 102 | expect(response.data['method'], 'POST'); 103 | expect(response.data['body'], jsonEncode(map)); 104 | 105 | // test put 106 | response = await dio.putUri(Uri(path: '/test'), data: map); 107 | expect(response.data['method'], 'PUT'); 108 | expect(response.data['body'], jsonEncode(map)); 109 | 110 | // test patch 111 | response = await dio.patchUri(Uri(path: '/test'), data: map); 112 | expect(response.data['method'], 'PATCH'); 113 | expect(response.data['body'], jsonEncode(map)); 114 | 115 | // test head 116 | response = await dio.deleteUri(Uri(path: '/test'), data: map); 117 | expect(response.data['method'], 'DELETE'); 118 | expect(response.data['path'], '/test'); 119 | }); 120 | 121 | test('#test redirect', () async { 122 | Response response; 123 | response = await dio.get('/redirect'); 124 | assert(response.isRedirect == true); 125 | assert(response.redirects.length == 1); 126 | var ri = response.redirects.first; 127 | assert(ri.statusCode == 302); 128 | assert(ri.method == 'GET'); 129 | assert(ri.location.path == '/'); 130 | }); 131 | 132 | test('#test generic parameters', () async { 133 | Response response; 134 | 135 | // default is "Map" 136 | response = await dio.get('/test'); 137 | assert(response.data is Map); 138 | 139 | // get response as `string` 140 | response = await dio.get('/test'); 141 | assert(response.data is String); 142 | 143 | // get response as `Map` 144 | response = await dio.get('/test'); 145 | assert(response.data is Map); 146 | 147 | // get response as `List` 148 | response = await dio.get('/list'); 149 | assert(response.data is List); 150 | expect(response.data[0], 1); 151 | }); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /dio_http/test/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/dio_http/test/test.jpg -------------------------------------------------------------------------------- /dio_http/test/upload_stream_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio_http/dio_http.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | void main() { 8 | var dio = Dio(); 9 | dio.options.baseUrl = 'http://httpbin.org/'; 10 | test('stream', () async { 11 | Response r; 12 | const str = 'hello 😌'; 13 | var bytes = utf8.encode(str).toList(); 14 | var stream = Stream.fromIterable(bytes.map((e) => [e])); 15 | r = await dio.put( 16 | '/put', 17 | data: stream, 18 | options: Options( 19 | contentType: Headers.textPlainContentType, 20 | headers: { 21 | Headers.contentLengthHeader: bytes.length, // set content-length 22 | }, 23 | ), 24 | ); 25 | expect(r.data['data'], str); 26 | }); 27 | 28 | test('file stream', () async { 29 | var f = File('../dio/test/test.jpg'); 30 | var r = await dio.put( 31 | '/put', 32 | data: f.openRead(), 33 | options: Options( 34 | contentType: 'image/jpeg', 35 | headers: { 36 | Headers.contentLengthHeader: f.lengthSync(), // set content-length 37 | }, 38 | ), 39 | ); 40 | var img = base64Encode(f.readAsBytesSync()); 41 | expect(r.data['data'], 'data:application/octet-stream;base64,' + img); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /dio_http/test/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | import 'dart:typed_data'; 9 | import 'package:pedantic/pedantic.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | /// The current server instance. 13 | HttpServer? _server; 14 | 15 | Encoding requiredEncodingForCharset(String charset) => 16 | Encoding.getByName(charset) ?? 17 | (throw FormatException('Unsupported encoding "$charset".')); 18 | 19 | /// The URL for the current server instance. 20 | Uri get serverUrl => Uri.parse('http://localhost:${_server?.port}'); 21 | 22 | /// Starts a new HTTP server. 23 | Future startServer() async { 24 | _server = (await HttpServer.bind('localhost', 0)) 25 | ..listen((request) async { 26 | var path = request.uri.path; 27 | var response = request.response; 28 | 29 | if (path == '/error') { 30 | const content = 'error'; 31 | response 32 | ..statusCode = 400 33 | ..contentLength = content.length 34 | ..write(content); 35 | unawaited(response.close()); 36 | return; 37 | } 38 | 39 | if (path == '/loop') { 40 | var n = int.parse(request.uri.query); 41 | response 42 | ..statusCode = 302 43 | ..headers 44 | .set('location', serverUrl.resolve('/loop?${n + 1}').toString()) 45 | ..contentLength = 0; 46 | unawaited(response.close()); 47 | return; 48 | } 49 | 50 | if (path == '/redirect') { 51 | response 52 | ..statusCode = 302 53 | ..headers.set('location', serverUrl.resolve('/').toString()) 54 | ..contentLength = 0; 55 | unawaited(response.close()); 56 | return; 57 | } 58 | 59 | if (path == '/no-content-length') { 60 | response 61 | ..statusCode = 200 62 | ..contentLength = -1 63 | ..write('body'); 64 | unawaited(response.close()); 65 | return; 66 | } 67 | 68 | if (path == '/list') { 69 | response.headers.contentType = ContentType('application', 'json'); 70 | response 71 | ..statusCode = 200 72 | ..contentLength = -1 73 | ..write('[1,2,3]'); 74 | unawaited(response.close()); 75 | return; 76 | } 77 | 78 | if (path == '/download') { 79 | const content = 'I am a text file'; 80 | response.headers.set('content-encoding', 'plain'); 81 | response 82 | ..statusCode = 200 83 | ..contentLength = content.length 84 | ..write(content); 85 | 86 | Future.delayed(Duration(milliseconds: 300), () { 87 | response.close(); 88 | }); 89 | return; 90 | } 91 | 92 | var requestBodyBytes = await ByteStream(request).toBytes(); 93 | var encodingName = request.uri.queryParameters['response-encoding']; 94 | var outputEncoding = encodingName == null 95 | ? ascii 96 | : requiredEncodingForCharset(encodingName); 97 | 98 | response.headers.contentType = 99 | ContentType('application', 'json', charset: outputEncoding.name); 100 | response.headers.set('single', 'value'); 101 | 102 | dynamic requestBody; 103 | if (requestBodyBytes.isEmpty) { 104 | requestBody = null; 105 | } else if (request.headers.contentType?.charset != null) { 106 | var encoding = 107 | requiredEncodingForCharset(request.headers.contentType!.charset!); 108 | requestBody = encoding.decode(requestBodyBytes); 109 | } else { 110 | requestBody = requestBodyBytes; 111 | } 112 | 113 | var content = { 114 | 'method': request.method, 115 | 'path': request.uri.path, 116 | 'query': request.uri.query, 117 | 'headers': {} 118 | }; 119 | if (requestBody != null) content['body'] = requestBody; 120 | request.headers.forEach((name, values) { 121 | // These headers are automatically generated by dart:io, so we don't 122 | // want to test them here. 123 | if (name == 'cookie' || name == 'host') return; 124 | 125 | content['headers'][name] = values; 126 | }); 127 | 128 | var body = json.encode(content); 129 | response 130 | ..contentLength = body.length 131 | ..write(body); 132 | unawaited(response.close()); 133 | }); 134 | } 135 | 136 | /// Stops the current HTTP server. 137 | void stopServer() { 138 | if (_server != null) { 139 | _server!.close(); 140 | _server = null; 141 | } 142 | } 143 | 144 | /// A matcher for functions that throw SocketException. 145 | final Matcher throwsSocketException = 146 | throwsA(const TypeMatcher()); 147 | 148 | /// A stream of chunks of bytes representing a single piece of data. 149 | class ByteStream extends StreamView> { 150 | ByteStream(Stream> stream) : super(stream); 151 | 152 | /// Returns a single-subscription byte stream that will emit the given bytes 153 | /// in a single chunk. 154 | factory ByteStream.fromBytes(List bytes) => 155 | ByteStream(Stream.fromIterable([bytes])); 156 | 157 | /// Collects the data of this stream in a [Uint8List]. 158 | Future toBytes() { 159 | var completer = Completer(); 160 | var sink = ByteConversionSink.withCallback( 161 | (bytes) => completer.complete(Uint8List.fromList(bytes))); 162 | listen(sink.add, 163 | onError: completer.completeError, 164 | onDone: sink.close, 165 | cancelOnError: true); 166 | return completer.future; 167 | } 168 | 169 | /// Collect the data of this stream in a [String], decoded according to 170 | /// [encoding], which defaults to `UTF8`. 171 | Future bytesToString([Encoding encoding = utf8]) => 172 | encoding.decodeStream(this); 173 | 174 | Stream toStringStream([Encoding encoding = utf8]) => 175 | encoding.decoder.bind(this); 176 | } 177 | -------------------------------------------------------------------------------- /doc/custom.html: -------------------------------------------------------------------------------- 1 | 5 | 21 | 29 | 30 | -------------------------------------------------------------------------------- /example/adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | import 'package:dio_http/dio_http.dart'; 4 | import 'package:dio_http/adapter.dart'; 5 | 6 | class MyAdapter extends HttpClientAdapter { 7 | final DefaultHttpClientAdapter _adapter = DefaultHttpClientAdapter(); 8 | 9 | @override 10 | Future fetch(RequestOptions options, 11 | Stream? requestStream, Future? cancelFuture) async { 12 | var uri = options.uri; 13 | // hook requests to google.com 14 | if (uri.host == 'google.com') { 15 | return ResponseBody.fromString('Too young too simple!', 200); 16 | } 17 | return _adapter.fetch(options, requestStream, cancelFuture); 18 | } 19 | 20 | @override 21 | void close({bool force = false}) { 22 | _adapter.close(force: force); 23 | } 24 | } 25 | 26 | void main() async { 27 | var dio = Dio(); 28 | dio.httpClientAdapter = MyAdapter(); 29 | var response = await dio.get('https://google.com'); 30 | print(response); 31 | response = await dio.get('https://baidu.com'); 32 | print(response); 33 | } 34 | -------------------------------------------------------------------------------- /example/bee.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/bee.mp4 -------------------------------------------------------------------------------- /example/cancel_request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:dio_http/dio_http.dart'; 3 | 4 | void main() async { 5 | var dio = Dio(); 6 | dio.interceptors.add(LogInterceptor()); 7 | // Token can be shared with different requests. 8 | var token = CancelToken(); 9 | // In one minute, we cancel! 10 | Timer(Duration(milliseconds: 500), () { 11 | token.cancel('cancelled'); 12 | }); 13 | 14 | // The follow three requests with the same token. 15 | var url1 = 'https://www.google.com'; 16 | var url2 = 'https://www.facebook.com'; 17 | var url3 = 'https://www.baidu.com'; 18 | 19 | await Future.wait([ 20 | dio 21 | .get(url1, cancelToken: token) 22 | .then((response) => print('${response.requestOptions.path}: succeed!')) 23 | .catchError( 24 | (e) { 25 | if (CancelToken.isCancel(e)) { 26 | print('$url1: $e'); 27 | } 28 | }, 29 | ), 30 | dio 31 | .get(url2, cancelToken: token) 32 | .then((response) => print('${response.requestOptions.path}: succeed!')) 33 | .catchError((e) { 34 | if (CancelToken.isCancel(e)) { 35 | print('$url2: $e'); 36 | } 37 | }), 38 | dio 39 | .get(url3, cancelToken: token) 40 | .then((response) => print('${response.requestOptions.path}: succeed!')) 41 | .catchError((e) { 42 | if (CancelToken.isCancel(e)) { 43 | print('$url3: $e'); 44 | } 45 | print(e); 46 | }) 47 | ]); 48 | } 49 | -------------------------------------------------------------------------------- /example/cookie_mgr.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:dio_http_cookie_manager/dio_http_cookie_manager.dart'; 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | 5 | void main() async { 6 | var dio = Dio(); 7 | var cookieJar = CookieJar(); 8 | dio.interceptors 9 | ..add(LogInterceptor()) 10 | ..add(CookieManager(cookieJar)); 11 | await dio.get('https://baidu.com/'); 12 | // Print cookies 13 | print(cookieJar.loadForRequest(Uri.parse('https://baidu.com/'))); 14 | // second request with the cookie 15 | await dio.get('https://baidu.com/'); 16 | } 17 | -------------------------------------------------------------------------------- /example/custom_cache_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | class CacheInterceptor extends Interceptor { 4 | CacheInterceptor(); 5 | 6 | final _cache = {}; 7 | 8 | @override 9 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 10 | var response = _cache[options.uri]; 11 | if (options.extra['refresh'] == true) { 12 | print('${options.uri}: force refresh, ignore cache! \n'); 13 | return handler.next(options); 14 | } else if (response != null) { 15 | print('cache hit: ${options.uri} \n'); 16 | return handler.resolve(response); 17 | } 18 | super.onRequest(options, handler); 19 | } 20 | 21 | @override 22 | void onResponse(Response response, ResponseInterceptorHandler handler) { 23 | _cache[response.requestOptions.uri] = response; 24 | super.onResponse(response, handler); 25 | } 26 | 27 | @override 28 | void onError(DioError err, ErrorInterceptorHandler handler) { 29 | print('onError: $err'); 30 | super.onError(err, handler); 31 | } 32 | } 33 | 34 | void main() async { 35 | var dio = Dio(); 36 | dio.options.baseUrl = 'https://baidu.com'; 37 | dio.interceptors 38 | ..add(CacheInterceptor()) 39 | ..add(LogInterceptor(requestHeader: false, responseHeader: false)); 40 | 41 | await dio.get('/'); // second request 42 | await dio.get('/'); // Will hit cache 43 | // Force refresh 44 | await dio.get('/', options: Options(extra: {'refresh': true})); 45 | } 46 | -------------------------------------------------------------------------------- /example/dio.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dio_http/dio_http.dart'; 3 | 4 | void main() async { 5 | var dio = Dio(); 6 | dio.options 7 | ..baseUrl = 'http://httpbin.org/' 8 | ..connectTimeout = 5000 //5s 9 | ..receiveTimeout = 5000 10 | ..validateStatus = (int? status) { 11 | return status != null && status > 0; 12 | } 13 | ..headers = { 14 | HttpHeaders.userAgentHeader: 'dio', 15 | 'common-header': 'xx', 16 | }; 17 | 18 | // Or you can create dio instance and config it as follow: 19 | // var dio = Dio(BaseOptions( 20 | // baseUrl: "http://www.dtworkroom.com/doris/1/2.0.0/", 21 | // connectTimeout: 5000, 22 | // receiveTimeout: 5000, 23 | // headers: { 24 | // HttpHeaders.userAgentHeader: 'dio', 25 | // 'common-header': 'xx', 26 | // }, 27 | // )); 28 | dio.interceptors 29 | ..add(InterceptorsWrapper( 30 | onRequest: (options, handler) { 31 | // return handler.resolve( Response(data:"xxx")); 32 | // return handler.reject( DioError(message: "eh")); 33 | return handler.next(options); 34 | }, 35 | )) 36 | ..add(LogInterceptor(responseBody: false)); //Open log; 37 | 38 | var response = await dio.get('https://www.google.com/'); 39 | 40 | // Download a file 41 | response = await dio.download( 42 | 'https://www.google.com/', 43 | './example/xx.html', 44 | queryParameters: {'a': 1}, 45 | onReceiveProgress: (received, total) { 46 | if (total != -1) { 47 | print('$received,$total'); 48 | } 49 | }, 50 | ); 51 | 52 | // Create a FormData 53 | var formData = FormData.fromMap({ 54 | 'age': 25, 55 | 'file': await MultipartFile.fromFile( 56 | './example/upload.txt', 57 | filename: 'upload.txt', 58 | ) 59 | }); 60 | 61 | // Send FormData 62 | response = await dio.post('/test', data: formData); 63 | print(response); 64 | 65 | // post data with "application/x-www-form-urlencoded" format 66 | response = await dio.post( 67 | '/test', 68 | data: { 69 | 'id': 8, 70 | 'info': { 71 | 'name': 'wendux', 72 | 'age': 25, 73 | } 74 | }, 75 | options: Options( 76 | contentType: Headers.formUrlEncodedContentType, 77 | ), 78 | ); 79 | print(response.data); 80 | } 81 | -------------------------------------------------------------------------------- /example/download.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:dio_http/dio_http.dart'; 4 | 5 | // In this example we download a image and listen the downloading progress. 6 | void main() async { 7 | var dio = Dio(); 8 | dio.interceptors.add(LogInterceptor()); 9 | // This is big file(about 200M) 10 | // var url = "http://download.dcloud.net.cn/HBuilder.9.0.2.macosx_64.dmg"; 11 | 12 | var url = 13 | 'https://cdn.jsdelivr.net/gh/flutterchina/flutter-in-action@1.0/docs/imgs/book.jpg'; 14 | 15 | // var url = "https://www.baidu.com/img/bdlogo.gif"; 16 | await download1(dio, url, './example/book.jpg'); 17 | await download1(dio, url, (Headers headers) => './example/book1.jpg'); 18 | await download2(dio, url, './example/book2.jpg'); 19 | } 20 | 21 | Future download1(Dio dio, String url, savePath) async { 22 | var cancelToken = CancelToken(); 23 | try { 24 | await dio.download( 25 | url, 26 | savePath, 27 | onReceiveProgress: showDownloadProgress, 28 | cancelToken: cancelToken, 29 | ); 30 | } catch (e) { 31 | print(e); 32 | } 33 | } 34 | 35 | //Another way to downloading small file 36 | Future download2(Dio dio, String url, String savePath) async { 37 | try { 38 | var response = await dio.get( 39 | url, 40 | onReceiveProgress: showDownloadProgress, 41 | //Received data with List 42 | options: Options( 43 | responseType: ResponseType.bytes, 44 | followRedirects: false, 45 | receiveTimeout: 0, 46 | ), 47 | ); 48 | print(response.headers); 49 | var file = File(savePath); 50 | var raf = file.openSync(mode: FileMode.write); 51 | // response.data is List type 52 | raf.writeFromSync(response.data); 53 | await raf.close(); 54 | } catch (e) { 55 | print(e); 56 | } 57 | } 58 | 59 | void showDownloadProgress(received, total) { 60 | if (total != -1) { 61 | print((received / total * 100).toStringAsFixed(0) + '%'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/download_with_trunks.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:dio_http/dio_http.dart'; 4 | 5 | void main() async { 6 | var url = 'http://download.dcloud.net.cn/HBuilder.9.0.2.macosx_64.dmg'; 7 | var savePath = './example/HBuilder.9.0.2.macosx_64.dmg'; 8 | 9 | // var url = "https://www.baidu.com/img/bdlogo.gif"; 10 | // var savePath = "./example/bg.gif"; 11 | 12 | await downloadWithChunks(url, savePath, onReceiveProgress: (received, total) { 13 | if (total != -1) { 14 | print('${(received / total * 100).floor()}%'); 15 | } 16 | }); 17 | } 18 | 19 | /// Downloading by spiting as file in chunks 20 | Future downloadWithChunks( 21 | url, 22 | savePath, { 23 | ProgressCallback? onReceiveProgress, 24 | }) async { 25 | const firstChunkSize = 102; 26 | const maxChunk = 3; 27 | 28 | var total = 0; 29 | var dio = Dio(); 30 | var progress = []; 31 | 32 | void Function(int, int) createCallback(no) { 33 | return (int received, int _) { 34 | progress[no] = received; 35 | if (onReceiveProgress != null && total != 0) { 36 | onReceiveProgress(progress.reduce((a, b) => a + b), total); 37 | } 38 | }; 39 | } 40 | 41 | Future downloadChunk(url, start, end, no) async { 42 | progress.add(0); 43 | --end; 44 | return dio.download( 45 | url, 46 | savePath + 'temp$no', 47 | onReceiveProgress: createCallback(no), 48 | options: Options( 49 | headers: {'range': 'bytes=$start-$end'}, 50 | ), 51 | ); 52 | } 53 | 54 | Future mergeTempFiles(chunk) async { 55 | var f = File(savePath + 'temp0'); 56 | var ioSink = f.openWrite(mode: FileMode.writeOnlyAppend); 57 | for (var i = 1; i < chunk; ++i) { 58 | var _f = File(savePath + 'temp$i'); 59 | await ioSink.addStream(_f.openRead()); 60 | await _f.delete(); 61 | } 62 | await ioSink.close(); 63 | await f.rename(savePath); 64 | } 65 | 66 | var response = await downloadChunk(url, 0, firstChunkSize, 0); 67 | if (response.statusCode == 206) { 68 | total = int.parse(response.headers 69 | .value(HttpHeaders.contentRangeHeader)! 70 | .split('/') 71 | .last); 72 | var reserved = 73 | total - int.parse(response.headers.value(Headers.contentLengthHeader)!); 74 | var chunk = (reserved / firstChunkSize).ceil() + 1; 75 | if (chunk > 1) { 76 | var chunkSize = firstChunkSize; 77 | if (chunk > maxChunk + 1) { 78 | chunk = maxChunk + 1; 79 | chunkSize = (reserved / maxChunk).ceil(); 80 | } 81 | var futures = []; 82 | for (var i = 0; i < maxChunk; ++i) { 83 | var start = firstChunkSize + i * chunkSize; 84 | futures.add(downloadChunk(url, start, start + chunkSize, i + 1)); 85 | } 86 | await Future.wait(futures); 87 | } 88 | await mergeTempFiles(chunk); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /example/extend_dio.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:dio_http/native_imp.dart'; 3 | 4 | class HttpService extends DioForNative { 5 | HttpService([BaseOptions? baseOptions]) : super(baseOptions) { 6 | options 7 | ..baseUrl = 'http://httpbin.org/' 8 | ..contentType = Headers.jsonContentType; 9 | } 10 | 11 | Future echo(String data) { 12 | return post('/post', data: data).then((resp) => resp.data['data']); 13 | } 14 | } 15 | 16 | void main() async { 17 | var httpService = HttpService(); 18 | var res = await httpService.echo('hello server!'); 19 | print(res); 20 | } 21 | -------------------------------------------------------------------------------- /example/flutter_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/flutter_app/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 60bd88df915880d23877bfc1602e8ddcf4c4dd2a 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/flutter_app/README.md: -------------------------------------------------------------------------------- 1 | # flutter_app 2 | 3 | A new Flutter application for dio test 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/flutter_app/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 30 29 | 30 | defaultConfig { 31 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 32 | applicationId "club.flutterchina.flutter_app" 33 | minSdkVersion 16 34 | targetSdkVersion 30 35 | versionCode flutterVersionCode.toInteger() 36 | versionName flutterVersionName 37 | } 38 | 39 | buildTypes { 40 | release { 41 | // TODO: Add your own signing config for the release build. 42 | // Signing with the debug keys for now, so `flutter run --release` works. 43 | signingConfig signingConfigs.debug 44 | } 45 | } 46 | } 47 | 48 | flutter { 49 | source '../..' 50 | } 51 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/java/club/flutterchina/flutter_app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package club.flutterchina.flutter_app; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/flutter_app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_app/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.1.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/flutter_app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/flutter_app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/flutter_app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/flutter_app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/flutter_app/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/flutter_app/lib/http.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | var dio = Dio(); 4 | -------------------------------------------------------------------------------- /example/flutter_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | //import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:dio_http/dio_http.dart'; 7 | import 'http.dart'; // make dio as global top-level variable 8 | import 'routes/request.dart'; 9 | 10 | // Must be top-level function 11 | _parseAndDecode(String response) { 12 | return jsonDecode(response); 13 | } 14 | 15 | parseJson(String text) { 16 | return compute(_parseAndDecode, text); 17 | } 18 | 19 | void main() { 20 | // add interceptors 21 | //dio.interceptors.add(CookieManager(CookieJar())); 22 | dio.interceptors.add(LogInterceptor()); 23 | //(dio.transformer as DefaultTransformer).jsonDecodeCallback = parseJson; 24 | dio.options.receiveTimeout = 15000; 25 | // (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = 26 | // (client) { 27 | // client.findProxy = (uri) { 28 | // //proxy to my PC(charles) 29 | // return "PROXY 10.1.10.250:8888"; 30 | // }; 31 | // }; 32 | runApp(MyApp()); 33 | } 34 | 35 | class MyApp extends StatelessWidget { 36 | // This widget is the root of your application. 37 | @override 38 | Widget build(BuildContext context) { 39 | return MaterialApp( 40 | title: 'Flutter Demo', 41 | theme: ThemeData( 42 | primarySwatch: Colors.blue, 43 | ), 44 | home: MyHomePage(title: 'Flutter Demo Home Page'), 45 | ); 46 | } 47 | } 48 | 49 | class MyHomePage extends StatefulWidget { 50 | MyHomePage({Key? key, this.title = ''}) : super(key: key); 51 | 52 | final String title; 53 | 54 | @override 55 | _MyHomePageState createState() => _MyHomePageState(); 56 | } 57 | 58 | class _MyHomePageState extends State { 59 | String _text = ""; 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Scaffold( 64 | appBar: AppBar( 65 | title: Text(widget.title), 66 | ), 67 | body: Container( 68 | padding: EdgeInsets.all(16), 69 | child: Column(children: [ 70 | ElevatedButton( 71 | child: Text("Request"), 72 | onPressed: () async { 73 | try { 74 | await dio 75 | .get("http://httpbin.org/status/404") 76 | .then((r) { 77 | setState(() { 78 | print(r.data); 79 | _text = r.data!.replaceAll(RegExp(r"\s"), ""); 80 | }); 81 | }); 82 | } catch (e) { 83 | print("sss"); 84 | print(e); 85 | } 86 | }, 87 | ), 88 | ElevatedButton( 89 | child: Text("Open new page5"), 90 | onPressed: () { 91 | Navigator.push(context, MaterialPageRoute(builder: (context) { 92 | return RequestRoute(); 93 | })); 94 | }, 95 | ), 96 | Expanded( 97 | child: SingleChildScrollView( 98 | child: Text(_text), 99 | ), 100 | ) 101 | ]), 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /example/flutter_app/lib/routes/request.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../http.dart'; 3 | 4 | class RequestRoute extends StatefulWidget { 5 | @override 6 | _RequestRouteState createState() => _RequestRouteState(); 7 | } 8 | 9 | class _RequestRouteState extends State { 10 | String _text = ""; 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: Text("New Page"), 16 | ), 17 | body: Container( 18 | padding: EdgeInsets.all(16), 19 | child: Column(children: [ 20 | ElevatedButton( 21 | child: Text("Request"), 22 | onPressed: () { 23 | dio.get("http://httpbin.org/get").then((r) { 24 | setState(() { 25 | _text = r.data!; 26 | }); 27 | }); 28 | }, 29 | ), 30 | Expanded( 31 | child: SingleChildScrollView( 32 | child: Text(_text), 33 | ), 34 | ) 35 | ]), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/flutter_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_app 2 | description: A new Flutter application for dio test 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | dio_http: 27 | path: ../../dio_http 28 | 29 | 30 | # The following adds the Cupertino Icons font to your application. 31 | # Use with the CupertinoIcons class for iOS style icons. 32 | cupertino_icons: ^1.0.2 33 | 34 | dev_dependencies: 35 | flutter_test: 36 | sdk: flutter 37 | 38 | # For information on the generic Dart part of this file, see the 39 | # following page: https://dart.dev/tools/pub/pubspec 40 | 41 | # The following section is specific to Flutter. 42 | flutter: 43 | 44 | # The following line ensures that the Material Icons font is 45 | # included with your application, so that you can use the icons in 46 | # the material Icons class. 47 | uses-material-design: true 48 | 49 | # To add assets to your application, add an assets section, like this: 50 | # assets: 51 | # - images/a_dot_burr.jpeg 52 | # - images/a_dot_ham.jpeg 53 | 54 | # An image asset can refer to one or more resolution-specific "variants", see 55 | # https://flutter.dev/assets-and-images/#resolution-aware. 56 | 57 | # For details regarding adding assets from package dependencies, see 58 | # https://flutter.dev/assets-and-images/#from-packages 59 | 60 | # To add custom fonts to your application, add a fonts section here, 61 | # in this "flutter" section. Each entry in this list should have a 62 | # "family" key with the font family name, and a "fonts" key with a 63 | # list giving the asset and other descriptors for the font. For 64 | # example: 65 | # fonts: 66 | # - family: Schyler 67 | # fonts: 68 | # - asset: fonts/Schyler-Regular.ttf 69 | # - asset: fonts/Schyler-Italic.ttf 70 | # style: italic 71 | # - family: Trajan Pro 72 | # fonts: 73 | # - asset: fonts/TrajanPro.ttf 74 | # - asset: fonts/TrajanPro_Bold.ttf 75 | # weight: 700 76 | # 77 | # For details regarding fonts from package dependencies, 78 | # see https://flutter.dev/custom-fonts/#from-packages 79 | -------------------------------------------------------------------------------- /example/flutter_app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/formdata.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:dio_http/dio_http.dart'; 4 | import 'package:dio_http/adapter.dart'; 5 | 6 | void showProgress(received, total) { 7 | if (total != -1) { 8 | print((received / total * 100).toStringAsFixed(0) + '%'); 9 | } 10 | } 11 | 12 | Future FormData1() async { 13 | return FormData.fromMap({ 14 | 'name': 'wendux', 15 | 'age': 25, 16 | 'file': await MultipartFile.fromFile( 17 | './example/xx.png', 18 | filename: 'xx.png', 19 | ), 20 | 'files': [ 21 | await MultipartFile.fromFile( 22 | './example/upload.txt', 23 | filename: 'upload.txt', 24 | ), 25 | MultipartFile.fromFileSync( 26 | './example/upload.txt', 27 | filename: 'upload.txt', 28 | ), 29 | ] 30 | }); 31 | } 32 | 33 | Future FormData2() async { 34 | var formData = FormData(); 35 | 36 | formData.fields 37 | ..add(MapEntry( 38 | 'name', 39 | 'wendux', 40 | )) 41 | ..add(MapEntry( 42 | 'age', 43 | '25', 44 | )); 45 | 46 | formData.files.add(MapEntry( 47 | 'file', 48 | await MultipartFile.fromFile( 49 | './example/xx.png', 50 | filename: 'xx.png', 51 | ), 52 | )); 53 | 54 | formData.files.addAll([ 55 | MapEntry( 56 | 'files', 57 | await MultipartFile.fromFile( 58 | './example/upload.txt', 59 | filename: 'upload.txt', 60 | ), 61 | ), 62 | MapEntry( 63 | 'files', 64 | MultipartFile.fromFileSync( 65 | './example/upload.txt', 66 | filename: 'upload.txt', 67 | ), 68 | ), 69 | ]); 70 | return formData; 71 | } 72 | 73 | Future FormData3() async { 74 | return FormData.fromMap({ 75 | 'file': await MultipartFile.fromFile( 76 | './example/upload.txt', 77 | filename: 'uploadfile', 78 | ), 79 | }); 80 | } 81 | 82 | /// FormData will create readable "multipart/form-data" streams. 83 | /// It can be used to submit forms and file uploads to http server. 84 | void main() async { 85 | var dio = Dio(); 86 | dio.options.baseUrl = 'http://localhost:3000/'; 87 | dio.interceptors.add(LogInterceptor()); 88 | //dio.interceptors.add(LogInterceptor(requestBody: true)); 89 | (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = 90 | (HttpClient client) { 91 | client.findProxy = (uri) { 92 | //proxy all request to localhost:8888 93 | return 'PROXY localhost:8888'; 94 | }; 95 | client.badCertificateCallback = 96 | (X509Certificate cert, String host, int port) => true; 97 | }; 98 | Response response; 99 | 100 | var formData1 = await FormData1(); 101 | var formData2 = await FormData2(); 102 | var bytes1 = await formData1.readAsBytes(); 103 | var bytes2 = await formData2.readAsBytes(); 104 | assert(bytes1.length == bytes2.length); 105 | 106 | var t = await FormData3(); 107 | print(utf8.decode(await t.readAsBytes())); 108 | 109 | response = await dio.post( 110 | //"/upload", 111 | 'http://localhost:3000/upload', 112 | data: await FormData3(), 113 | onSendProgress: (received, total) { 114 | if (total != -1) { 115 | print((received / total * 100).toStringAsFixed(0) + '%'); 116 | } 117 | }, 118 | ); 119 | print(response); 120 | } 121 | -------------------------------------------------------------------------------- /example/generic.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | void main() async { 4 | var dio = Dio( 5 | BaseOptions( 6 | baseUrl: 'http://httpbin.org/', 7 | method: 'GET', 8 | ), 9 | ); 10 | 11 | Response response; 12 | // No generic type, the ResponseType will work. 13 | response = await dio.get('/get'); 14 | print(response.data is Map); 15 | // Specify the generic type(Map) 16 | response = await dio.get('/get'); 17 | print(response.data is Map); 18 | 19 | // Specify the generic type(String) 20 | response = await dio.get('/get'); 21 | print(response.data is String); 22 | // Specify the ResponseType as ResponseType.plain 23 | response = await dio.get( 24 | '/get', 25 | options: Options(responseType: ResponseType.plain), 26 | ); 27 | print(response.data is String); 28 | 29 | // the content of "https://baidu.com" is a html file, So it can't be convert to Map type, 30 | // it will cause a Error (type 'String' is not a subtype of type 'Map') 31 | try { 32 | response = await dio.get('https://baidu.com'); 33 | } on DioError catch (e) { 34 | print(e.message); 35 | } 36 | 37 | // This works well. 38 | response = await dio.get('https://baidu.com'); 39 | 40 | // This works well too. 41 | response = await dio.get('https://baidu.com'); 42 | 43 | // This is the recommended way. 44 | var r = await dio.get('https://baidu.com'); 45 | print(r.data?.length); 46 | } 47 | -------------------------------------------------------------------------------- /example/http2_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:dio_http2_adapter/dio_http2_adapter.dart'; 3 | 4 | void main() async { 5 | var dio = Dio() 6 | ..options.baseUrl = 'https://google.com' 7 | ..interceptors.add(LogInterceptor()) 8 | ..httpClientAdapter = Http2Adapter( 9 | ConnectionManager(idleTimeout: 10000), 10 | ); 11 | 12 | Response response; 13 | response = await dio.get('/?xx=6'); 14 | response.redirects.forEach((e) { 15 | print('redirect: ${e.statusCode} ${e.location}'); 16 | }); 17 | print(response.data); 18 | } 19 | -------------------------------------------------------------------------------- /example/interceptor_lock.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dio_http/dio_http.dart'; 4 | 5 | void main() async { 6 | var dio = Dio(); 7 | // dio instance to request token 8 | var tokenDio = Dio(); 9 | String? csrfToken; 10 | dio.options.baseUrl = 'http://www.dtworkroom.com/doris/1/2.0.0/'; 11 | tokenDio.options = dio.options; 12 | dio.interceptors.add(InterceptorsWrapper( 13 | onRequest: (options, handler) { 14 | print('send request:path:${options.path},baseURL:${options.baseUrl}'); 15 | if (csrfToken == null) { 16 | print('no token,request token firstly...'); 17 | dio.lock(); 18 | //print(dio.interceptors.requestLock.locked); 19 | tokenDio.get('/token').then((d) { 20 | options.headers['csrfToken'] = csrfToken = d.data['data']['token']; 21 | print('request token succeed, value: ' + d.data['data']['token']); 22 | print( 23 | 'continue to perform request:path:${options.path},baseURL:${options.path}'); 24 | handler.next(options); 25 | }).catchError((error, stackTrace) { 26 | handler.reject(error, true); 27 | }).whenComplete(() => dio.unlock()); // unlock the dio 28 | } else { 29 | options.headers['csrfToken'] = csrfToken; 30 | return handler.next(options); 31 | } 32 | }, 33 | onError: (error, handler) { 34 | //print(error); 35 | // Assume 401 stands for token expired 36 | if (error.response?.statusCode == 401) { 37 | var options = error.response!.requestOptions; 38 | // If the token has been updated, repeat directly. 39 | if (csrfToken != options.headers['csrfToken']) { 40 | options.headers['csrfToken'] = csrfToken; 41 | //repeat 42 | dio.fetch(options).then( 43 | (r) => handler.resolve(r), 44 | onError: (e) { 45 | handler.reject(e); 46 | }, 47 | ); 48 | return; 49 | } 50 | // update token and repeat 51 | // Lock to block the incoming request until the token updated 52 | dio.lock(); 53 | dio.interceptors.responseLock.lock(); 54 | dio.interceptors.errorLock.lock(); 55 | tokenDio.get('/token').then((d) { 56 | //update csrfToken 57 | options.headers['csrfToken'] = csrfToken = d.data['data']['token']; 58 | }).whenComplete(() { 59 | dio.unlock(); 60 | dio.interceptors.responseLock.unlock(); 61 | dio.interceptors.errorLock.unlock(); 62 | }).then((e) { 63 | //repeat 64 | dio.fetch(options).then( 65 | (r) => handler.resolve(r), 66 | onError: (e) { 67 | handler.reject(e); 68 | }, 69 | ); 70 | }); 71 | return; 72 | } 73 | return handler.next(error); 74 | }, 75 | )); 76 | 77 | FutureOr _onResult(d) { 78 | print('request ok!'); 79 | } 80 | 81 | await Future.wait([ 82 | dio.get('/test?tag=1').then(_onResult), 83 | dio.get('/test?tag=2').then(_onResult), 84 | dio.get('/test?tag=3').then(_onResult) 85 | ]); 86 | } 87 | -------------------------------------------------------------------------------- /example/options.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dio_http/dio_http.dart'; 3 | 4 | void main() async { 5 | var dio = Dio(BaseOptions( 6 | baseUrl: 'http://httpbin.org/', 7 | connectTimeout: 5000, 8 | receiveTimeout: 100000, 9 | // 5s 10 | headers: { 11 | HttpHeaders.userAgentHeader: 'dio', 12 | 'api': '1.0.0', 13 | }, 14 | contentType: Headers.jsonContentType, 15 | // Transform the response data to a String encoded with UTF8. 16 | // The default value is [ResponseType.JSON]. 17 | responseType: ResponseType.plain, 18 | )); 19 | 20 | Response response; 21 | 22 | response = await dio.get('/get'); 23 | print(response.data); 24 | 25 | var responseMap = await dio.get( 26 | '/get', 27 | // Transform response data to Json Map 28 | options: Options(responseType: ResponseType.json), 29 | ); 30 | print(responseMap.data); 31 | response = await dio.post( 32 | '/post', 33 | data: { 34 | 'id': 8, 35 | 'info': {'name': 'wendux', 'age': 25} 36 | }, 37 | // Send data with "application/x-www-form-urlencoded" format 38 | options: Options( 39 | contentType: Headers.formUrlEncodedContentType, 40 | ), 41 | ); 42 | print(response.data); 43 | 44 | response = await dio.fetch( 45 | RequestOptions(path: 'https://baidu.com/'), 46 | ); 47 | print(response.data); 48 | } 49 | -------------------------------------------------------------------------------- /example/post_stream_and_bytes.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:dio_http/dio_http.dart'; 6 | 7 | void main() async { 8 | var dio = Dio(BaseOptions( 9 | connectTimeout: 5000, 10 | baseUrl: 'http://httpbin.org/', 11 | )); 12 | 13 | dio.interceptors.add(LogInterceptor(responseBody: true)); 14 | 15 | var file = File('./example/bee.mp4'); 16 | 17 | // Sending stream 18 | await dio.post( 19 | 'post', 20 | data: file.openRead(), 21 | options: Options( 22 | headers: { 23 | HttpHeaders.contentTypeHeader: ContentType.text, 24 | HttpHeaders.contentLengthHeader: file.lengthSync(), 25 | // HttpHeaders.authorizationHeader: 'Bearer $token', 26 | }, 27 | ), 28 | ); 29 | 30 | // Sending bytes with Stream(Just an example, you can send json(Map) directly in action) 31 | var postData = utf8.encode('{"userName":"wendux"}'); 32 | await dio.post( 33 | 'post', 34 | data: Stream.fromIterable(postData.map((e) => [e])), 35 | options: Options( 36 | headers: { 37 | HttpHeaders.contentLengthHeader: postData.length, // Set content-length 38 | }, 39 | ), 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /example/proxy.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dio_http/dio_http.dart'; 3 | import 'package:dio_http/adapter.dart'; 4 | 5 | void main() async { 6 | var dio = Dio(); 7 | dio.options 8 | ..headers['user-agent'] = 'xxx' 9 | ..contentType = 'text'; 10 | // dio.options.connectTimeout = 2000; 11 | // More about HttpClient proxy topic please refer to Dart SDK doc. 12 | (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = 13 | (HttpClient client) { 14 | client.findProxy = (uri) { 15 | //proxy all request to localhost:8888 16 | return 'PROXY localhost:8888'; 17 | }; 18 | client.badCertificateCallback = 19 | (X509Certificate cert, String host, int port) => true; 20 | }; 21 | 22 | Response response; 23 | response = await dio.get('https://www.baidu.com'); 24 | print(response.statusCode); 25 | response = await dio.get('https://www.baidu.com'); 26 | print(response.statusCode); 27 | } 28 | -------------------------------------------------------------------------------- /example/request_interceptors.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | void main() async { 4 | var dio = Dio(); 5 | dio.options.baseUrl = 'http://httpbin.org/'; 6 | dio.options.connectTimeout = 5000; 7 | dio.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) { 8 | switch (options.path) { 9 | case '/fakepath1': 10 | return handler.resolve( 11 | Response( 12 | requestOptions: options, 13 | data: 'fake data', 14 | ), 15 | ); 16 | case '/fakepath2': 17 | dio 18 | .get('/get') 19 | .then(handler.resolve) 20 | .catchError((e) => handler.reject(e)); 21 | break; 22 | case '/fakepath3': 23 | return handler.reject( 24 | DioError( 25 | requestOptions: options, 26 | error: 'test error', 27 | ), 28 | ); 29 | default: 30 | return handler.next(options); //continue 31 | } 32 | })); 33 | Response response; 34 | response = await dio.get('/fakepath1'); 35 | assert(response.data == 'fake data'); 36 | response = await dio.get('/fakepath2'); 37 | assert(response.data['headers'] is Map); 38 | try { 39 | response = await dio.get('/fakepath3'); 40 | } on DioError catch (e) { 41 | print(1); 42 | assert(e.message == 'test error'); 43 | assert(e.response == null); 44 | } 45 | response = await dio.get('/get'); 46 | assert(response.data['headers'] is Map); 47 | try { 48 | await dio.get('xsddddd'); 49 | } on DioError catch (e) { 50 | assert(e.response!.statusCode == 404); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/response_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio_http/dio_http.dart'; 4 | 5 | void main() async { 6 | const URL_NOT_FIND = 'https://wendux.github.io/xxxxx/'; 7 | const URL_NOT_FIND_1 = URL_NOT_FIND + '1'; 8 | const URL_NOT_FIND_2 = URL_NOT_FIND + '2'; 9 | const URL_NOT_FIND_3 = URL_NOT_FIND + '3'; 10 | var dio = Dio(); 11 | dio.options.baseUrl = 'http://httpbin.org/'; 12 | dio.interceptors.add(InterceptorsWrapper( 13 | onResponse: (response, handler) { 14 | response.data = json.decode(response.data['data']); 15 | handler.next(response); 16 | }, 17 | onError: (DioError e, handler) { 18 | if (e.response != null) { 19 | switch (e.response!.requestOptions.path) { 20 | case URL_NOT_FIND: 21 | return handler.next(e); 22 | case URL_NOT_FIND_1: 23 | handler.resolve( 24 | Response( 25 | requestOptions: e.requestOptions, 26 | data: 'fake data', 27 | ), 28 | ); 29 | break; 30 | case URL_NOT_FIND_2: 31 | handler.resolve( 32 | Response( 33 | requestOptions: e.requestOptions, 34 | data: 'fake data', 35 | ), 36 | ); 37 | break; 38 | case URL_NOT_FIND_3: 39 | handler.next( 40 | e..error = 'custom error info [${e.response?.statusCode}]', 41 | ); 42 | break; 43 | } 44 | } else { 45 | handler.next(e); 46 | } 47 | }, 48 | )); 49 | 50 | Response response; 51 | response = await dio.post('/post', data: {'a': 5}); 52 | print(response.headers); 53 | assert(response.data['a'] == 5); 54 | try { 55 | await dio.get(URL_NOT_FIND); 56 | } on DioError catch (e) { 57 | assert(e.response!.statusCode == 404); 58 | } 59 | response = await dio.get(URL_NOT_FIND + '1'); 60 | assert(response.data == 'fake data'); 61 | response = await dio.get(URL_NOT_FIND + '2'); 62 | assert(response.data == 'fake data'); 63 | try { 64 | await dio.get(URL_NOT_FIND + '3'); 65 | } on DioError catch (e) { 66 | assert(e.message == 'custom error info [404]'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | 3 | void getHttp() async { 4 | var dio = Dio(); 5 | dio.interceptors.add(LogInterceptor(responseBody: true)); 6 | dio.options.baseUrl = 'http://httpbin.org'; 7 | dio.options.headers = {'Authorization': 'Bearer '}; 8 | //dio.options.baseUrl = "http://localhost:3000"; 9 | var response = await dio.post( 10 | '/post', 11 | data: null, 12 | options: Options( 13 | contentType: Headers.jsonContentType, 14 | ), 15 | ); 16 | print(response); 17 | } 18 | 19 | void main() async { 20 | getHttp(); 21 | } 22 | -------------------------------------------------------------------------------- /example/transfomer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:dio_http/dio_http.dart'; 3 | 4 | /// If the request data is a `List` type, the [DefaultTransformer] will send data 5 | /// by calling its `toString()` method. However, normally the List object is 6 | /// not expected for request data( mostly need Map ). So we provide a custom 7 | /// [Transformer] that will throw error when request data is a `List` type. 8 | 9 | class MyTransformer extends DefaultTransformer { 10 | @override 11 | Future transformRequest(RequestOptions options) async { 12 | if (options.data is List) { 13 | throw DioError( 14 | error: "Can't send List to sever directly", 15 | requestOptions: options, 16 | ); 17 | } else { 18 | return super.transformRequest(options); 19 | } 20 | } 21 | 22 | /// The [Options] doesn't contain the cookie info. we add the cookie 23 | /// info to [Options.extra], and you can retrieve it in [ResponseInterceptor] 24 | /// and [Response] with `response.request.extra["cookies"]`. 25 | @override 26 | Future transformResponse( 27 | RequestOptions options, 28 | ResponseBody response, 29 | ) async { 30 | options.extra['self'] = 'XX'; 31 | return super.transformResponse(options, response); 32 | } 33 | } 34 | 35 | void main() async { 36 | var dio = Dio(); 37 | // Use custom Transformer 38 | dio.transformer = MyTransformer(); 39 | 40 | var response = await dio.get('https://www.baidu.com'); 41 | print(response.requestOptions.extra['self']); 42 | 43 | try { 44 | await dio.post('https://www.baidu.com', data: ['1', '2']); 45 | } catch (e) { 46 | print(e); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example/upload.txt: -------------------------------------------------------------------------------- 1 | 你好, 世界 2 | & 3 | $ 4 | % U。想 。。 5 | 6 | -------------------------------------------------------------------------------- /example/xx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-tools/dio_http/cce2d35db3351e1b09f24d8eb7d86a8fbb4642ee/example/xx.png -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Steps to Reproduce 10 | 11 | 15 | 16 | ## Logs 17 | 18 | -------------------------------------------------------------------------------- /migration_to_4.x.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Migration to 4.x 4 | 5 | This guide is primarily for users with prior dio 3.x experience who want to learn about the new features and changes in dio 4.x . 6 | 7 | ## New Features 8 | 9 | 1. **Interceptors:** Add `handler` for Interceptor APIs which can specify the subsequent interceptors processing logic more finely(whether to skip them or not)) 10 | 2. Support multiple encoding styles ( `options.listFormat` ) for request `List` parameters. 11 | 3. Make keys of request headers be case-insensitive. 12 | 4. New API: `Future dio.fetch( RequestOptions )`. 13 | 5. New API: `RequestOptions options.compose(BaseOptions baseOpt,...)`. 14 | 15 | ## Breaking Changes 16 | 17 | 1. **Null safety support** (Dart >=2.12). 18 | 19 | 2. **The `Interceptor` APIs signature has changed**. 20 | 21 | 3. Rename `options.merge` to `options.copyWith`. 22 | 23 | 4. Rename `DioErrorType` enums from uppercase to camel style. 24 | 25 | 5. Delete `dio.resolve` and `dio.reject` APIs (use `handler` instead in interceptors). 26 | 27 | 6. Class `BaseOptions` no longer inherits from `Options` class. 28 | 29 | 7. Change `requestStream` type of `HttpClientAdapter.fetch` from `Stream>` to `Stream`. 30 | 31 | 8. Download API: Add real uri and redirect information to headers. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /plugins/cookie_manager/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /plugins/cookie_manager/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 32c946f31bfff6c766229d7b85ef0ae70de2c89a 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /plugins/cookie_manager/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.0] 2 | 3 | * Publish this package, using dio_http as a dependency 4 | 5 | ## [2.0.0] 6 | 7 | * support dio 4.0.0 8 | 9 | ## [2.0.0-beta1] 10 | 11 | * support nullsafety 12 | 13 | ## [1.0.0] - 2019.9.18 14 | 15 | * First stable version 16 | 17 | ## [0.0.1] - 2019.9.17 18 | 19 | * A cookie manager for Dio. 20 | -------------------------------------------------------------------------------- /plugins/cookie_manager/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 wendux 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /plugins/cookie_manager/README.md: -------------------------------------------------------------------------------- 1 | # dio_http_cookie_manager [![Pub](https://img.shields.io/pub/v/dio_http_cookie_manager.svg?style=flat-square)](https://pub.dartlang.org/packages/dio_http_cookie_manager) 2 | 3 | A cookie manager for [dio_http](https://github.com/dart-tools/dio_http). 4 | 5 | ## Getting Started 6 | 7 | ### Install 8 | 9 | ```yaml 10 | dependencies: 11 | dio_http_cookie_manager: ^3.0.0 #latest version 12 | ``` 13 | 14 | ### Usage 15 | 16 | ```dart 17 | import 'package:dio_http/dio_http.dart'; 18 | import 'package:dio_http_cookie_manager/dio_http_cookie_manager.dart'; 19 | import 'package:cookie_jar/cookie_jar.dart'; 20 | 21 | main() async { 22 | var dio = Dio(); 23 | var cookieJar=CookieJar(); 24 | dio.interceptors.add(CookieManager(cookieJar)); 25 | 26 | // first request, and save cookies (CookieManager do it). 27 | await dio.get("https://baidu.com/"); 28 | 29 | // Print cookies 30 | // print(await cookieJar.loadForRequest(Uri.parse("https://baidu.com/"))); 31 | 32 | // second request with the cookies 33 | await dio.get("https://baidu.com/"); 34 | ... 35 | } 36 | ``` 37 | 38 | ## Cookie Manager 39 | 40 | CookieManager Interceptor can help us manage the request/response cookies automatically. CookieManager depends on `cookieJar` package : 41 | 42 | > The dio_http_cookie_manager manage API is based on the withdrawn [cookie_jar](https://github.com/flutterchina/cookie_jar). 43 | 44 | You can create a `CookieJar` or `PersistCookieJar` to manage cookies automatically, and dio use the `CookieJar` by default, which saves the cookies **in RAM**. If you want to persists cookies, you can use the `PersistCookieJar` class, for example: 45 | 46 | ```dart 47 | dio.interceptors.add(CookieManager(PersistCookieJar())) 48 | ``` 49 | 50 | `PersistCookieJar` persists the cookies in files, so if the application exit, the cookies always exist unless call `delete` explicitly. 51 | 52 | > Note: In flutter, the path passed to `PersistCookieJar` must be valid(exists in phones and with write access). you can use [path_provider](https://pub.dartlang.org/packages/path_provider) package to get right path. 53 | 54 | In flutter: 55 | 56 | ```dart 57 | Directory appDocDir = await getApplicationDocumentsDirectory(); 58 | String appDocPath = appDocDir.path; 59 | 60 | var cj = PersistCookieJar(ignoreExpires: true, storage: FileStorage(appDocPath +"/.cookies/" )); 61 | dio.interceptors.add(CookieManager(cj)); 62 | ... 63 | ``` 64 | -------------------------------------------------------------------------------- /plugins/cookie_manager/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:dio_http_cookie_manager/dio_http_cookie_manager.dart'; 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | 5 | main() async { 6 | var dio = Dio(); 7 | var cookieJar = CookieJar(); 8 | dio.interceptors.add(CookieManager(cookieJar)); 9 | await dio.get("https://baidu.com/"); 10 | // Print cookies 11 | print(cookieJar.loadForRequest(Uri.parse("https://baidu.com/"))); 12 | // second request with the cookie 13 | await dio.get("https://baidu.com/"); 14 | } 15 | -------------------------------------------------------------------------------- /plugins/cookie_manager/lib/dio_http_cookie_manager.dart: -------------------------------------------------------------------------------- 1 | library dio_http_cookie_manager; 2 | 3 | export 'src/cookie_mgr.dart'; 4 | -------------------------------------------------------------------------------- /plugins/cookie_manager/lib/src/cookie_mgr.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:dio_http/dio_http.dart'; 5 | 6 | /// Don't use this class in Browser environment 7 | class CookieManager extends Interceptor { 8 | /// Cookie manager for http requests。Learn more details about 9 | /// CookieJar please refer to [cookie_jar](https://github.com/flutterchina/cookie_jar) 10 | final CookieJar cookieJar; 11 | 12 | CookieManager(this.cookieJar); 13 | 14 | @override 15 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 16 | cookieJar.loadForRequest(options.uri).then((cookies) { 17 | var cookie = getCookies(cookies); 18 | if (cookie.isNotEmpty) { 19 | options.headers[HttpHeaders.cookieHeader] = cookie; 20 | } 21 | handler.next(options); 22 | }).catchError((e, stackTrace) { 23 | var err = DioError(requestOptions: options, error: e); 24 | err.stackTrace = stackTrace; 25 | handler.reject(err, true); 26 | }); 27 | } 28 | 29 | @override 30 | void onResponse(Response response, ResponseInterceptorHandler handler) { 31 | _saveCookies(response) 32 | .then((_) => handler.next(response)) 33 | .catchError((e, stackTrace) { 34 | var err = DioError(requestOptions: response.requestOptions, error: e); 35 | err.stackTrace = stackTrace; 36 | handler.reject(err, true); 37 | }); 38 | } 39 | 40 | @override 41 | void onError(DioError err, ErrorInterceptorHandler handler) { 42 | if (err.response != null) { 43 | _saveCookies(err.response!) 44 | .then((_) => handler.next(err)) 45 | .catchError((e, stackTrace) { 46 | var _err = DioError( 47 | requestOptions: err.response!.requestOptions, 48 | error: e, 49 | ); 50 | _err.stackTrace = stackTrace; 51 | handler.next(_err); 52 | }); 53 | } else { 54 | handler.next(err); 55 | } 56 | } 57 | 58 | Future _saveCookies(Response response) async { 59 | var cookies = response.headers[HttpHeaders.setCookieHeader]; 60 | 61 | if (cookies != null) { 62 | await cookieJar.saveFromResponse( 63 | response.requestOptions.uri, 64 | cookies.map((str) => Cookie.fromSetCookieValue(str)).toList(), 65 | ); 66 | } 67 | } 68 | 69 | static String getCookies(List cookies) { 70 | return cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; '); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /plugins/cookie_manager/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dio_http_cookie_manager 2 | description: A cookie manager for Dio, which supports persistent cookies in RAM and file. 3 | version: 3.0.0 4 | homepage: https://github.com/dart-tools/dio_http/tree/master/plugins/cookie_manager 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | cookie_jar: ^3.0.0 11 | dio_http: ^5.0.4 12 | # dio: 13 | # path: ../../dio 14 | 15 | dev_dependencies: 16 | test: ^1.17.11 17 | pedantic: ^1.10.0 18 | 19 | 20 | -------------------------------------------------------------------------------- /plugins/cookie_manager/test/basic_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:test/test.dart'; 3 | import 'package:cookie_jar/cookie_jar.dart'; 4 | import 'package:dio_http_cookie_manager/dio_http_cookie_manager.dart'; 5 | 6 | void main() { 7 | test('cookie-jar', () async { 8 | var dio = Dio(); 9 | var cookieJar = CookieJar(); 10 | dio.interceptors 11 | ..add(CookieManager(cookieJar)) 12 | ..add(LogInterceptor()); 13 | await dio.get('https://baidu.com/'); 14 | // Print cookies 15 | var cookies = 16 | await cookieJar.loadForRequest(Uri.parse('https://baidu.com/')); 17 | print(cookies); 18 | // second request with the cookie 19 | await dio.get('https://baidu.com/'); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /plugins/http2_adapter/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /plugins/http2_adapter/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 32c946f31bfff6c766229d7b85ef0ae70de2c89a 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /plugins/http2_adapter/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [2.0.0] 3 | 4 | * support dio 4.0.0 5 | 6 | ## [2.0.0-beta2] 7 | 8 | - support null-safety 9 | - support dio 4.x 10 | 11 | 12 | ## [1.0.1] - 2020.8.7 13 | 14 | - merge #760 15 | 16 | ## [1.0.0] - 2019.9.18 17 | 18 | * Support redirect 19 | 20 | ## [0.0.2] - 2019.9.17 21 | 22 | * A Dio HttpAdapter which support Http/2.0. 23 | -------------------------------------------------------------------------------- /plugins/http2_adapter/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 wendux 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugins/http2_adapter/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Dependencies 4 | 5 | ``` 6 | dependencies: 7 | dio: ^4.0.0 8 | dio_http2_adapter: ^2.0.0 9 | ``` 10 | 11 | ### Example 12 | 13 | ```dart 14 | import 'package:dio_http/dio_http.dart'; 15 | import 'package:dio_http2_adapter/dio_http2_adapter.dart'; 16 | 17 | void main() async { 18 | var dio = Dio() 19 | ..options.baseUrl = 'https://google.com' 20 | ..interceptors.add(LogInterceptor()) 21 | ..httpClientAdapter = Http2Adapter( 22 | ConnectionManager( 23 | idleTimeout: 10000, 24 | // Ignore bad certificate 25 | onClientCreate: (_, config) => config.onBadCertificate = (_) => true, 26 | ), 27 | ); 28 | 29 | Response response; 30 | 31 | response = await dio.get('/?xx=6'); 32 | print(response.data?.length); 33 | print(response.redirects.length); 34 | print(response.data); 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /plugins/http2_adapter/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:dio_http2_adapter/dio_http2_adapter.dart'; 3 | 4 | void main() async { 5 | var dio = Dio() 6 | ..options.baseUrl = 'https://google.com' 7 | ..interceptors.add(LogInterceptor()) 8 | ..httpClientAdapter = Http2Adapter( 9 | ConnectionManager( 10 | idleTimeout: 10000, 11 | // Ignore bad certificate 12 | onClientCreate: (_, config) => config.onBadCertificate = (_) => true, 13 | ), 14 | ); 15 | 16 | Response response; 17 | 18 | response = await dio.get('/?xx=6'); 19 | print(response.data?.length); 20 | print(response.redirects.length); 21 | print(response.data); 22 | } 23 | -------------------------------------------------------------------------------- /plugins/http2_adapter/lib/dio_http2_adapter.dart: -------------------------------------------------------------------------------- 1 | library dio_http2_adapter; 2 | 3 | export 'src/http2_adapter.dart'; 4 | -------------------------------------------------------------------------------- /plugins/http2_adapter/lib/src/client_setting.dart: -------------------------------------------------------------------------------- 1 | part of 'http2_adapter.dart'; 2 | 3 | class ClientSetting { 4 | /// The certificate provided by the server is checked 5 | /// using the trusted certificates set in the SecurityContext object. 6 | /// The default SecurityContext object contains a built-in set of trusted 7 | /// root certificates for well-known certificate authorities. 8 | SecurityContext? context; 9 | 10 | /// [onBadCertificate] is an optional handler for unverifiable certificates. 11 | /// The handler receives the [X509Certificate], and can inspect it and 12 | /// decide (or let the user decide) whether to accept 13 | /// the connection or not. The handler should return true 14 | /// to continue the [SecureSocket] connection. 15 | bool Function(X509Certificate certificate)? onBadCertificate; 16 | } 17 | -------------------------------------------------------------------------------- /plugins/http2_adapter/lib/src/connection_manager.dart: -------------------------------------------------------------------------------- 1 | part of 'http2_adapter.dart'; 2 | 3 | /// ConnectionManager is used to manager the connections that should be reusable. 4 | /// The main responsibility of ConnectionManager is to implement a connection reuse 5 | /// strategy for http2. 6 | abstract class ConnectionManager { 7 | factory ConnectionManager({ 8 | int idleTimeout = 15000, 9 | void Function(Uri uri, ClientSetting)? onClientCreate, 10 | }) => 11 | _ConnectionManager( 12 | idleTimeout: idleTimeout, 13 | onClientCreate: onClientCreate, 14 | ); 15 | 16 | /// Get the connection(may reuse) for each request. 17 | Future getConnection(RequestOptions options); 18 | 19 | void removeConnection(ClientTransportConnection transport); 20 | 21 | void close({bool force = false}); 22 | } 23 | -------------------------------------------------------------------------------- /plugins/http2_adapter/lib/src/connection_manager_imp.dart: -------------------------------------------------------------------------------- 1 | part of 'http2_adapter.dart'; 2 | 3 | /// Default implementation of ConnectionManager 4 | class _ConnectionManager implements ConnectionManager { 5 | /// Callback when socket created. 6 | /// 7 | /// We can set trusted certificates and handler 8 | /// for unverifiable certificates. 9 | final void Function(Uri uri, ClientSetting)? onClientCreate; 10 | 11 | /// Sets the idle timeout(milliseconds) of non-active persistent 12 | /// connections. For the sake of socket reuse feature with http/2, 13 | /// the value should not be less than 1000 (1s). 14 | final int _idleTimeout; 15 | 16 | /// Saving the reusable connections 17 | final _transportsMap = {}; 18 | 19 | /// Saving the connecting futures 20 | final _connectFutures = >{}; 21 | 22 | bool _closed = false; 23 | bool _forceClosed = false; 24 | 25 | _ConnectionManager({int? idleTimeout, this.onClientCreate}) 26 | : _idleTimeout = idleTimeout ?? 1000; 27 | 28 | @override 29 | Future getConnection( 30 | RequestOptions options) async { 31 | if (_closed) { 32 | throw Exception( 33 | "Can't establish connection after [ConnectionManager] closed!"); 34 | } 35 | var uri = options.uri; 36 | var domain = '${uri.host}:${uri.port}'; 37 | var transportState = _transportsMap[domain]; 38 | if (transportState == null) { 39 | var _initFuture = _connectFutures[domain]; 40 | if (_initFuture == null) { 41 | _connectFutures[domain] = _initFuture = _connect(options); 42 | } 43 | transportState = await _initFuture; 44 | if (_forceClosed) { 45 | transportState.dispose(); 46 | } else { 47 | _transportsMap[domain] = transportState; 48 | var _ = _connectFutures.remove(domain); 49 | } 50 | } else { 51 | // Check whether the connection is terminated, if it is, reconnecting. 52 | if (!transportState.transport.isOpen) { 53 | transportState.dispose(); 54 | _transportsMap[domain] = transportState = await _connect(options); 55 | } 56 | } 57 | return transportState.activeTransport; 58 | } 59 | 60 | Future<_ClientTransportConnectionState> _connect( 61 | RequestOptions options) async { 62 | var uri = options.uri; 63 | var domain = '${uri.host}:${uri.port}'; 64 | var clientConfig = ClientSetting(); 65 | if (onClientCreate != null) { 66 | onClientCreate!(uri, clientConfig); 67 | } 68 | var socket; 69 | try { 70 | // Create socket 71 | socket = await SecureSocket.connect( 72 | uri.host, 73 | uri.port, 74 | timeout: options.connectTimeout > 0 75 | ? Duration(milliseconds: options.connectTimeout) 76 | : null, 77 | context: clientConfig.context, 78 | onBadCertificate: clientConfig.onBadCertificate, 79 | supportedProtocols: ['h2'], 80 | ); 81 | } on SocketException catch (e) { 82 | if (e.osError == null) { 83 | if (e.message.contains('timed out')) { 84 | throw DioError( 85 | requestOptions: options, 86 | error: 'Connecting timed out [${options.connectTimeout}ms]', 87 | type: DioErrorType.connectTimeout, 88 | ); 89 | } 90 | } 91 | rethrow; 92 | } 93 | // Config a ClientTransportConnection and save it 94 | var transport = ClientTransportConnection.viaSocket(socket); 95 | var _transportState = _ClientTransportConnectionState(transport); 96 | transport.onActiveStateChanged = (bool isActive) { 97 | _transportState.isActive = isActive; 98 | if (!isActive) { 99 | _transportState.latestIdleTimeStamp = 100 | DateTime.now().millisecondsSinceEpoch; 101 | } 102 | }; 103 | // 104 | _transportState.delayClose( 105 | _closed ? 50 : _idleTimeout, 106 | () { 107 | _transportsMap.remove(domain); 108 | _transportState.transport.finish(); 109 | }, 110 | ); 111 | return _transportState; 112 | } 113 | 114 | @override 115 | void removeConnection(ClientTransportConnection transport) { 116 | _ClientTransportConnectionState? _transportState; 117 | _transportsMap.removeWhere((_, state) { 118 | if (state.transport == transport) { 119 | _transportState = state; 120 | return true; 121 | } 122 | return false; 123 | }); 124 | _transportState?.dispose(); 125 | } 126 | 127 | @override 128 | void close({bool force = false}) { 129 | _closed = true; 130 | _forceClosed = force; 131 | if (force) { 132 | _transportsMap.forEach((key, value) => value.dispose()); 133 | } 134 | } 135 | } 136 | 137 | class _ClientTransportConnectionState { 138 | _ClientTransportConnectionState(this.transport); 139 | 140 | ClientTransportConnection transport; 141 | 142 | ClientTransportConnection get activeTransport { 143 | isActive = true; 144 | latestIdleTimeStamp = DateTime.now().millisecondsSinceEpoch; 145 | return transport; 146 | } 147 | 148 | bool isActive = true; 149 | late int latestIdleTimeStamp; 150 | Timer? _timer; 151 | 152 | void delayClose(int idleTimeout, void Function() callback) { 153 | idleTimeout = idleTimeout < 100 ? 100 : idleTimeout; 154 | _startTimer(callback, idleTimeout, idleTimeout); 155 | } 156 | 157 | void dispose() { 158 | _timer?.cancel(); 159 | transport.finish(); 160 | } 161 | 162 | void _startTimer(void Function() callback, int duration, int idleTimeout) { 163 | _timer = Timer(Duration(milliseconds: duration), () { 164 | if (!isActive) { 165 | var interval = 166 | DateTime.now().millisecondsSinceEpoch - latestIdleTimeStamp; 167 | if (interval >= duration) { 168 | return callback(); 169 | } 170 | return _startTimer(callback, duration - interval, idleTimeout); 171 | } 172 | // if active 173 | _startTimer(callback, idleTimeout, idleTimeout); 174 | }); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /plugins/http2_adapter/lib/src/http2_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'dart:typed_data'; 5 | import 'package:http2/http2.dart'; 6 | import 'package:dio_http/dio_http.dart'; 7 | 8 | part 'connection_manager.dart'; 9 | 10 | part 'client_setting.dart'; 11 | 12 | part 'connection_manager_imp.dart'; 13 | 14 | /// A Dio HttpAdapter which implements Http/2.0. 15 | class Http2Adapter extends HttpClientAdapter { 16 | final ConnectionManager _connectionMgr; 17 | 18 | Http2Adapter(ConnectionManager? connectionManager) 19 | : _connectionMgr = connectionManager ?? ConnectionManager(); 20 | 21 | @override 22 | Future fetch( 23 | RequestOptions options, 24 | Stream? requestStream, 25 | Future? cancelFuture, 26 | ) async { 27 | var redirects = []; 28 | return _fetch( 29 | options, 30 | requestStream, 31 | cancelFuture, 32 | redirects, 33 | ); 34 | } 35 | 36 | Future _fetch( 37 | RequestOptions options, 38 | Stream? requestStream, 39 | Future? cancelFuture, 40 | List redirects, 41 | ) async { 42 | final transport = await _connectionMgr.getConnection(options); 43 | final uri = options.uri; 44 | var path = uri.path; 45 | const excludeMethods = ['PUT', 'POST', 'PATCH']; 46 | 47 | if (path.isEmpty || !path.startsWith('/')) path = '/' + path; 48 | if (uri.query.trim().isNotEmpty) path += ('?' + uri.query); 49 | var headers = [ 50 | Header.ascii(':method', options.method), 51 | Header.ascii(':path', path), 52 | Header.ascii(':scheme', uri.scheme), 53 | Header.ascii(':authority', uri.host), 54 | ]; 55 | 56 | // Add custom headers 57 | headers.addAll( 58 | options.headers.keys 59 | .map((key) => Header.ascii(key, options.headers[key] ?? '')) 60 | .toList(), 61 | ); 62 | 63 | // Creates a new outgoing stream. 64 | final stream = transport.makeRequest(headers); 65 | 66 | // ignore: unawaited_futures 67 | cancelFuture?.whenComplete(() { 68 | Future(() { 69 | stream.terminate(); 70 | }); 71 | }); 72 | 73 | var list; 74 | var hasRequestData = requestStream != null; 75 | if (!excludeMethods.contains(options.method) && hasRequestData) { 76 | list = await requestStream!.toList(); 77 | requestStream = Stream.fromIterable(list); 78 | } 79 | 80 | if (hasRequestData) { 81 | await requestStream!.listen((data) { 82 | stream.outgoingMessages.add(DataStreamMessage(data)); 83 | }).asFuture(); 84 | } 85 | 86 | await stream.outgoingMessages.close(); 87 | 88 | final sc = StreamController(); 89 | final responseHeaders = Headers(); 90 | var completer = Completer(); 91 | var statusCode; 92 | var needRedirect = false; 93 | late StreamSubscription subscription; 94 | var needResponse = false; 95 | subscription = stream.incomingMessages.listen( 96 | (message) async { 97 | if (message is HeadersStreamMessage) { 98 | for (var header in message.headers) { 99 | var name = utf8.decode(header.name); 100 | var value = utf8.decode(header.value); 101 | responseHeaders.add(name, value); 102 | } 103 | 104 | var status = responseHeaders.value(':status'); 105 | if (status != null) { 106 | statusCode = int.parse(status); 107 | responseHeaders.removeAll(':status'); 108 | 109 | needRedirect = list != null && _needRedirect(options, statusCode); 110 | 111 | needResponse = 112 | !needRedirect && options.validateStatus(statusCode) || 113 | options.receiveDataWhenStatusError; 114 | 115 | completer.complete(); 116 | } 117 | } else if (message is DataStreamMessage) { 118 | if (needResponse) { 119 | sc.add(Uint8List.fromList(message.bytes)); 120 | } else { 121 | // ignore: unawaited_futures 122 | subscription.cancel().whenComplete(() => sc.close()); 123 | } 124 | } 125 | }, 126 | onDone: () => sc.close(), 127 | onError: (e) { 128 | // If connection is being forcefully terminated, remove the connection 129 | if (e is TransportConnectionException) { 130 | _connectionMgr.removeConnection(transport); 131 | } 132 | if (!completer.isCompleted) { 133 | completer.completeError(e, StackTrace.current); 134 | } else { 135 | sc.addError(e); 136 | } 137 | }, 138 | cancelOnError: true, 139 | ); 140 | 141 | await completer.future; 142 | 143 | // Handle redirection 144 | if (needRedirect) { 145 | var url = responseHeaders.value('location'); 146 | redirects.add( 147 | RedirectRecord(statusCode, options.method, Uri.parse(url ?? ''))); 148 | return _fetch( 149 | options.copyWith(path: url, maxRedirects: --options.maxRedirects), 150 | Stream.fromIterable(list), 151 | cancelFuture, 152 | redirects, 153 | ); 154 | } 155 | return ResponseBody( 156 | sc.stream, 157 | statusCode, 158 | headers: responseHeaders.map, 159 | redirects: redirects, 160 | isRedirect: redirects.isNotEmpty, 161 | ); 162 | } 163 | 164 | bool _needRedirect(RequestOptions options, int status) { 165 | const statusCodes = [301, 302, 303, 307, 308]; 166 | return options.followRedirects && 167 | options.maxRedirects > 0 && 168 | statusCodes.contains(status); 169 | } 170 | 171 | @override 172 | void close({bool force = false}) { 173 | _connectionMgr.close(force: force); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /plugins/http2_adapter/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dio_http2_adapter 2 | description: A Dio HttpClientAdapter which support Http/2.0(Support connection reuse, header compression, server pushing is not supported yet.) 3 | version: 2.0.0 4 | homepage: https://github.com/dart-tools/dio_http/tree/master/plugins/http2_adapter 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | http2: ^2.0.0 11 | dio_http: ^5.0.4 12 | # dio: 13 | # path: ../../dio 14 | 15 | dev_dependencies: 16 | test: ^1.17.11 17 | pedantic: ^1.10.0 18 | 19 | -------------------------------------------------------------------------------- /plugins/http2_adapter/test/http2_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http/dio_http.dart'; 2 | import 'package:test/test.dart'; 3 | import 'package:dio_http2_adapter/dio_http2_adapter.dart'; 4 | 5 | void main() { 6 | test('adds one to input values', () async { 7 | var dio = Dio() 8 | ..options.baseUrl = 'https://www.ustc.edu.cn/' 9 | ..interceptors.add(LogInterceptor()) 10 | ..httpClientAdapter = Http2Adapter( 11 | ConnectionManager( 12 | idleTimeout: 10, 13 | onClientCreate: (_, config) => config.onBadCertificate = (_) => true, 14 | ), 15 | ); 16 | 17 | Response response; 18 | response = await dio.get('?xx=6'); 19 | assert(response.statusCode == 200); 20 | response = await dio.get( 21 | 'nkjnjknjn.html', 22 | options: Options(validateStatus: (status) => true), 23 | ); 24 | assert(response.statusCode == 404); 25 | }); 26 | 27 | test('request with payload', () async { 28 | final dio = Dio() 29 | ..options.baseUrl = 'https://postman-echo.com/' 30 | ..httpClientAdapter = Http2Adapter(ConnectionManager( 31 | idleTimeout: 10, 32 | )); 33 | 34 | final res = await dio.post('post', data: 'TEST'); 35 | assert(res.data['data'] == 'TEST'); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dio_http_example 2 | description: A powerful Http client for Dart, which supports Interceptors, FormData, Request Cancellation, File Downloading, Timeout etc. 3 | version: 0.0.1 4 | homepage: https://github.com/dart-tools/dio_http 5 | publish_to: none 6 | environment: 7 | sdk: ">=2.12.0-0 <3.0.0" 8 | 9 | dependencies: 10 | dio_http: ^5.0.4 11 | dio_http2_adapter: 12 | path: plugins/http2_adapter 13 | dio_http_cookie_manager: 14 | path: plugins/cookie_manager 15 | test: ^1.17.11 16 | pedantic: ^1.10.0 17 | 18 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | cd dio 2 | flutter test --coverage . 3 | genhtml -o coverage coverage/lcov.info 4 | # Open in the default browser (mac): 5 | open coverage/index.html 6 | 7 | -------------------------------------------------------------------------------- /test/readme.md: -------------------------------------------------------------------------------- 1 | ## run tests 2 | 3 | ```dart 4 | ./test.sh 5 | ``` 6 | 7 | Test cases are [here](https://github.com/dart-tools/dio_http/tree/master/dio/test) 8 | --------------------------------------------------------------------------------