WIP: Proposal: remove deprecated packages #240
| @ -177,13 +177,13 @@ Then implements your data sources: | ||||
| 
 | ||||
| ```dart | ||||
| class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { | ||||
|   final MiddlewareClient _client; | ||||
|   final Client _client; | ||||
| 
 | ||||
|   PhotoApiDataSourceImpl(this._client); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Photo> getPhoto(int id) async { | ||||
|     final response = await _client.get(Uri.parse('/photos/$id')); | ||||
|     final response = await _client.get(Uri.parse('$kDefaultApiUrl/photos/$id')); | ||||
|     final photo = | ||||
|         PhotoModel.fromJson(jsonDecode(response.body) as Map<String, Object?>); | ||||
|     return photo; | ||||
| @ -197,7 +197,7 @@ class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { | ||||
|         (startQuery.isNotEmpty || limitQuery.isNotEmpty) ? '?' : ''; | ||||
|     final delimiter2 = | ||||
|         (startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : ''; | ||||
|     final url = '/photos$delimiter1$startQuery$delimiter2$limitQuery'; | ||||
|     final url = '$kDefaultApiUrl/photos$delimiter1$startQuery$delimiter2$limitQuery'; | ||||
|     final response = await _client.get(Uri.parse(url)); | ||||
|     final photos = | ||||
|         ListPhotoModel.fromJson({'photos': jsonDecode(response.body)}); | ||||
| @ -206,9 +206,7 @@ class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| > 1: Note that here we use `MiddlewareClient` from our http package. | ||||
| 
 | ||||
| > 2: You can create multiple implementations (one real and one mock for example). | ||||
| > 1: You can create multiple implementations (one real and one mock for example). | ||||
| 
 | ||||
| And implement the repositories: | ||||
| 
 | ||||
|  | ||||
| @ -1,20 +1,17 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Copyright (C) 2024 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| export 'middleware_context.dart'; | ||||
| export 'middleware_request.dart'; | ||||
| export 'middleware_response.dart'; | ||||
| export 'unfreezed_request.dart'; | ||||
| const String kDefaultApiUrl = 'https://jsonplaceholder.typicode.com/'; | ||||
| @ -26,7 +26,6 @@ import 'package:architecture_example/domain/data_sources/local/favorite_local_da | ||||
| import 'package:architecture_example/domain/data_sources/remote/album_remote_data_source.dart'; | ||||
| import 'package:architecture_example/domain/data_sources/remote/photo_remote_data_source.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| 
 | ||||
| final getIt = GetIt.I; | ||||
| 
 | ||||
| @ -50,22 +49,11 @@ abstract class GetItInitializer { | ||||
|       ..registerLazySingleton<FavoriteLocalDataSource>( | ||||
|         FavoriteHiveDataSource.new, | ||||
|       ) | ||||
|       ..registerLazySingleton<MiddlewareClient>(() { | ||||
|         final Pipeline pipeline = Pipeline() | ||||
|           ..addMiddleware( | ||||
|             const UriPrefixMiddleware( | ||||
|               protocol: Protocols.https, | ||||
|               authority: 'jsonplaceholder.typicode.com', | ||||
|             ), | ||||
|           ) | ||||
|           ..addMiddleware(const BodyToJsonMiddleware()); | ||||
|         return MiddlewareClient(pipeline: pipeline); | ||||
|       }) | ||||
|       ..registerLazySingleton<PhotoRemoteDataSource>( | ||||
|         () => PhotoApiDataSourceImpl(getIt()), | ||||
|         PhotoApiDataSourceImpl.new, | ||||
|       ) | ||||
|       ..registerLazySingleton<AlbumRemoteDataSource>( | ||||
|         () => AlbumApiDataSourceImpl(getIt()), | ||||
|         AlbumApiDataSourceImpl.new, | ||||
|       ); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -16,20 +16,25 @@ | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:architecture_example/core/constants/constants.dart'; | ||||
| import 'package:architecture_example/data/models/album_model.dart'; | ||||
| import 'package:architecture_example/data/models/list_album_model.dart'; | ||||
| import 'package:architecture_example/domain/data_sources/remote/album_remote_data_source.dart'; | ||||
| import 'package:architecture_example/domain/entities/album.dart'; | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | ||||
| 
 | ||||
| class AlbumApiDataSourceImpl extends AlbumRemoteDataSource { | ||||
|   AlbumApiDataSourceImpl(this._client); | ||||
|   final MiddlewareClient _client; | ||||
|   AlbumApiDataSourceImpl({ | ||||
|     Client? client, | ||||
|   }) : _client = client ?? Client(); | ||||
| 
 | ||||
|   final Client _client; | ||||
| 
 | ||||
|   @override | ||||
|   Future<Album> getAlbum(int id) async { | ||||
|     final response = await _client.get(Uri.parse('/albums/$id')); | ||||
|     final response = await _client | ||||
|         .get(Uri.parse('$kDefaultApiUrl/albums/$id')); | ||||
|     final album = | ||||
|         AlbumModel.fromJson(jsonDecode(response.body) as Map<String, Object?>); | ||||
|     return album; | ||||
| @ -43,7 +48,7 @@ class AlbumApiDataSourceImpl extends AlbumRemoteDataSource { | ||||
|         (startQuery.isNotEmpty || limitQuery.isNotEmpty) ? '?' : ''; | ||||
|     final delimiter2 = | ||||
|         (startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : ''; | ||||
|     final url = '/albums$delimiter1$startQuery$delimiter2$limitQuery'; | ||||
|     final url = '$kDefaultApiUrl/albums$delimiter1$startQuery$delimiter2$limitQuery'; | ||||
|     final response = await _client.get(Uri.parse(url)); | ||||
|     final albums = | ||||
|         ListAlbumModel.fromJson({'albums': jsonDecode(response.body)}); | ||||
|  | ||||
| @ -16,20 +16,24 @@ | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:architecture_example/core/constants/constants.dart'; | ||||
| import 'package:architecture_example/data/models/list_photo_model.dart'; | ||||
| import 'package:architecture_example/data/models/photo_model.dart'; | ||||
| import 'package:architecture_example/domain/data_sources/remote/photo_remote_data_source.dart'; | ||||
| import 'package:architecture_example/domain/entities/photo.dart'; | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | ||||
| 
 | ||||
| class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { | ||||
|   PhotoApiDataSourceImpl(this._client); | ||||
|   final MiddlewareClient _client; | ||||
|   PhotoApiDataSourceImpl({ | ||||
|     Client? client, | ||||
|   }) : _client = client ?? Client(); | ||||
| 
 | ||||
|   final Client _client; | ||||
| 
 | ||||
|   @override | ||||
|   Future<Photo> getPhoto(int id) async { | ||||
|     final response = await _client.get(Uri.parse('/photos/$id')); | ||||
|     final response = await _client.get(Uri.parse('$kDefaultApiUrl/photos/$id')); | ||||
|     final photo = | ||||
|         PhotoModel.fromJson(jsonDecode(response.body) as Map<String, Object?>); | ||||
|     return photo; | ||||
| @ -43,7 +47,8 @@ class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { | ||||
|         (startQuery.isNotEmpty || limitQuery.isNotEmpty) ? '?' : ''; | ||||
|     final delimiter2 = | ||||
|         (startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : ''; | ||||
|     final url = '/photos$delimiter1$startQuery$delimiter2$limitQuery'; | ||||
|     final url = | ||||
|         '$kDefaultApiUrl/photos$delimiter1$startQuery$delimiter2$limitQuery'; | ||||
|     final response = await _client.get(Uri.parse(url)); | ||||
|     final photos = | ||||
|         ListPhotoModel.fromJson({'photos': jsonDecode(response.body)}); | ||||
| @ -60,7 +65,8 @@ class PhotoApiDataSourceImpl extends PhotoRemoteDataSource { | ||||
|     final limitQuery = limit.isNotNull ? '_limit=$limit' : ''; | ||||
|     final delimiter = | ||||
|         (startQuery.isNotEmpty && limitQuery.isNotEmpty) ? '&' : ''; | ||||
|     final url = '/photos?albumId=$albumId&$startQuery$delimiter$limitQuery'; | ||||
|     final url = | ||||
|         '$kDefaultApiUrl/photos?albumId=$albumId&$startQuery$delimiter$limitQuery'; | ||||
|     final response = await _client.get(Uri.parse(url)); | ||||
|     final photos = | ||||
|         ListPhotoModel.fromJson({'photos': jsonDecode(response.body)}); | ||||
|  | ||||
| @ -45,12 +45,10 @@ dependencies: | ||||
|   wyatt_bloc_helper: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|     version: ^2.0.2 | ||||
|   wyatt_http_client: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|     version: ^2.0.1 | ||||
|   wyatt_type_utils: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub | ||||
|     version: ^0.0.5 | ||||
|   http: ^1.2.1 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   build_runner: ^2.3.2 | ||||
|  | ||||
							
								
								
									
										1
									
								
								packages/wyatt_http_client/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								packages/wyatt_http_client/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | ||||
| ../../.gitignore | ||||
| @ -1 +0,0 @@ | ||||
| ../../.pubignore | ||||
| @ -1,24 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (C) 2022 WYATT GROUP | ||||
|  * Please see the AUTHORS file for details. | ||||
|  *  | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * any later version. | ||||
|  *  | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
|  * GNU General Public License for more details. | ||||
|  *  | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
| 
 | ||||
| { | ||||
|     "recommendations": [ | ||||
|         "psioniq.psi-header", | ||||
|         "blaugold.melos-code" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										71
									
								
								packages/wyatt_http_client/.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										71
									
								
								packages/wyatt_http_client/.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @ -1,71 +0,0 @@ | ||||
| { | ||||
|     "psi-header.changes-tracking": { | ||||
|         "isActive": true | ||||
|       }, | ||||
|       "psi-header.config": { | ||||
|         "blankLinesAfter": 1, | ||||
|         "forceToTop": true | ||||
|       }, | ||||
|       "psi-header.lang-config": [ | ||||
|         { | ||||
|           "beforeHeader": [ | ||||
|             "# -*- coding:utf-8 -*-", | ||||
|             "#!/usr/bin/env python3" | ||||
|           ], | ||||
|           "begin": "###", | ||||
|           "end": "###", | ||||
|           "language": "python", | ||||
|           "prefix": "# " | ||||
|         }, | ||||
|         { | ||||
|           "beforeHeader": [ | ||||
|             "#!/usr/bin/env sh", | ||||
|             "" | ||||
|           ], | ||||
|           "language": "shellscript", | ||||
|           "begin": "", | ||||
|           "end": "", | ||||
|           "prefix": "# " | ||||
|         }, | ||||
|         { | ||||
|           "begin": "", | ||||
|           "end": "", | ||||
|           "language": "dart", | ||||
|           "prefix": "// " | ||||
|         }, | ||||
|         { | ||||
|           "begin": "", | ||||
|           "end": "", | ||||
|           "language": "yaml", | ||||
|           "prefix": "# " | ||||
|         }, | ||||
|         { | ||||
|           "begin": "<!--", | ||||
|           "end": "-->", | ||||
|           "language": "markdown", | ||||
|         }, | ||||
|       ], | ||||
|       "psi-header.templates": [ | ||||
|         { | ||||
|           "language": "*", | ||||
|           "template": [ | ||||
|             "Copyright (C) <<year>> WYATT GROUP", | ||||
|             "Please see the AUTHORS file for details.", | ||||
|             "", | ||||
|             "This program is free software: you can redistribute it and/or modify", | ||||
|             "it under the terms of the GNU General Public License as published by", | ||||
|             "the Free Software Foundation, either version 3 of the License, or", | ||||
|             "any later version.", | ||||
|             "", | ||||
|             "This program is distributed in the hope that it will be useful,", | ||||
|             "but WITHOUT ANY WARRANTY; without even the implied warranty of", | ||||
|             "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", | ||||
|             "GNU General Public License for more details.", | ||||
|             "", | ||||
|             "You should have received a copy of the GNU General Public License", | ||||
|             "along with this program. If not, see <https://www.gnu.org/licenses/>." | ||||
|           ], | ||||
|         } | ||||
|       ], | ||||
|       "dart.runPubGetOnPubspecChanges": false, | ||||
| } | ||||
| @ -1 +0,0 @@ | ||||
| ../../AUTHORS | ||||
| @ -1,25 +0,0 @@ | ||||
| ## 2.0.1 | ||||
| 
 | ||||
|  - **FIX**: apply dart fix --apply. | ||||
| 
 | ||||
| ## 2.0.0 | ||||
| 
 | ||||
| > Note: This release has breaking changes. | ||||
| 
 | ||||
|  - **DOCS**: add simple example. | ||||
|  - **BREAKING** **REFACTOR**: fix cascade dart good practices + docs. | ||||
| 
 | ||||
| ## 1.2.0 | ||||
| 
 | ||||
|  - **FEAT**: add new middleware feature. | ||||
|  - **FEAT**: implements doublelinked list for middlewares. | ||||
|  - **FEAT**: [WIP] implements middleware system. | ||||
|  - **FEAT**: [WIP] work on middleware feature. | ||||
| 
 | ||||
| ## 1.1.0 | ||||
| 
 | ||||
|  - **FEAT**: add oauth2 refresh token client. | ||||
| 
 | ||||
| ## 1.0.0 | ||||
| 
 | ||||
| - Initial version. | ||||
| @ -1 +0,0 @@ | ||||
| ../../LICENSE | ||||
| @ -1,182 +0,0 @@ | ||||
| <!-- | ||||
|  * Copyright (C) 2022 WYATT GROUP | ||||
|  * Please see the AUTHORS file for details. | ||||
| 
 | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * any later version. | ||||
| 
 | ||||
|  * This program is distributed in the hope that it will be useful,  | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
|  * GNU General Public License for more details. | ||||
| 
 | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| --> | ||||
| 
 | ||||
| # HTTP Client | ||||
| 
 | ||||
| <p align="left"> | ||||
|   <a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis"><img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" /></a> | ||||
|   <img src="https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square" alt="SDK: Dart & Flutter" /> | ||||
| </p> | ||||
| 
 | ||||
| HTTP Client for Dart with Middlewares ! | ||||
| 
 | ||||
| ## Getting started | ||||
| 
 | ||||
| Simply add wyatt_http_client in pubspec.yaml, then | ||||
| 
 | ||||
| ```dart | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| Firstly you have to understand **Middleware** and **Pipeline** concepts. | ||||
| 
 | ||||
| In `wyatt_http_client` a middleware is an object where requests and responses  | ||||
| pass through. And a pipeline is basicaly a list of middlewares. | ||||
| 
 | ||||
| In a pipeline with middlewares A and B, if request pass through A, then B,  | ||||
| the response will pass through B then A. | ||||
| 
 | ||||
| > You can `print(pipeline)` to get full process order of a pipeline. | ||||
| 
 | ||||
| For example, if you want to log every request, and simplify an url you can use provided `SimpleLogger` and `UriPrefix` . | ||||
| 
 | ||||
| ```dart | ||||
| // Create the Pipeline | ||||
| final Pipeline pipeline = Pipeline() | ||||
|     ..addMiddleware( | ||||
|         const UriPrefixMiddleware( | ||||
|             protocol: Protocols.http, | ||||
|             authority: 'localhost:80', | ||||
|         ), | ||||
|     ) | ||||
|     ..addMiddleware(const SimpleLoggerMiddleware()); | ||||
| ``` | ||||
| 
 | ||||
| Then if you print the pipeline,  | ||||
| 
 | ||||
| ``` | ||||
| [Req] -> UriPrefix -> SimpleLogger | ||||
| [Res] -> SimpleLogger | ||||
| ``` | ||||
| 
 | ||||
| > The `response` doesn't pass through `UriPrefix` because it's an `OnRequestMiddleware` only. | ||||
| 
 | ||||
| And you can create a client. | ||||
| 
 | ||||
| ```dart | ||||
| final client = MiddlewareClient(pipeline: pipeline); | ||||
| ``` | ||||
| 
 | ||||
| At this point you can use `client` like every Client from `package:http/http.dart` . | ||||
| 
 | ||||
| ## Recipes | ||||
| 
 | ||||
| ### Rest API with URL Authentication | ||||
| 
 | ||||
| Let's build a client for a REST API where the (bad) authentication is through the URL. | ||||
| We need some middlewares: | ||||
| 
 | ||||
| * SimpleLogger, to log every request and response (useful for debug). | ||||
| * BodyToJson, to automaticaly transform Map object to JSON. | ||||
| * UriPrefix, to simplify the build of an API Object (save protocol and API prefix). | ||||
| * UnsafeAuth, to use url based authentication. | ||||
| 
 | ||||
| Let's start by creating the Pipeline: | ||||
| 
 | ||||
| ```dart | ||||
| final Pipeline pipeline = Pipeline() | ||||
|     ..addMiddleware( | ||||
|         const UriPrefixMiddleware( | ||||
|             protocol: Protocols.http, | ||||
|             authority: 'localhost:80', | ||||
|         ), | ||||
|     ) | ||||
|     ..addMiddleware(const BodyToJsonMiddleware()) | ||||
|     ..addMiddleware( | ||||
|         const UnsafeAuthMiddleware( | ||||
|             username: 'wyatt', | ||||
|             password: 'motdepasse', | ||||
|         ), | ||||
|     ) | ||||
|     ..addMiddleware(SimpleLoggerMiddleware()); | ||||
| ``` | ||||
| 
 | ||||
| Then simply create a client and make a call. | ||||
| 
 | ||||
| ```dart | ||||
| final client = MiddlewareClient(pipeline: pipeline); | ||||
| 
 | ||||
| await client.get(Uri.parse('/protected')); | ||||
| ``` | ||||
| 
 | ||||
| > Here it make a `GET` call on `http://localhost:80/protected?username=wyatt&password=motdepasse` | ||||
| 
 | ||||
| And voilà. | ||||
| 
 | ||||
| ### Rest API with Oauth2 | ||||
| 
 | ||||
| So now we want a real authentication. | ||||
| 
 | ||||
| ```dart | ||||
| final Pipeline pipeline = Pipeline() | ||||
|     ..addMiddleware( | ||||
|         const UriPrefixMiddleware( | ||||
|             protocol: Protocols.http, | ||||
|             authority: 'localhost:80', | ||||
|         ), | ||||
|     ) | ||||
|     ..addMiddleware(const BodyToJsonMiddleware()) | ||||
|     ..addMiddleware( | ||||
|         RefreshTokenAuthMiddleware( | ||||
|             authorizationEndpoint: '/auth/sign-in', | ||||
|             tokenEndpoint: '/auth/refresh', | ||||
|             accessTokenParser: (body) => body['access_token']! as String, | ||||
|             refreshTokenParser: (body) => body['refresh_token']! as String, | ||||
|             unauthorized: HttpStatus.forbidden, | ||||
|         ), | ||||
|     ) | ||||
|     ..addMiddleware(const SimpleLoggerMiddleware()); | ||||
| ``` | ||||
| 
 | ||||
| > Here we just change `UnsafeAuthMiddleware` by `RefreshTokenAuthMiddleware` and the whole app while adapt to a new authentication system. | ||||
| 
 | ||||
| ### Create a new Middleware | ||||
| 
 | ||||
| You can create your own middleware by implementing `Middleware` class, and use mixins to add `OnRequest` and/or `OnResponse` methods. | ||||
| 
 | ||||
| ```dart | ||||
| class SimpleLoggerMiddleware  | ||||
|     with OnRequestMiddleware, OnResponseMiddleware  | ||||
|     implements Middleware { | ||||
|      | ||||
|   const SimpleLoggerMiddleware(); | ||||
|    | ||||
|   @override | ||||
|   String getName() => 'SimpleLogger'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     print('${getName()}::OnRequest'); | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     print('${getName()}::OnResponse'); | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| @ -1 +0,0 @@ | ||||
| include: package:wyatt_analysis/analysis_options.yaml | ||||
| @ -1,78 +0,0 @@ | ||||
| // Copyright (C) 2023 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| 
 | ||||
| Future<void> testSimpleGet() async { | ||||
|   print('testSimpleGet'); | ||||
|   final pipeline = Pipeline() | ||||
|     ..addMiddleware(const BodyToJsonMiddleware()) | ||||
|     ..addMiddleware(const SimpleLoggerMiddleware()); | ||||
| 
 | ||||
|   final client = MiddlewareClient(pipeline: pipeline); | ||||
| 
 | ||||
|   final response = await client | ||||
|       .get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1')); | ||||
|   print(response.body); | ||||
| } | ||||
| 
 | ||||
| Future<void> testUriPrefix() async { | ||||
|   print('testUriPrefix'); | ||||
|   final pipeline = Pipeline() | ||||
|     ..addMiddleware( | ||||
|       const UriPrefixMiddleware( | ||||
|         protocol: Protocols.https, | ||||
|         authority: 'jsonplaceholder.typicode.com', | ||||
|       ), | ||||
|     ) | ||||
|     ..addMiddleware(const BodyToJsonMiddleware()) | ||||
|     ..addMiddleware(const SimpleLoggerMiddleware()); | ||||
| 
 | ||||
|   final client = MiddlewareClient(pipeline: pipeline); | ||||
| 
 | ||||
|   final response = await client.get(Uri.parse('/todos/1')); | ||||
|   print(response.body); | ||||
| } | ||||
| 
 | ||||
| Future<void> testBasicAuth() async { | ||||
|   print('testBasicAuth'); | ||||
|   final pipeline = Pipeline() | ||||
|     ..addMiddleware( | ||||
|       const BasicAuthMiddleware( | ||||
|         username: 'guest', | ||||
|         password: 'guest', | ||||
|       ), | ||||
|     ) | ||||
|     ..addMiddleware(const BodyToJsonMiddleware()) | ||||
|     ..addMiddleware(const SimpleLoggerMiddleware()); | ||||
| 
 | ||||
|   final client = MiddlewareClient(pipeline: pipeline); | ||||
| 
 | ||||
|   final response = | ||||
|       await client.get(Uri.parse('https://jigsaw.w3.org/HTTP/Basic/')); | ||||
| 
 | ||||
|   if (HttpStatus.from(response.statusCode).isSuccess()) { | ||||
|     print("🎉 You're in!"); | ||||
|   } else { | ||||
|     print("⭕️ Nope, you're not in."); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void main(List<String> args) { | ||||
|   testSimpleGet(); | ||||
|   testUriPrefix(); | ||||
|   testBasicAuth(); | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| 
 | ||||
| /// {@template middleware} | ||||
| /// A middleware is a class that can intercept requests and responses | ||||
| /// and modify them before they are sent to the server or before they | ||||
| /// are returned to the client. | ||||
| /// {@endtemplate} | ||||
| abstract class Middleware { | ||||
|   /// {@macro middleware} | ||||
|   const Middleware(); | ||||
| 
 | ||||
|   /// The name of the middleware. | ||||
|   String getName(); | ||||
| } | ||||
| 
 | ||||
| mixin OnRequestMiddleware { | ||||
|   /// Performs an action before the request is sent to the server. | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| mixin OnResponseMiddleware { | ||||
|   /// Performs an action before the response is returned to the client. | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ); | ||||
| } | ||||
| @ -1,134 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| import 'package:wyatt_http_client/src/models/unfreezed_request.dart'; | ||||
| import 'package:wyatt_http_client/src/pipeline.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_methods.dart'; | ||||
| 
 | ||||
| /// {@template middleware_client} | ||||
| /// A custom [Client] implementation that allows you to intercept requests | ||||
| /// and responses and modify them before they are sent to the server or | ||||
| /// before they are returned to the client. | ||||
| /// {@endtemplate} | ||||
| class MiddlewareClient extends BaseClient { | ||||
|   /// {@macro middleware_client} | ||||
|   MiddlewareClient({ | ||||
|     Pipeline? pipeline, | ||||
|     Client? inner, | ||||
|   })  : pipeline = pipeline ?? Pipeline(), | ||||
|         inner = inner ?? Client(); | ||||
| 
 | ||||
|   /// The [Client] that will be used to send requests. | ||||
|   final Client inner; | ||||
| 
 | ||||
|   /// The [Pipeline] that will be used to intercept requests and responses. | ||||
|   final Pipeline pipeline; | ||||
| 
 | ||||
|   @override | ||||
|   Future<Response> head(Uri url, {Map<String, String>? headers}) => | ||||
|       _sendUnstreamed(HttpMethods.head.method, url, headers); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Response> get(Uri url, {Map<String, String>? headers}) => | ||||
|       _sendUnstreamed(HttpMethods.get.method, url, headers); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Response> post( | ||||
|     Uri url, { | ||||
|     Map<String, String>? headers, | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   }) => | ||||
|       _sendUnstreamed(HttpMethods.post.method, url, headers, body, encoding); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Response> put( | ||||
|     Uri url, { | ||||
|     Map<String, String>? headers, | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   }) => | ||||
|       _sendUnstreamed(HttpMethods.put.method, url, headers, body, encoding); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Response> patch( | ||||
|     Uri url, { | ||||
|     Map<String, String>? headers, | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   }) => | ||||
|       _sendUnstreamed(HttpMethods.patch.method, url, headers, body, encoding); | ||||
| 
 | ||||
|   @override | ||||
|   Future<Response> delete( | ||||
|     Uri url, { | ||||
|     Map<String, String>? headers, | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   }) => | ||||
|       _sendUnstreamed(HttpMethods.delete.method, url, headers, body, encoding); | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) => inner.send(request); | ||||
| 
 | ||||
|   Future<Response> _sendUnstreamed( | ||||
|     String method, | ||||
|     Uri url, | ||||
|     Map<String, String>? headers, [ | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   ]) async { | ||||
|     final originalRequest = MiddlewareRequest( | ||||
|       unfreezedRequest: UnfreezedRequest( | ||||
|         method: method, | ||||
|         url: url, | ||||
|         headers: headers, | ||||
|         body: body, | ||||
|         encoding: encoding, | ||||
|       ), | ||||
|     ); | ||||
|     final requestContext = MiddlewareContext( | ||||
|       pipeline: pipeline, | ||||
|       client: this, | ||||
|       originalRequest: originalRequest, | ||||
|     ); | ||||
| 
 | ||||
|     final modifiedRequest = await pipeline.onRequest( | ||||
|       requestContext, | ||||
|       originalRequest.copyWith(), | ||||
|     ); | ||||
| 
 | ||||
|     final originalResponse = MiddlewareResponse( | ||||
|       httpResponse: await Response.fromStream( | ||||
|         await send(modifiedRequest.request), | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     final responseContext = | ||||
|         requestContext.copyWith(originalResponse: originalResponse); | ||||
| 
 | ||||
|     final modifiedResponse = | ||||
|         await pipeline.onResponse(responseContext, originalResponse.copyWith()); | ||||
| 
 | ||||
|     return modifiedResponse.httpResponse as Response; | ||||
|   } | ||||
| } | ||||
| @ -1,65 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/authentication_methods.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| 
 | ||||
| /// {@template basic_auth_middleware} | ||||
| /// A middleware that adds basic authentication to the request. | ||||
| /// {@endtemplate} | ||||
| class BasicAuthMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   /// {@macro basic_auth_middleware} | ||||
|   const BasicAuthMiddleware({ | ||||
|     this.username, | ||||
|     this.password, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|   }); | ||||
| 
 | ||||
|   /// The username to use for authentication. | ||||
|   final String? username; | ||||
| 
 | ||||
|   /// The password to use for authentication. | ||||
|   final String? password; | ||||
| 
 | ||||
|   /// The header to use for authentication. | ||||
|   final String authenticationHeader; | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'BasicAuth'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     if (username == null || password == null) { | ||||
|       return request; | ||||
|     } | ||||
| 
 | ||||
|     final mutation = { | ||||
|       authenticationHeader: '${AuthenticationMethods.basic} ' | ||||
|           '${base64Encode(utf8.encode('$username:$password'))}', | ||||
|     }; | ||||
|     final Map<String, String> headers = request.headers..addAll(mutation); | ||||
|     request.modifyRequest(request.unfreezedRequest.copyWith(headers: headers)); | ||||
|     return request; | ||||
|   } | ||||
| } | ||||
| @ -1,50 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| 
 | ||||
| /// {@template body_to_json_middleware} | ||||
| /// A middleware that transforms the body in json if it's a [Map]. | ||||
| /// {@endtemplate} | ||||
| class BodyToJsonMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   /// {@macro body_to_json_middleware} | ||||
|   const BodyToJsonMiddleware(); | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'BodyToJson'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     final mutation = { | ||||
|       'content-type': 'application/json; charset=utf-8', | ||||
|     }; | ||||
|     if (request.body is Map) { | ||||
|       final Map<String, String> headers = request.headers..addAll(mutation); | ||||
|       request.modifyRequest( | ||||
|         request.unfreezedRequest | ||||
|             .copyWith(headers: headers, body: jsonEncode(request.body)), | ||||
|       ); | ||||
|     } | ||||
|     return request; | ||||
|   } | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| 
 | ||||
| /// {@template default_middleware} | ||||
| /// A default middleware that does nothing. | ||||
| /// {@endtemplate} | ||||
| class DefaultMiddleware implements Middleware { | ||||
|   /// {@macro default_middleware} | ||||
|   const DefaultMiddleware(); | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'DefaultMiddleware'; | ||||
| } | ||||
| @ -1,87 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/digest_auth.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| 
 | ||||
| /// {@template digest_auth_middleware} | ||||
| /// A middleware that handles digest authentication. | ||||
| /// {@endtemplate} | ||||
| class DigestAuthMiddleware | ||||
|     with OnRequestMiddleware, OnResponseMiddleware | ||||
|     implements Middleware { | ||||
|   /// {@macro digest_auth_middleware} | ||||
|   DigestAuthMiddleware({ | ||||
|     required this.username, | ||||
|     required this.password, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     this.wwwAuthenticateHeader = HeaderKeys.wwwAuthenticate, | ||||
|     this.unauthorized = HttpStatus.unauthorized, | ||||
|   }) : _digestAuth = DigestAuth(username, password); | ||||
|   final String username; | ||||
|   final String password; | ||||
|   final DigestAuth _digestAuth; | ||||
|   final String authenticationHeader; | ||||
|   final String wwwAuthenticateHeader; | ||||
|   final HttpStatus unauthorized; | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'DigestAuth'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     if (_digestAuth.isReady()) { | ||||
|       final mutation = { | ||||
|         authenticationHeader: _digestAuth.getAuthString( | ||||
|           request.method, | ||||
|           request.url, | ||||
|         ), | ||||
|       }; | ||||
|       final Map<String, String> headers = request.headers..addAll(mutation); | ||||
|       request | ||||
|           .modifyRequest(request.unfreezedRequest.copyWith(headers: headers)); | ||||
|     } | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     if (response.status == unauthorized) { | ||||
|       final authInfo = | ||||
|           response.headers[HeaderKeys.wwwAuthenticate.toLowerCase()]; | ||||
|       _digestAuth.initFromAuthenticateHeader(authInfo); | ||||
| 
 | ||||
|       final MiddlewareRequest? newRequest = context.lastRequest?.copyWith(); | ||||
| 
 | ||||
|       if (newRequest != null) { | ||||
|         final newResponse = await context.client.send(newRequest.request); | ||||
|         return MiddlewareResponse(httpResponse: newResponse); | ||||
|       } | ||||
|     } | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| // All built-in middlewares | ||||
| 
 | ||||
| export 'basic_auth_middleware.dart'; | ||||
| export 'body_to_json_middleware.dart'; | ||||
| export 'default_middleware.dart'; | ||||
| export 'digest_auth_middleware.dart'; | ||||
| export 'refresh_token_auth_middleware.dart'; | ||||
| export 'simple_logger_middleware.dart'; | ||||
| export 'unsafe_auth_middleware.dart'; | ||||
| export 'uri_prefix_middleware.dart'; | ||||
| @ -1,184 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middleware_client.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/authentication_methods.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/delay.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| 
 | ||||
| typedef TokenParser = String Function(Map<String, dynamic>); | ||||
| 
 | ||||
| /// {@template refresh_token_auth_middleware} | ||||
| /// A middleware that refreshes the access token when it expires. | ||||
| /// This middleware is useful for OAuth2. | ||||
| /// {@endtemplate} | ||||
| class RefreshTokenAuthMiddleware | ||||
|     with OnRequestMiddleware, OnResponseMiddleware | ||||
|     implements Middleware { | ||||
|   /// {@macro refresh_token_auth_middleware} | ||||
|   RefreshTokenAuthMiddleware({ | ||||
|     required this.authorizationEndpoint, | ||||
|     required this.tokenEndpoint, | ||||
|     required this.accessTokenParser, | ||||
|     required this.refreshTokenParser, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     this.authenticationMethod = AuthenticationMethods.bearer, | ||||
|     this.unauthorized = HttpStatus.unauthorized, | ||||
|     this.maxAttempts = 8, | ||||
|   }); | ||||
|   final String authorizationEndpoint; | ||||
|   final String tokenEndpoint; | ||||
| 
 | ||||
|   String? accessToken; | ||||
|   final TokenParser accessTokenParser; | ||||
|   String? refreshToken; | ||||
|   final TokenParser refreshTokenParser; | ||||
| 
 | ||||
|   final String authenticationHeader; | ||||
|   final String authenticationMethod; | ||||
|   final HttpStatus unauthorized; | ||||
|   final int maxAttempts; | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'RefreshToken'; | ||||
| 
 | ||||
|   Future<MiddlewareRequest?> refresh(MiddlewareContext context) async { | ||||
|     final subPipeline = context.pipeline.sub(this); | ||||
|     final httpClient = MiddlewareClient( | ||||
|       pipeline: subPipeline, | ||||
|       inner: context.client.inner, | ||||
|     ); | ||||
|     final headers = { | ||||
|       authenticationHeader: '$authenticationMethod $refreshToken', | ||||
|     }; | ||||
|     final response = MiddlewareResponse( | ||||
|       httpResponse: await httpClient.get( | ||||
|         Uri.parse(tokenEndpoint), | ||||
|         headers: headers, | ||||
|       ), | ||||
|     ); | ||||
|     if (response.status.isSuccess()) { | ||||
|       final body = jsonDecode(response.body) as Map<String, dynamic>; | ||||
|       accessToken = accessTokenParser(body); | ||||
| 
 | ||||
|       // Then modify current request with accessToken | ||||
|       final mutation = { | ||||
|         authenticationHeader: '$authenticationMethod $accessToken', | ||||
|       }; | ||||
|       final Map<String, String>? headers = context.lastRequest?.headers | ||||
|         ?..addAll(mutation); | ||||
|       final newRequest = context.lastRequest?.copyWith( | ||||
|         unfreezedRequest: | ||||
|             context.lastRequest?.unfreezedRequest.copyWith(headers: headers), | ||||
|       ); | ||||
| 
 | ||||
|       return newRequest; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   Future<MiddlewareRequest?> retry(MiddlewareContext context) async { | ||||
|     // Retry | ||||
|     int attempt = 1; | ||||
|     while (attempt <= maxAttempts) { | ||||
|       // Delayed before retry | ||||
|       await Future<void>.delayed(Delay.getRetryDelay(attempt)); | ||||
| 
 | ||||
|       final newRequest = await refresh(context); | ||||
|       if (newRequest != null) { | ||||
|         return newRequest; | ||||
|       } | ||||
|       attempt++; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     // Check if it is authorization | ||||
|     if (context.originalRequest?.url == Uri.parse(authorizationEndpoint)) { | ||||
|       return request; | ||||
|     } | ||||
|     // Check if it is refresh | ||||
|     if (context.originalRequest?.url == Uri.parse(tokenEndpoint)) { | ||||
|       return request; | ||||
|     } | ||||
|     // If AccessToken not null then return request with authorization header | ||||
|     if (accessToken != null) { | ||||
|       final mutation = { | ||||
|         authenticationHeader: '$authenticationMethod $accessToken', | ||||
|       }; | ||||
|       final Map<String, String> headers = request.headers..addAll(mutation); | ||||
|       request | ||||
|           .modifyRequest(request.unfreezedRequest.copyWith(headers: headers)); | ||||
|       return request; | ||||
|     } | ||||
|     // If AccessToken is null BUT there is a refreshToken, then try refreshing | ||||
|     if (refreshToken != null) { | ||||
|       MiddlewareRequest? newRequest = await refresh(context); | ||||
|       newRequest ??= await retry(context); | ||||
|       return newRequest ?? request; | ||||
|     } | ||||
|     // Pass | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     // Check if it is authorization | ||||
|     if (context.originalRequest?.url == Uri.parse(authorizationEndpoint)) { | ||||
|       // If success, then update tokens | ||||
|       if (response.status.isSuccess()) { | ||||
|         final body = jsonDecode(response.body) as Map<String, dynamic>; | ||||
|         final accessToken = accessTokenParser(body); | ||||
|         final refreshToken = refreshTokenParser(body); | ||||
| 
 | ||||
|         if (accessToken.isNotEmpty) { | ||||
|           this.accessToken = accessToken; | ||||
|         } | ||||
|         if (refreshToken.isNotEmpty) { | ||||
|           this.refreshToken = refreshToken; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (response.status == unauthorized) { | ||||
|       // Refresh | ||||
|       MiddlewareRequest? newRequest = await refresh(context); | ||||
|       newRequest ??= await retry(context); | ||||
| 
 | ||||
|       if (newRequest != null) { | ||||
|         return response.copyWith( | ||||
|           httpResponse: await context.client.send(newRequest.request), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @ -1,72 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| 
 | ||||
| /// {@template simple_logger_middleware} | ||||
| /// A simple logger middleware that logs the request and response. | ||||
| /// {@endtemplate} | ||||
| class SimpleLoggerMiddleware | ||||
|     with OnRequestMiddleware, OnResponseMiddleware | ||||
|     implements Middleware { | ||||
|   /// {@macro simple_logger_middleware} | ||||
|   const SimpleLoggerMiddleware(); | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'SimpleLogger'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     final log = StringBuffer() | ||||
|       ..writeln('${getName()}::OnRequest') | ||||
|       ..writeln('>> ${request.method} ${request.url}'); | ||||
|     if (request.headers.isNotEmpty) { | ||||
|       log.writeln('>> Headers:'); | ||||
|       request.headers.forEach((key, value) { | ||||
|         log.writeln('>>   $key: $value'); | ||||
|       }); | ||||
|     } | ||||
|     if (request.encodedBody.isNotEmpty) { | ||||
|       log | ||||
|         ..writeln('>> Body:') | ||||
|         ..writeln(request.encodedBody); | ||||
|     } | ||||
|     print(log); | ||||
| 
 | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     final log = StringBuffer() | ||||
|       ..writeln('${getName()}::OnResponse') | ||||
|       ..writeln('>> Status: ${response.status.name.toUpperCase()}') | ||||
|       ..writeln('>> Length: ${response.contentLength ?? '0'} bytes'); | ||||
| 
 | ||||
|     print(log); | ||||
| 
 | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @ -1,56 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/convert.dart'; | ||||
| 
 | ||||
| /// {@template unsafe_auth_middleware} | ||||
| /// A middleware that appends the username and password to the URL. | ||||
| /// | ||||
| /// This is not recommended to use in production. | ||||
| /// {@endtemplate} | ||||
| class UnsafeAuthMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   const UnsafeAuthMiddleware({ | ||||
|     this.username, | ||||
|     this.password, | ||||
|     this.usernameField = 'username', | ||||
|     this.passwordField = 'password', | ||||
|   }); | ||||
|   final String? username; | ||||
|   final String? password; | ||||
| 
 | ||||
|   final String usernameField; | ||||
|   final String passwordField; | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'UnsafeAuth'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     if (username == null || password == null) { | ||||
|       return request; | ||||
|     } | ||||
|     final Uri uri = | ||||
|         request.url + '?$usernameField=$username&$passwordField=$password'; | ||||
|     request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); | ||||
|     return request; | ||||
|   } | ||||
| } | ||||
| @ -1,50 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/protocols.dart'; | ||||
| 
 | ||||
| /// {@template uri_prefix_middleware} | ||||
| /// A middleware that adds a prefix to the request's URI. | ||||
| /// {@endtemplate} | ||||
| class UriPrefixMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   /// {@macro uri_prefix_middleware} | ||||
|   const UriPrefixMiddleware({ | ||||
|     required this.protocol, | ||||
|     required this.authority, | ||||
|   }); | ||||
| 
 | ||||
|   /// The protocol of the prefix. | ||||
|   final Protocols protocol; | ||||
| 
 | ||||
|   /// The authority of the prefix. | ||||
|   final String? authority; | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'UriPrefix'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); | ||||
|     request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); | ||||
|     return request; | ||||
|   } | ||||
| } | ||||
| @ -1,78 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware_client.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| import 'package:wyatt_http_client/src/pipeline.dart'; | ||||
| 
 | ||||
| /// {@template middleware_context} | ||||
| /// A class that contains the context of the middleware. | ||||
| /// {@endtemplate} | ||||
| class MiddlewareContext { | ||||
|   /// {@macro middleware_context} | ||||
|   const MiddlewareContext({ | ||||
|     required this.pipeline, | ||||
|     required this.client, | ||||
|     this.originalRequest, | ||||
|     this.lastRequest, | ||||
|     this.originalResponse, | ||||
|     this.lastResponse, | ||||
|   }); | ||||
| 
 | ||||
|   /// The pipeline that the middleware is in. | ||||
|   final Pipeline pipeline; | ||||
| 
 | ||||
|   /// The client that the middleware is in. | ||||
|   final MiddlewareClient client; | ||||
| 
 | ||||
|   /// The original request that the middleware is in. | ||||
|   final MiddlewareRequest? originalRequest; | ||||
| 
 | ||||
|   /// The last request that the middleware is in. | ||||
|   final MiddlewareRequest? lastRequest; | ||||
| 
 | ||||
|   /// The original response that the middleware is in. | ||||
|   final MiddlewareResponse? originalResponse; | ||||
| 
 | ||||
|   /// The last response that the middleware is in. | ||||
|   final MiddlewareResponse? lastResponse; | ||||
| 
 | ||||
|   /// Create a copy of this [MiddlewareContext] with the given fields replaced | ||||
|   /// with the new values. | ||||
|   MiddlewareContext copyWith({ | ||||
|     Pipeline? pipeline, | ||||
|     MiddlewareClient? client, | ||||
|     MiddlewareRequest? originalRequest, | ||||
|     MiddlewareRequest? lastRequest, | ||||
|     MiddlewareResponse? originalResponse, | ||||
|     MiddlewareResponse? lastResponse, | ||||
|   }) => | ||||
|       MiddlewareContext( | ||||
|         pipeline: pipeline ?? this.pipeline, | ||||
|         client: client ?? this.client, | ||||
|         originalRequest: originalRequest ?? this.originalRequest, | ||||
|         lastRequest: lastRequest ?? this.lastRequest, | ||||
|         originalResponse: originalResponse ?? this.originalResponse, | ||||
|         lastResponse: lastResponse ?? this.lastResponse, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => | ||||
|       'MiddlewareContext(pipeline: $pipeline, client: $client, ' | ||||
|       'originalRequest: $originalRequest, lastRequest: $lastRequest, ' | ||||
|       'originalResponse: $originalResponse, lastResponse: $lastResponse)'; | ||||
| } | ||||
| @ -1,102 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:wyatt_http_client/src/models/unfreezed_request.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/convert.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/request_utils.dart'; | ||||
| 
 | ||||
| /// {@template middleware_request} | ||||
| /// A class that represents a middleware request. | ||||
| /// {@endtemplate} | ||||
| class MiddlewareRequest { | ||||
|   /// {@macro middleware_request} | ||||
|   MiddlewareRequest({ | ||||
|     required this.unfreezedRequest, | ||||
|   }) : _httpRequest = Request(unfreezedRequest.method, unfreezedRequest.url); | ||||
| 
 | ||||
|   /// The unfreezed request. | ||||
|   UnfreezedRequest unfreezedRequest; | ||||
| 
 | ||||
|   Request _httpRequest; | ||||
| 
 | ||||
|   /// The http request. (Read-only) | ||||
|   Request get request => _httpRequest; | ||||
| 
 | ||||
|   /// The request method (proxy, read-only). | ||||
|   String get method => _httpRequest.method; | ||||
| 
 | ||||
|   /// The request url (proxy, read-only). | ||||
|   Uri get url => _httpRequest.url; | ||||
| 
 | ||||
|   /// The request headers (proxy, read-only). | ||||
|   Map<String, String> get headers => _httpRequest.headers; | ||||
| 
 | ||||
|   /// The request body (proxy, read-only). | ||||
|   Encoding get encoding => _httpRequest.encoding; | ||||
| 
 | ||||
|   /// The request body (proxy, read-only). | ||||
|   String get encodedBody => _httpRequest.body; | ||||
| 
 | ||||
|   /// The request body (proxy, read-only). | ||||
|   Object? get body => unfreezedRequest.body; | ||||
| 
 | ||||
|   /// Copies this request and returns a new request with the given | ||||
|   /// [unfreezedRequest]. | ||||
|   MiddlewareRequest copyWith({ | ||||
|     UnfreezedRequest? unfreezedRequest, | ||||
|   }) => | ||||
|       MiddlewareRequest( | ||||
|         unfreezedRequest: unfreezedRequest ?? this.unfreezedRequest, | ||||
|       ); | ||||
| 
 | ||||
|   /// Modifies the request with the given [unfreezedRequest]. | ||||
|   void modifyRequest(UnfreezedRequest unfreezedRequest) { | ||||
|     String? body; | ||||
|     if (unfreezedRequest.body != null) { | ||||
|       var body = unfreezedRequest.body; | ||||
|       if (body is String) { | ||||
|         body = body; | ||||
|       } else if (body is List) { | ||||
|         body = String.fromCharCodes(body.cast<int>()); | ||||
|       } else if (body is Map) { | ||||
|         body = Convert.mapToQuery(body.cast<String, String>()); | ||||
|       } | ||||
|     } | ||||
|     _httpRequest = RequestUtils.copyRequestWith( | ||||
|       _httpRequest, | ||||
|       method: unfreezedRequest.method, | ||||
|       url: unfreezedRequest.url, | ||||
|       headers: unfreezedRequest.headers, | ||||
|       body: body, | ||||
|     ) as Request; | ||||
|     if (unfreezedRequest.encoding != null) { | ||||
|       _httpRequest.encoding = unfreezedRequest.encoding!; | ||||
|     } | ||||
|     this.unfreezedRequest = unfreezedRequest; | ||||
|   } | ||||
| 
 | ||||
|   /// Applies the changes made to the request by modifying it with the | ||||
|   /// [unfreezedRequest]. | ||||
|   void apply() { | ||||
|     modifyRequest(unfreezedRequest); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'MiddlewareRequest(unfreezedRequest: $unfreezedRequest)'; | ||||
| } | ||||
| @ -1,63 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| 
 | ||||
| /// {@template middleware_response} | ||||
| /// A class that represents a middleware response. | ||||
| /// {@endtemplate} | ||||
| class MiddlewareResponse { | ||||
|   /// {@macro middleware_response} | ||||
|   const MiddlewareResponse({ | ||||
|     required this.httpResponse, | ||||
|   }); | ||||
| 
 | ||||
|   /// {@macro middleware_response} | ||||
|   final BaseResponse httpResponse; | ||||
| 
 | ||||
|   /// The status code of the response. (proxy) | ||||
|   int get statusCode => httpResponse.statusCode; | ||||
| 
 | ||||
|   /// The status of the response. (proxy) | ||||
|   HttpStatus get status => HttpStatus.from(statusCode); | ||||
| 
 | ||||
|   /// The body of the response. (proxy or empty string) | ||||
|   String get body { | ||||
|     if (httpResponse is Response) { | ||||
|       return (httpResponse as Response).body; | ||||
|     } else { | ||||
|       return ''; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// The content length of the response. (proxy) | ||||
|   int? get contentLength => httpResponse.contentLength; | ||||
| 
 | ||||
|   /// The headers of the response. (proxy) | ||||
|   Map<String, String> get headers => httpResponse.headers; | ||||
| 
 | ||||
|   /// Returns a copy of this response with the given [httpResponse]. | ||||
|   MiddlewareResponse copyWith({ | ||||
|     BaseResponse? httpResponse, | ||||
|   }) => | ||||
|       MiddlewareResponse( | ||||
|         httpResponse: httpResponse ?? this.httpResponse, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'MiddlewareResponse(httpResponse: $httpResponse)'; | ||||
| } | ||||
| @ -1,69 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| /// {@template unfreezed_request} | ||||
| /// A class that represents an unfreezed request. | ||||
| /// It is used to unfreeze a Request object, and allows you to | ||||
| /// modify the request before sending it. | ||||
| /// {@endtemplate} | ||||
| class UnfreezedRequest { | ||||
|   /// {@macro unfreezed_request} | ||||
|   const UnfreezedRequest({ | ||||
|     required this.method, | ||||
|     required this.url, | ||||
|     this.headers, | ||||
|     this.body, | ||||
|     this.encoding, | ||||
|   }); | ||||
| 
 | ||||
|   /// The request method. | ||||
|   final String method; | ||||
| 
 | ||||
|   /// The request url. | ||||
|   final Uri url; | ||||
| 
 | ||||
|   /// The request headers. | ||||
|   final Map<String, String>? headers; | ||||
| 
 | ||||
|   /// The request body. | ||||
|   final Object? body; | ||||
| 
 | ||||
|   /// The request encoding. | ||||
|   final Encoding? encoding; | ||||
| 
 | ||||
|   /// Copies this request and returns a new request with the given [method], | ||||
|   /// [url], [headers], [body] and [encoding]. | ||||
|   UnfreezedRequest copyWith({ | ||||
|     String? method, | ||||
|     Uri? url, | ||||
|     Map<String, String>? headers, | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   }) => | ||||
|       UnfreezedRequest( | ||||
|         method: method ?? this.method, | ||||
|         url: url ?? this.url, | ||||
|         headers: headers ?? this.headers, | ||||
|         body: body ?? this.body, | ||||
|         encoding: encoding ?? this.encoding, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'UnfreezedRequest(method: $method, url: $url, headers: ' | ||||
|       '$headers, body: $body, encoding: $encoding)'; | ||||
| } | ||||
| @ -1,122 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_context.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_request.dart'; | ||||
| import 'package:wyatt_http_client/src/models/middleware_response.dart'; | ||||
| 
 | ||||
| /// {@template pipeline} | ||||
| /// A [Pipeline] is a list of [Middleware]s that are executed in order. | ||||
| /// {@endtemplate} | ||||
| class Pipeline { | ||||
|   /// {@macro pipeline} | ||||
|   Pipeline() : _middlewares = <Middleware>[]; | ||||
| 
 | ||||
|   /// {@macro pipeline} | ||||
|   Pipeline.fromIterable(Iterable<Middleware> middlewares) | ||||
|       : _middlewares = middlewares.toList(); | ||||
| 
 | ||||
|   final List<Middleware> _middlewares; | ||||
| 
 | ||||
|   /// The length of the [Pipeline]. | ||||
|   /// | ||||
|   /// This is the number of [Middleware]s in the [Pipeline]. | ||||
|   int get length => _middlewares.length; | ||||
| 
 | ||||
|   /// Add a [Middleware] to this [Pipeline] | ||||
|   void addMiddleware(Middleware middleware) { | ||||
|     _middlewares.add(middleware); | ||||
|   } | ||||
| 
 | ||||
|   /// Create new [Pipeline] from the start or end to a specified [Middleware]. | ||||
|   Pipeline sub( | ||||
|     Middleware middleware, { | ||||
|     bool include = false, | ||||
|     bool fromEnd = false, | ||||
|   }) { | ||||
|     final nodes = <Middleware>[]; | ||||
|     final list = fromEnd ? _middlewares.reversed : _middlewares; | ||||
|     for (final m in list) { | ||||
|       if (m != middleware) { | ||||
|         nodes.add(m); | ||||
|       } | ||||
|       if (m == middleware) { | ||||
|         if (include) { | ||||
|           nodes.add(m); | ||||
|         } | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     return Pipeline.fromIterable(fromEnd ? nodes.reversed : nodes); | ||||
|   } | ||||
| 
 | ||||
|   /// Call the [onRequest] method of all [OnRequestMiddleware]s in the | ||||
|   /// [Pipeline]. | ||||
|   /// | ||||
|   /// The [MiddlewareRequest] returned by the last [OnRequestMiddleware] is | ||||
|   /// returned. | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     MiddlewareRequest req = request..apply(); | ||||
|     MiddlewareContext ctx = context.copyWith(lastRequest: req); | ||||
|     for (final middleware in _middlewares) { | ||||
|       if (middleware is OnRequestMiddleware) { | ||||
|         req = await (middleware as OnRequestMiddleware).onRequest(ctx, request); | ||||
|         ctx = context.copyWith(lastRequest: req); | ||||
|       } | ||||
|     } | ||||
|     return req; | ||||
|   } | ||||
| 
 | ||||
|   /// Call the [onResponse] method of all [OnResponseMiddleware]s in the | ||||
|   /// [Pipeline]. | ||||
|   /// | ||||
|   /// The [MiddlewareResponse] returned by the last [OnResponseMiddleware] is | ||||
|   /// returned. | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     MiddlewareResponse res = response; | ||||
|     MiddlewareContext ctx = context.copyWith(lastResponse: res); | ||||
|     for (final middleware in _middlewares.reversed) { | ||||
|       if (middleware is OnResponseMiddleware) { | ||||
|         res = await (middleware as OnResponseMiddleware) | ||||
|             .onResponse(ctx, response); | ||||
|         ctx = context.copyWith(lastResponse: res); | ||||
|       } | ||||
|     } | ||||
|     return res; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     final req = <String>[]; | ||||
|     final res = <String>[]; | ||||
|     for (final middleware in _middlewares) { | ||||
|       if (middleware is OnRequestMiddleware) { | ||||
|         req.add(middleware.getName()); | ||||
|       } | ||||
|       if (middleware is OnResponseMiddleware) { | ||||
|         res.insert(0, middleware.getName()); | ||||
|       } | ||||
|     } | ||||
|     return '[Req] -> ${req.join(' -> ')}\n[Res] -> ${res.join(' -> ')}'; | ||||
|   } | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Defines some authentication methods | ||||
| abstract class AuthenticationMethods { | ||||
|   /// The `Basic` authentication method. | ||||
|   static const String basic = 'Basic'; | ||||
| 
 | ||||
|   /// The `Bearer` authentication method. | ||||
|   static const String bearer = 'Bearer'; | ||||
| 
 | ||||
|   /// The `Digest` authentication method. | ||||
|   static const String digest = 'Digest'; | ||||
| } | ||||
| @ -1,62 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| /// Defines some convert functions. | ||||
| abstract class Convert { | ||||
|   /// Converts a list of bytes to a hex string. | ||||
|   /// | ||||
|   /// If [upperCase] is `true`, the hex string will be in uppercase. | ||||
|   static String toHex(List<int> bytes, {bool upperCase = false}) { | ||||
|     final buffer = StringBuffer(); | ||||
|     for (final int part in bytes) { | ||||
|       if (part & 0xff != part) { | ||||
|         throw const FormatException('Non-byte integer detected'); | ||||
|       } | ||||
|       buffer.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}'); | ||||
|     } | ||||
|     if (upperCase) { | ||||
|       return buffer.toString().toUpperCase(); | ||||
|     } else { | ||||
|       return buffer.toString(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Converts a map to a query string. | ||||
|   /// | ||||
|   /// If [encoding] is `null`, the default encoding is `utf8`. | ||||
|   /// | ||||
|   /// For example, the map `{a: 1, b: 2}` will be converted to `a=1&b=2`. | ||||
|   static String mapToQuery(Map<String, String> map, {Encoding? encoding}) { | ||||
|     final pairs = <List<String>>[]; | ||||
|     map.forEach( | ||||
|       (key, value) => pairs.add([ | ||||
|         Uri.encodeQueryComponent(key, encoding: encoding ?? utf8), | ||||
|         Uri.encodeQueryComponent(value, encoding: encoding ?? utf8), | ||||
|       ]), | ||||
|     ); | ||||
|     return pairs.map((pair) => '${pair[0]}=${pair[1]}').join('&'); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| extension UriX on Uri { | ||||
|   /// Returns a new [Uri] by appending the given [path] to this [Uri]. | ||||
|   Uri operator +(String path) { | ||||
|     final thisPath = toString(); | ||||
|     return Uri.parse(thisPath + path); | ||||
|   } | ||||
| } | ||||
| @ -1,30 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:crypto/crypto.dart'; | ||||
| 
 | ||||
| /// Defines some crypto functions. | ||||
| abstract class Crypto { | ||||
|   /// Hash a string using MD5 | ||||
|   static String md5Hash(String data) { | ||||
|     final content = const Utf8Encoder().convert(data); | ||||
|     const md5Crypto = md5; | ||||
|     final digest = md5Crypto.convert(content).toString(); | ||||
|     return digest; | ||||
|   } | ||||
| } | ||||
| @ -1,38 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:core'; | ||||
| import 'dart:math'; | ||||
| 
 | ||||
| /// Defines some delay functions. | ||||
| abstract class Delay { | ||||
|   /// Returns a delay based on the [attempt]. | ||||
|   static Duration getRetryDelay(int attempt) { | ||||
|     assert(attempt >= 0, 'attempt cannot be negative'); | ||||
|     if (attempt <= 0) { | ||||
|       return Duration.zero; | ||||
|     } | ||||
|     final rand = Random(); | ||||
|     const Duration delayFactor = Duration(milliseconds: 200); | ||||
|     const double randomizationFactor = 0.25; | ||||
|     const Duration maxDelay = Duration(seconds: 30); | ||||
| 
 | ||||
|     final rf = randomizationFactor * (rand.nextDouble() * 2 - 1) + 1; | ||||
|     final exp = min(attempt, 31); // prevent overflows. | ||||
|     final delay = delayFactor * pow(2.0, exp) * rf; | ||||
|     return delay < maxDelay ? delay : maxDelay; | ||||
|   } | ||||
| } | ||||
| @ -1,196 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:math'; | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/utils/convert.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/crypto.dart'; | ||||
| 
 | ||||
| /// A class for digest authentication. | ||||
| class DigestAuth { | ||||
|   // request counter | ||||
| 
 | ||||
|   DigestAuth(this.username, this.password); | ||||
|   final String username; | ||||
|   final String password; | ||||
| 
 | ||||
|   // must get from first response | ||||
|   String? _algorithm; | ||||
|   String? _qop; | ||||
|   String? _realm; | ||||
|   String? _nonce; | ||||
|   String? _opaque; | ||||
| 
 | ||||
|   int _nc = 0; | ||||
| 
 | ||||
|   /// Splits WWW-Authenticate header into a map. | ||||
|   Map<String, String>? splitWWWAuthenticateHeader(String header) { | ||||
|     if (!header.startsWith('Digest ')) { | ||||
|       throw ArgumentError.value( | ||||
|         header, | ||||
|         'header', | ||||
|         'Header must start with "Digest "', | ||||
|       ); | ||||
|     } | ||||
|     final h = header.substring(7); // remove 'Digest ' | ||||
|     final ret = <String, String>{}; | ||||
| 
 | ||||
|     final components = h.split(',').map((token) => token.trim()); | ||||
|     for (final component in components) { | ||||
|       final kv = component.split('='); | ||||
|       ret[kv[0]] = kv.getRange(1, kv.length).join('=').replaceAll('"', ''); | ||||
|     } | ||||
|     return ret; | ||||
|   } | ||||
| 
 | ||||
|   String _computeNonce() { | ||||
|     final rnd = Random.secure(); | ||||
|     final values = List<int>.generate(16, (i) => rnd.nextInt(256)); | ||||
| 
 | ||||
|     return Convert.toHex(values); | ||||
|   } | ||||
| 
 | ||||
|   String _formatNonceCount(int nc) => nc.toRadixString(16).padLeft(8, '0'); | ||||
| 
 | ||||
|   String _computeHA1( | ||||
|     String realm, | ||||
|     String? algorithm, | ||||
|     String username, | ||||
|     String password, | ||||
|     String? nonce, | ||||
|     String? cnonce, | ||||
|   ) { | ||||
|     if (algorithm == null || algorithm == 'MD5') { | ||||
|       final token1 = '$username:$realm:$password'; | ||||
|       return Crypto.md5Hash(token1); | ||||
|     } else if (algorithm == 'MD5-sess') { | ||||
|       final token1 = '$username:$realm:$password'; | ||||
|       final md51 = Crypto.md5Hash(token1); | ||||
|       final token2 = '$md51:$nonce:$cnonce'; | ||||
|       return Crypto.md5Hash(token2); | ||||
|     } else { | ||||
|       throw ArgumentError.value( | ||||
|         algorithm, | ||||
|         'algorithm', | ||||
|         'Unsupported algorithm', | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Map<String, String?> _computeResponse( | ||||
|     String method, | ||||
|     String path, | ||||
|     String body, | ||||
|     String? algorithm, | ||||
|     String? qop, | ||||
|     String? opaque, | ||||
|     String realm, | ||||
|     String? cnonce, | ||||
|     String? nonce, | ||||
|     int nc, | ||||
|     String username, | ||||
|     String password, | ||||
|   ) { | ||||
|     final ret = <String, String?>{}; | ||||
| 
 | ||||
|     final ha1 = | ||||
|         _computeHA1(realm, algorithm, username, password, nonce, cnonce); | ||||
| 
 | ||||
|     String ha2; | ||||
| 
 | ||||
|     if (qop == 'auth-int') { | ||||
|       final bodyHash = Crypto.md5Hash(body); | ||||
|       final token2 = '$method:$path:$bodyHash'; | ||||
|       ha2 = Crypto.md5Hash(token2); | ||||
|     } else { | ||||
|       // qop in [null, auth] | ||||
|       final token2 = '$method:$path'; | ||||
|       ha2 = Crypto.md5Hash(token2); | ||||
|     } | ||||
| 
 | ||||
|     final nonceCount = _formatNonceCount(nc); | ||||
|     ret['username'] = username; | ||||
|     ret['realm'] = realm; | ||||
|     ret['nonce'] = nonce; | ||||
|     ret['uri'] = path; | ||||
|     if (qop != null) { | ||||
|       ret['qop'] = qop; | ||||
|     } | ||||
|     ret['nc'] = nonceCount; | ||||
|     ret['cnonce'] = cnonce; | ||||
|     if (opaque != null) { | ||||
|       ret['opaque'] = opaque; | ||||
|     } | ||||
|     ret['algorithm'] = algorithm; | ||||
| 
 | ||||
|     if (qop == null) { | ||||
|       final token3 = '$ha1:$nonce:$ha2'; | ||||
|       ret['response'] = Crypto.md5Hash(token3); | ||||
|     } else if (qop == 'auth' || qop == 'auth-int') { | ||||
|       final token3 = '$ha1:$nonce:$nonceCount:$cnonce:$qop:$ha2'; | ||||
|       ret['response'] = Crypto.md5Hash(token3); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|   } | ||||
| 
 | ||||
|   String getAuthString(String method, Uri url) { | ||||
|     final cnonce = _computeNonce(); | ||||
|     _nc += 1; | ||||
|     // if url has query parameters, append query to path | ||||
|     final path = url.hasQuery ? '${url.path}?${url.query}' : url.path; | ||||
| 
 | ||||
|     // after the first request we have the nonce, so we can provide credentials | ||||
|     final authValues = _computeResponse( | ||||
|       method, | ||||
|       path, | ||||
|       '', | ||||
|       _algorithm, | ||||
|       _qop, | ||||
|       _opaque, | ||||
|       _realm!, | ||||
|       cnonce, | ||||
|       _nonce, | ||||
|       _nc, | ||||
|       username, | ||||
|       password, | ||||
|     ); | ||||
|     final authValuesString = authValues.entries | ||||
|         .where((e) => e.value != null) | ||||
|         .map((e) => [e.key, '="', e.value, '"'].join()) | ||||
|         .toList() | ||||
|         .join(', '); | ||||
|     final authString = 'Digest $authValuesString'; | ||||
|     return authString; | ||||
|   } | ||||
| 
 | ||||
|   void initFromAuthenticateHeader(String? authInfo) { | ||||
|     if (authInfo == null) { | ||||
|       throw ArgumentError.notNull('authInfo'); | ||||
|     } | ||||
|     final values = splitWWWAuthenticateHeader(authInfo); | ||||
|     if (values != null) { | ||||
|       _algorithm = values['algorithm'] ?? _algorithm; | ||||
|       _qop = values['qop'] ?? _qop; | ||||
|       _realm = values['realm'] ?? _realm; | ||||
|       _nonce = values['nonce'] ?? _nonce; | ||||
|       _opaque = values['opaque'] ?? _opaque; | ||||
|       _nc = 0; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool isReady() => _nonce != null && (_nc == 0 || _qop != null); | ||||
| } | ||||
| @ -1,27 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Defines some header keys. | ||||
| abstract class HeaderKeys { | ||||
|   /// The `Authorization` header key. | ||||
|   static const String authorization = 'Authorization'; | ||||
| 
 | ||||
|   /// The `WWW-Authenticate` header key. | ||||
|   static const String wwwAuthenticate = 'WWW-Authenticate'; | ||||
| 
 | ||||
|   /// The `Content-Type` header key. | ||||
|   static const String contentType = 'Content-Type'; | ||||
| } | ||||
| @ -1,32 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Defines http verb methods. | ||||
| enum HttpMethods { | ||||
|   head('HEAD'), | ||||
|   get('GET'), | ||||
|   post('POST'), | ||||
|   put('PUT'), | ||||
|   patch('PATCH'), | ||||
|   delete('DELETE'); | ||||
| 
 | ||||
|   const HttpMethods(this.method); | ||||
| 
 | ||||
|   /// Returns the method of the http verb. | ||||
|   /// | ||||
|   /// For example, the method of [HttpMethods.get] is `GET`. | ||||
|   final String method; | ||||
| } | ||||
| @ -1,116 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Status codes for HTTP responses. Extracted from dart:io | ||||
| enum HttpStatus { | ||||
|   continue_(100), | ||||
|   switchingProtocols(101), | ||||
|   processing(102), | ||||
|   ok(200), | ||||
|   created(201), | ||||
|   accepted(202), | ||||
|   nonAuthoritativeInformation(203), | ||||
|   noContent(204), | ||||
|   resetContent(205), | ||||
|   partialContent(206), | ||||
|   multiStatus(207), | ||||
|   alreadyReported(208), | ||||
|   imUsed(226), | ||||
|   multipleChoices(300), | ||||
|   movedPermanently(301), | ||||
|   found(302), | ||||
|   movedTemporarily(302), // Common alias for found. | ||||
|   seeOther(303), | ||||
|   notModified(304), | ||||
|   useProxy(305), | ||||
|   temporaryRedirect(307), | ||||
|   permanentRedirect(308), | ||||
|   badRequest(400), | ||||
|   unauthorized(401), | ||||
|   paymentRequired(402), | ||||
|   forbidden(403), | ||||
|   notFound(404), | ||||
|   methodNotAllowed(405), | ||||
|   notAcceptable(406), | ||||
|   proxyAuthenticationRequired(407), | ||||
|   requestTimeout(408), | ||||
|   conflict(409), | ||||
|   gone(410), | ||||
|   lengthRequired(411), | ||||
|   preconditionFailed(412), | ||||
|   requestEntityTooLarge(413), | ||||
|   requestUriTooLong(414), | ||||
|   unsupportedMediaType(415), | ||||
|   requestedRangeNotSatisfiable(416), | ||||
|   expectationFailed(417), | ||||
|   misdirectedRequest(421), | ||||
|   unprocessableEntity(422), | ||||
|   locked(423), | ||||
|   failedDependency(424), | ||||
|   upgradeRequired(426), | ||||
|   preconditionRequired(428), | ||||
|   tooManyRequests(429), | ||||
|   requestHeaderFieldsTooLarge(431), | ||||
|   connectionClosedWithoutResponse(444), | ||||
|   unavailableForLegalReasons(451), | ||||
|   clientClosedRequest(499), | ||||
|   internalServerError(500), | ||||
|   notImplemented(501), | ||||
|   badGateway(502), | ||||
|   serviceUnavailable(503), | ||||
|   gatewayTimeout(504), | ||||
|   httpVersionNotSupported(505), | ||||
|   variantAlsoNegotiates(506), | ||||
|   insufficientStorage(507), | ||||
|   loopDetected(508), | ||||
|   notExtended(510), | ||||
|   networkAuthenticationRequired(511), | ||||
|   // Client generated status code. | ||||
|   networkConnectTimeoutError(599); | ||||
| 
 | ||||
|   const HttpStatus(this.statusCode); | ||||
| 
 | ||||
|   /// Returns the [HttpStatus] with the given [statusCode]. | ||||
|   factory HttpStatus.from(int status) => | ||||
|       HttpStatus.values.firstWhere((element) => element.statusCode == status); | ||||
| 
 | ||||
|   final int statusCode; | ||||
| 
 | ||||
|   bool equals(Object other) { | ||||
|     if (other is HttpStatus) { | ||||
|       return statusCode == other.statusCode; | ||||
|     } | ||||
|     if (other is int) { | ||||
|       return statusCode == other; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   /// Checks if the status code is in the range of 100-199. | ||||
|   bool isInfo() => statusCode >= 100 && statusCode < 200; | ||||
| 
 | ||||
|   /// Checks if the status code is in the range of 200-299. | ||||
|   bool isSuccess() => statusCode >= 200 && statusCode < 300; | ||||
| 
 | ||||
|   /// Checks if the status code is in the range of 300-399. | ||||
|   bool isRedirection() => statusCode >= 300 && statusCode < 400; | ||||
| 
 | ||||
|   /// Checks if the status code is in the range of 400-499. | ||||
|   bool isClientError() => statusCode >= 400 && statusCode < 500; | ||||
| 
 | ||||
|   /// Checks if the status code is in the range of 500-599. | ||||
|   bool isServerError() => statusCode >= 500 && statusCode < 600; | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Defines few protocols | ||||
| enum Protocols { | ||||
|   http, | ||||
|   https; | ||||
| 
 | ||||
|   /// Returns the scheme of the protocol. | ||||
|   /// | ||||
|   /// For example, the scheme of [Protocols.http] is `http://`. | ||||
|   String get scheme => '$name://'; | ||||
| } | ||||
| @ -1,95 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:http/http.dart'; | ||||
| 
 | ||||
| /// Defines some request utils. | ||||
| abstract class RequestUtils { | ||||
|   static Request _copyNormalRequestWith( | ||||
|     Request original, { | ||||
|     String? method, | ||||
|     Uri? url, | ||||
|     Map<String, String>? headers, | ||||
|     int? maxRedirects, | ||||
|     bool? followRedirects, | ||||
|     bool? persistentConnection, | ||||
|     String? body, | ||||
|   }) { | ||||
|     final request = Request(method ?? original.method, url ?? original.url) | ||||
|       ..followRedirects = followRedirects ?? original.followRedirects | ||||
|       ..headers.addAll(headers ?? original.headers) | ||||
|       ..maxRedirects = maxRedirects ?? original.maxRedirects | ||||
|       ..persistentConnection = | ||||
|           persistentConnection ?? original.persistentConnection | ||||
|       ..body = body ?? original.body; | ||||
| 
 | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   /// Copies the given [original] request and returns a new request with the | ||||
|   /// given [method], [url], [headers], [maxRedirects], [followRedirects], | ||||
|   /// [persistentConnection] and [body]. | ||||
|   static BaseRequest copyRequestWith( | ||||
|     BaseRequest original, { | ||||
|     String? method, | ||||
|     Uri? url, | ||||
|     Map<String, String>? headers, | ||||
|     int? maxRedirects, | ||||
|     bool? followRedirects, | ||||
|     bool? persistentConnection, | ||||
|     String? body, | ||||
|   }) { | ||||
|     if (original is Request) { | ||||
|       return _copyNormalRequestWith( | ||||
|         original, | ||||
|         method: method, | ||||
|         url: url, | ||||
|         headers: headers, | ||||
|         maxRedirects: maxRedirects, | ||||
|         followRedirects: followRedirects, | ||||
|         persistentConnection: persistentConnection, | ||||
|         body: body, | ||||
|       ); | ||||
|     } else { | ||||
|       throw UnimplementedError( | ||||
|         'Cannot handle requests of type ${original.runtimeType}', | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static Request _copyNormalRequest(Request original) { | ||||
|     final request = Request(original.method, original.url) | ||||
|       ..followRedirects = original.followRedirects | ||||
|       ..headers.addAll(original.headers) | ||||
|       ..maxRedirects = original.maxRedirects | ||||
|       ..persistentConnection = original.persistentConnection | ||||
|       ..body = original.body; | ||||
| 
 | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   /// Copies the given [original] request and returns a new request. | ||||
|   /// This method is useful when you want to modify the request | ||||
|   static BaseRequest copyRequest(BaseRequest original) { | ||||
|     if (original is Request) { | ||||
|       return _copyNormalRequest(original); | ||||
|     } else { | ||||
|       throw UnimplementedError( | ||||
|         'Cannot handle requests of type ${original.runtimeType}', | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| export 'authentication_methods.dart'; | ||||
| export 'digest_auth.dart'; | ||||
| export 'header_keys.dart'; | ||||
| export 'http_methods.dart'; | ||||
| export 'http_status.dart'; | ||||
| export 'protocols.dart'; | ||||
| export 'request_utils.dart'; | ||||
| @ -1,24 +0,0 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| library wyatt_http_client; | ||||
| 
 | ||||
| export 'src/middleware.dart'; | ||||
| export 'src/middleware_client.dart'; | ||||
| export 'src/middlewares/middlewares.dart'; | ||||
| export 'src/models/models.dart'; | ||||
| export 'src/pipeline.dart'; | ||||
| export 'src/utils/utils.dart'; | ||||
| @ -1,18 +0,0 @@ | ||||
| name: wyatt_http_client | ||||
| description: A Dart client for RESTful APIs with authentication. | ||||
| repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_http_client | ||||
| version: 2.0.1 | ||||
| 
 | ||||
| publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub | ||||
| 
 | ||||
| environment: | ||||
|   sdk: '>=2.17.0 <3.0.0' | ||||
| 
 | ||||
| dependencies:  | ||||
|   crypto: ^3.0.2 | ||||
|   http: ^1.1.0 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   wyatt_analysis: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub | ||||
|     version: ^2.5.0 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user