Compare commits
	
		
			5 Commits
		
	
	
		
			9e9ec9ba9b
			...
			a7204f588f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a7204f588f | |||
| 81367e5f3a | |||
| 14cb3485e4 | |||
| 58d3b70bee | |||
| ea4a30ca00 | 
| @ -1,39 +1,182 @@ | ||||
| <!-- | ||||
| This README describes the package. If you publish this package to pub.dev, | ||||
| this README's contents appear on the landing page for your package. | ||||
|  * Copyright (C) 2022 WYATT GROUP | ||||
|  * Please see the AUTHORS file for details. | ||||
| 
 | ||||
| For information about how to write a good package README, see the guide for | ||||
| [writing package pages](https://dart.dev/guides/libraries/writing-package-pages).  | ||||
|  * 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. | ||||
| 
 | ||||
| For general information about developing packages, see the Dart guide for | ||||
| [creating packages](https://dart.dev/guides/libraries/create-library-packages) | ||||
| and the Flutter guide for | ||||
| [developing packages and plugins](https://flutter.dev/developing-packages).  | ||||
|  * 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/>. | ||||
| --> | ||||
| 
 | ||||
| TODO: Put a short description of the package here that helps potential users | ||||
| know whether this package might be useful for them. | ||||
| # Dart - HTTP Client | ||||
| 
 | ||||
| ## Features | ||||
| <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> | ||||
| 
 | ||||
| TODO: List what your package can do. Maybe include images, gifs, or videos. | ||||
| HTTP Client for Dart with Middlewares ! | ||||
| 
 | ||||
| ## Getting started | ||||
| 
 | ||||
| TODO: List prerequisites and provide or point to information on how to | ||||
| start using the package. | ||||
| Simply add wyatt_http_client in pubspec.yaml, then | ||||
| 
 | ||||
| ```dart | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| TODO: Include short and useful examples for package users. Add longer examples | ||||
| to `/example` folder.  | ||||
| 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 | ||||
| const like = 'sample'; | ||||
| // Create the Pipeline | ||||
| final Pipeline pipeline = Pipeline() | ||||
|     .addMiddleware( | ||||
|         UriPrefixMiddleware( | ||||
|             protocol: Protocols.http, | ||||
|             authority: 'localhost:80', | ||||
|         ), | ||||
|     ) | ||||
|     .addMiddleware(SimpleLoggerMiddleware()); | ||||
| ``` | ||||
| 
 | ||||
| ## Additional information | ||||
| Then if you print the pipeline,  | ||||
| 
 | ||||
| TODO: Tell users more about the package: where to find more information, how to  | ||||
| contribute to the package, how to file issues, what response they can expect  | ||||
| from the package authors, and more. | ||||
| ``` | ||||
| [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( | ||||
|         UriPrefixMiddleware( | ||||
|             protocol: Protocols.http, | ||||
|             authority: 'localhost:80', | ||||
|         ), | ||||
|     ) | ||||
|     .addMiddleware(BodyToJsonMiddleware()) | ||||
|     .addMiddleware( | ||||
|         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( | ||||
|         UriPrefixMiddleware( | ||||
|             protocol: Protocols.http, | ||||
|             authority: 'localhost:80', | ||||
|         ), | ||||
|     ) | ||||
|     .addMiddleware(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(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 { | ||||
|    | ||||
|   @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; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| @ -17,14 +17,7 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/authentication/basic_authentication_client.dart'; | ||||
| import 'package:wyatt_http_client/src/authentication/bearer_authentication_client.dart'; | ||||
| import 'package:wyatt_http_client/src/authentication/digest_authentication_client.dart'; | ||||
| import 'package:wyatt_http_client/src/authentication/refresh_token_client.dart'; | ||||
| import 'package:wyatt_http_client/src/authentication/unsafe_authentication_client.dart'; | ||||
| import 'package:wyatt_http_client/src/rest_client.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/protocols.dart'; | ||||
| import 'package:wyatt_http_client/wyatt_http_client.dart'; | ||||
| 
 | ||||
| String lastToken = ''; | ||||
| int token = 0; | ||||
| @ -42,7 +35,7 @@ Future<void> handleBasic(HttpRequest req) async { | ||||
| 
 | ||||
| Future<void> handleBasicNegotiate(HttpRequest req) async { | ||||
|   if (req.headers.value('Authorization') == null) { | ||||
|     req.response.statusCode = HttpStatus.unauthorized; | ||||
|     req.response.statusCode = HttpStatus.unauthorized.statusCode; | ||||
|     req.response.headers.set(HeaderKeys.wwwAuthenticate, 'Basic realm="Wyatt"'); | ||||
|     print(req.response.headers.value('WWW-Authenticate')); | ||||
|     return req.response.close(); | ||||
| @ -56,7 +49,7 @@ Future<void> handleBearer(HttpRequest req) async { | ||||
| 
 | ||||
| Future<void> handleDigest(HttpRequest req) async { | ||||
|   if (req.headers.value('Authorization') == null) { | ||||
|     req.response.statusCode = HttpStatus.unauthorized; | ||||
|     req.response.statusCode = HttpStatus.unauthorized.statusCode; | ||||
|     req.response.headers.set( | ||||
|       'WWW-Authenticate', | ||||
|       'Digest realm="Wyatt", ' | ||||
| @ -110,7 +103,7 @@ Future<void> handleOauth2RefreshToken(HttpRequest req) async { | ||||
|         return req.response.close(); | ||||
|       } else { | ||||
|         lastToken = receivedToken; | ||||
|         req.response.statusCode = HttpStatus.unauthorized; | ||||
|         req.response.statusCode = HttpStatus.unauthorized.statusCode; | ||||
|         return req.response.close(); | ||||
|       } | ||||
|     default: | ||||
| @ -160,13 +153,13 @@ Future<void> server() async { | ||||
|           print('Authorized'); | ||||
|           error = 0; | ||||
|         } else { | ||||
|           request.response.statusCode = HttpStatus.unauthorized; | ||||
|           request.response.statusCode = HttpStatus.unauthorized.statusCode; | ||||
|         } | ||||
|         break; | ||||
|       case '/test/oauth2-test-timeout': | ||||
|         error++; | ||||
|         print('Error $error'); | ||||
|         request.response.statusCode = HttpStatus.unauthorized; | ||||
|         request.response.statusCode = HttpStatus.unauthorized.statusCode; | ||||
|         break; | ||||
|       case '/test/oauth2-login': | ||||
|         if (request.method == 'POST') { | ||||
| @ -189,12 +182,12 @@ Future<void> server() async { | ||||
|         } | ||||
|         break; | ||||
|       case '/test/oauth2-refresh-error': | ||||
|         request.response.statusCode = HttpStatus.unauthorized; | ||||
|         request.response.statusCode = HttpStatus.unauthorized.statusCode; | ||||
|         break; | ||||
| 
 | ||||
|       default: | ||||
|         print(' => Unknown path or method'); | ||||
|         request.response.statusCode = HttpStatus.notFound; | ||||
|         request.response.statusCode = HttpStatus.notFound.statusCode; | ||||
|     } | ||||
|     request.response.close(); | ||||
|     print('===================='); | ||||
| @ -204,73 +197,98 @@ Future<void> server() async { | ||||
| Future<void> main() async { | ||||
|   unawaited(server()); | ||||
|   final base = 'localhost:8080'; | ||||
|   final restClient = RestClient(protocol: Protocols.http, authority: base); | ||||
|   final uriPrefix = UriPrefixMiddleware( | ||||
|     protocol: Protocols.http, | ||||
|     authority: base, | ||||
|   ); | ||||
|   final jsonEncoder = BodyToJsonMiddleware(); | ||||
|   final logger = SimpleLoggerMiddleware(); | ||||
| 
 | ||||
|   // Basic | ||||
|   final basic = BasicAuthenticationClient( | ||||
|   final basicAuth = BasicAuthMiddleware( | ||||
|     username: 'username', | ||||
|     password: 'password', | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   final basic = MiddlewareClient( | ||||
|     pipeline: Pipeline.fromIterable([ | ||||
|       uriPrefix, | ||||
|       basicAuth, | ||||
|       logger, | ||||
|     ]), | ||||
|   ); | ||||
|   await basic.get(Uri.parse('/test/basic-test')); | ||||
| 
 | ||||
|   // Basic with negotiate | ||||
|   final basicWithNegotiate = BasicAuthenticationClient( | ||||
|     username: 'username', | ||||
|     password: 'password', | ||||
|     preemptive: false, | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   await basicWithNegotiate.get(Uri.parse('/test/basic-test-with-negotiate')); | ||||
| 
 | ||||
|   // Digest | ||||
|   final digest = DigestAuthenticationClient( | ||||
|   final digestAuth = DigestAuthMiddleware( | ||||
|     username: 'Mufasa', | ||||
|     password: 'Circle Of Life', | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   final digest = MiddlewareClient( | ||||
|     pipeline: Pipeline.fromIterable([ | ||||
|       uriPrefix, | ||||
|       digestAuth, | ||||
|       logger, | ||||
|     ]), | ||||
|   ); | ||||
|   await digest.get(Uri.parse('/test/digest-test')); | ||||
| 
 | ||||
|   // Bearer | ||||
|   final bearer = BearerAuthenticationClient( | ||||
|     token: 'access-token-test', | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   await bearer.get(Uri.parse('/test/bearer-test')); | ||||
|   // // Bearer | ||||
|   // final bearer = BearerAuthenticationClient( | ||||
|   //   token: 'access-token-test', | ||||
|   //   inner: restClient, | ||||
|   // ); | ||||
|   // await bearer.get(Uri.parse('/test/bearer-test')); | ||||
| 
 | ||||
|   // API Key | ||||
|   final apiKey = BearerAuthenticationClient( | ||||
|     token: 'awesome-api-key', | ||||
|     authenticationMethod: 'ApiKey', | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   await apiKey.get(Uri.parse('/test/apikey-test')); | ||||
|   // // API Key | ||||
|   // final apiKey = BearerAuthenticationClient( | ||||
|   //   token: 'awesome-api-key', | ||||
|   //   authenticationMethod: 'ApiKey', | ||||
|   //   inner: restClient, | ||||
|   // ); | ||||
|   // await apiKey.get(Uri.parse('/test/apikey-test')); | ||||
| 
 | ||||
|   // Unsafe URL | ||||
|   final unsafe = UnsafeAuthenticationClient( | ||||
|   final unsafeAuth = UnsafeAuthMiddleware( | ||||
|     username: 'Mufasa', | ||||
|     password: 'Circle Of Life', | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   final unsafe = MiddlewareClient( | ||||
|     pipeline: Pipeline.fromIterable([ | ||||
|       uriPrefix, | ||||
|       unsafeAuth, | ||||
|       logger, | ||||
|     ]), | ||||
|   ); | ||||
|   await unsafe.get(Uri.parse('/test/unsafe-test')); | ||||
| 
 | ||||
|   // OAuth2 | ||||
|   final refreshToken = RefreshTokenClient( | ||||
|   final refreshTokenAuth = RefreshTokenAuthMiddleware( | ||||
|     authorizationEndpoint: '/test/oauth2-test?action=login', | ||||
|     tokenEndpoint: '/test/oauth2-test?action=refresh', | ||||
|     accessTokenParser: (body) => body['accessToken']! as String, | ||||
|     refreshTokenParser: (body) => body['refreshToken']! as String, | ||||
|     inner: restClient, | ||||
|   ); | ||||
|   final refreshToken = MiddlewareClient( | ||||
|     pipeline: Pipeline.fromIterable([ | ||||
|       uriPrefix, | ||||
|       jsonEncoder, | ||||
|       refreshTokenAuth, | ||||
|       logger, | ||||
|     ]), | ||||
|   ); | ||||
|   await refreshToken.get(Uri.parse('/test/oauth2-test')); | ||||
|   await refreshToken.authorize(<String, String>{ | ||||
|   // Login | ||||
|   await refreshToken.post( | ||||
|     Uri.parse('/test/oauth2-test'), | ||||
|     body: <String, String>{ | ||||
|       'username': 'username', | ||||
|       'password': 'password', | ||||
|   }); | ||||
|     }, | ||||
|   ); | ||||
|   await refreshToken.get(Uri.parse('/test/oauth2-test')); | ||||
|   await refreshToken.refresh(); | ||||
|   await refreshToken.get(Uri.parse('/test/oauth2-test')); | ||||
|   await refreshToken.get(Uri.parse('/test/oauth2-test?action=access-denied')); | ||||
|   // await refreshToken.refresh(); | ||||
|   // await refreshToken.get(Uri.parse('/test/oauth2-test')); | ||||
|   // await refreshToken.get(Uri.parse('/test/oauth2-test?action=access-denied')); | ||||
| 
 | ||||
|   exit(0); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,398 @@ | ||||
| // 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/>. | ||||
| 
 | ||||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||
| import 'dart:convert'; | ||||
| 
 | ||||
| import 'package:wyatt_http_client/src/middleware_client.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/refresh_token_auth_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/pipeline.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/protocols.dart'; | ||||
| 
 | ||||
| enum EmailVerificationAction { | ||||
|   signUp, | ||||
|   resetPassword, | ||||
|   changeEmail; | ||||
| 
 | ||||
|   String toSnakeCase() { | ||||
|     return name.splitMapJoin( | ||||
|       RegExp('[A-Z]'), | ||||
|       onMatch: (m) => '_${m[0]?.toLowerCase()}', | ||||
|       onNonMatch: (n) => n, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   factory EmailVerificationAction.fromString(String str) { | ||||
|     return EmailVerificationAction.values.firstWhere( | ||||
|       (EmailVerificationAction element) => element.toSnakeCase() == str, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class VerifyCode { | ||||
|   final String email; | ||||
|   final String verificationCode; | ||||
|   final EmailVerificationAction action; | ||||
|   VerifyCode({ | ||||
|     required this.email, | ||||
|     required this.verificationCode, | ||||
|     required this.action, | ||||
|   }); | ||||
| 
 | ||||
|   VerifyCode copyWith({ | ||||
|     String? email, | ||||
|     String? verificationCode, | ||||
|     EmailVerificationAction? action, | ||||
|   }) { | ||||
|     return VerifyCode( | ||||
|       email: email ?? this.email, | ||||
|       verificationCode: verificationCode ?? this.verificationCode, | ||||
|       action: action ?? this.action, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return <String, dynamic>{ | ||||
|       'email': email, | ||||
|       'verification_code': verificationCode, | ||||
|       'action': action.toSnakeCase(), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   factory VerifyCode.fromMap(Map<String, dynamic> map) { | ||||
|     return VerifyCode( | ||||
|       email: map['email'] as String, | ||||
|       verificationCode: map['verification_code'] as String, | ||||
|       action: EmailVerificationAction.fromString(map['action'] as String), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String toJson() => json.encode(toMap()); | ||||
| 
 | ||||
|   factory VerifyCode.fromJson(String source) => | ||||
|       VerifyCode.fromMap(json.decode(source) as Map<String, dynamic>); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => | ||||
|       'VerifyCode(email: $email, verificationCode: $verificationCode, action: $action)'; | ||||
| } | ||||
| 
 | ||||
| class Account { | ||||
|   final String email; | ||||
|   final String? sessionId; | ||||
|   Account({ | ||||
|     required this.email, | ||||
|     this.sessionId, | ||||
|   }); | ||||
| 
 | ||||
|   Account copyWith({ | ||||
|     String? email, | ||||
|     String? sessionId, | ||||
|   }) { | ||||
|     return Account( | ||||
|       email: email ?? this.email, | ||||
|       sessionId: sessionId ?? this.sessionId, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return <String, dynamic>{ | ||||
|       'email': email, | ||||
|       'session_id': sessionId, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   factory Account.fromMap(Map<String, dynamic> map) { | ||||
|     return Account( | ||||
|       email: map['email'] as String, | ||||
|       sessionId: map['session_id'] != null ? map['session_id'] as String : null, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String toJson() => json.encode(toMap()); | ||||
| 
 | ||||
|   factory Account.fromJson(String source) => | ||||
|       Account.fromMap(json.decode(source) as Map<String, dynamic>); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'Account(email: $email, sessionId: $sessionId)'; | ||||
| } | ||||
| 
 | ||||
| class SignUp { | ||||
|   final String sessionId; | ||||
|   final String password; | ||||
|   SignUp({ | ||||
|     required this.sessionId, | ||||
|     required this.password, | ||||
|   }); | ||||
| 
 | ||||
|   SignUp copyWith({ | ||||
|     String? sessionId, | ||||
|     String? password, | ||||
|   }) { | ||||
|     return SignUp( | ||||
|       sessionId: sessionId ?? this.sessionId, | ||||
|       password: password ?? this.password, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return <String, dynamic>{ | ||||
|       'session_id': sessionId, | ||||
|       'password': password, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   factory SignUp.fromMap(Map<String, dynamic> map) { | ||||
|     return SignUp( | ||||
|       sessionId: map['session_id'] as String, | ||||
|       password: map['password'] as String, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String toJson() => json.encode(toMap()); | ||||
| 
 | ||||
|   factory SignUp.fromJson(String source) => | ||||
|       SignUp.fromMap(json.decode(source) as Map<String, dynamic>); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'SignUp(sessionId: $sessionId, password: $password)'; | ||||
| } | ||||
| 
 | ||||
| class TokenSuccess { | ||||
|   final String accessToken; | ||||
|   final String refreshToken; | ||||
|   final Account account; | ||||
|   TokenSuccess({ | ||||
|     required this.accessToken, | ||||
|     required this.refreshToken, | ||||
|     required this.account, | ||||
|   }); | ||||
| 
 | ||||
|   TokenSuccess copyWith({ | ||||
|     String? accessToken, | ||||
|     String? refreshToken, | ||||
|     Account? account, | ||||
|   }) { | ||||
|     return TokenSuccess( | ||||
|       accessToken: accessToken ?? this.accessToken, | ||||
|       refreshToken: refreshToken ?? this.refreshToken, | ||||
|       account: account ?? this.account, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return <String, dynamic>{ | ||||
|       'access_token': accessToken, | ||||
|       'refresh_token': refreshToken, | ||||
|       'account': account.toMap(), | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   factory TokenSuccess.fromMap(Map<String, dynamic> map) { | ||||
|     return TokenSuccess( | ||||
|       accessToken: map['access_token'] as String, | ||||
|       refreshToken: map['refresh_token'] as String, | ||||
|       account: Account.fromMap(map['account'] as Map<String, dynamic>), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String toJson() => json.encode(toMap()); | ||||
| 
 | ||||
|   factory TokenSuccess.fromJson(String source) => | ||||
|       TokenSuccess.fromMap(json.decode(source) as Map<String, dynamic>); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => | ||||
|       'TokenSuccess(accessToken: $accessToken, refreshToken: $refreshToken, account: $account)'; | ||||
| } | ||||
| 
 | ||||
| class Login { | ||||
|   final String email; | ||||
|   final String password; | ||||
|   Login({ | ||||
|     required this.email, | ||||
|     required this.password, | ||||
|   }); | ||||
| 
 | ||||
|   Login copyWith({ | ||||
|     String? email, | ||||
|     String? password, | ||||
|   }) { | ||||
|     return Login( | ||||
|       email: email ?? this.email, | ||||
|       password: password ?? this.password, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return <String, dynamic>{ | ||||
|       'email': email, | ||||
|       'password': password, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   factory Login.fromMap(Map<String, dynamic> map) { | ||||
|     return Login( | ||||
|       email: map['email'] as String, | ||||
|       password: map['password'] as String, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   String toJson() => json.encode(toMap()); | ||||
| 
 | ||||
|   factory Login.fromJson(String source) => | ||||
|       Login.fromMap(json.decode(source) as Map<String, dynamic>); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'Login(email: $email, password: $password)'; | ||||
| } | ||||
| 
 | ||||
| class FastAPI { | ||||
|   final String baseUrl; | ||||
|   final MiddlewareClient client; | ||||
|   final int apiVersion; | ||||
| 
 | ||||
|   FastAPI({ | ||||
|     this.baseUrl = 'localhost:80', | ||||
|     MiddlewareClient? client, | ||||
|     this.apiVersion = 1, | ||||
|   }) : client = client ?? MiddlewareClient(); | ||||
| 
 | ||||
|   String get apiPath => '/api/v$apiVersion'; | ||||
| 
 | ||||
|   Future<void> sendSignUpCode(String email) async { | ||||
|     final r = await client.post( | ||||
|       Uri.parse('$apiPath/auth/send-sign-up-code'), | ||||
|       body: <String, String>{ | ||||
|         'email': email, | ||||
|       }, | ||||
|     ); | ||||
|     if (r.statusCode != 201) { | ||||
|       throw Exception('Invalid reponse: ${r.statusCode}'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Account> verifyCode(VerifyCode verifyCode) async { | ||||
|     final r = await client.post( | ||||
|       Uri.parse('$apiPath/auth/verify-code'), | ||||
|       body: verifyCode.toMap(), | ||||
|     ); | ||||
|     if (r.statusCode != 202) { | ||||
|       throw Exception('Invalid reponse: ${r.statusCode}'); | ||||
|     } else { | ||||
|       return Account.fromMap( | ||||
|         (jsonDecode(r.body) as Map<String, dynamic>)['account'] | ||||
|             as Map<String, dynamic>, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<Account> signUp(SignUp signUp) async { | ||||
|     final r = await client.post( | ||||
|       Uri.parse('$apiPath/auth/sign-up'), | ||||
|       body: signUp.toMap(), | ||||
|     ); | ||||
|     if (r.statusCode != 201) { | ||||
|       throw Exception('Invalid reponse: ${r.statusCode}'); | ||||
|     } else { | ||||
|       return Account.fromJson(r.body); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Future<TokenSuccess> signInWithPassword(Login login) async { | ||||
|     final r = await client.post( | ||||
|       Uri.parse('$apiPath/auth/sign-in-with-password'), | ||||
|       body: login.toMap(), | ||||
|     ); | ||||
|     if (r.statusCode != 200) { | ||||
|       throw Exception('Invalid reponse: ${r.statusCode}'); | ||||
|     } else { | ||||
|       return TokenSuccess.fromJson(r.body); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Future<TokenSuccess> refresh() async { | ||||
|   //   final r = await client.refresh(); | ||||
|   //   return TokenSuccess.fromJson(r?.body ?? ''); | ||||
|   // } | ||||
| 
 | ||||
|   Future<List<Account>> getAccountList() async { | ||||
|     final r = await client.get( | ||||
|       Uri.parse('$apiPath/account'), | ||||
|     ); | ||||
|     if (r.statusCode != 200) { | ||||
|       throw Exception('Invalid reponse: ${r.statusCode}'); | ||||
|     } else { | ||||
|       final list = (jsonDecode(r.body) as Map<String, dynamic>)['founds'] | ||||
|           as List<Map<String, dynamic>>; | ||||
|       final result = <Account>[]; | ||||
|       for (final element in list) { | ||||
|         result.add(Account.fromMap(element)); | ||||
|       } | ||||
|       return result; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void main(List<String> args) async { | ||||
|   final Pipeline pipeline = Pipeline() | ||||
|       .addMiddleware( | ||||
|         UriPrefixMiddleware( | ||||
|           protocol: Protocols.http, | ||||
|           authority: 'localhost:80', | ||||
|         ), | ||||
|       ) | ||||
|       .addMiddleware(BodyToJsonMiddleware()) | ||||
|       .addMiddleware( | ||||
|         RefreshTokenAuthMiddleware( | ||||
|           authorizationEndpoint: '/api/v1/auth/sign-in-with-password', | ||||
|           tokenEndpoint: '/api/v1/auth/refresh', | ||||
|           accessTokenParser: (body) => body['access_token']! as String, | ||||
|           refreshTokenParser: (body) => body['refresh_token']! as String, | ||||
|           unauthorized: HttpStatus.forbidden, | ||||
|         ), | ||||
|       ) | ||||
|       .addMiddleware(SimpleLoggerMiddleware()); | ||||
| 
 | ||||
|   print(pipeline); | ||||
|   final client = MiddlewareClient(pipeline: pipeline); | ||||
| 
 | ||||
|   final api = FastAPI( | ||||
|     client: client, | ||||
|   ); | ||||
| 
 | ||||
|   await api.sendSignUpCode('git@pcl.ovh'); | ||||
|   final verifiedAccount = await api.verifyCode( | ||||
|     VerifyCode( | ||||
|       email: 'git@pcl.ovh', | ||||
|       verificationCode: '000000000', | ||||
|       action: EmailVerificationAction.signUp, | ||||
|     ), | ||||
|   ); | ||||
|   final registeredAccount = await api.signUp( | ||||
|     SignUp(sessionId: verifiedAccount.sessionId ?? '', password: 'password'), | ||||
|   ); | ||||
|   final signedInAccount = await api.signInWithPassword( | ||||
|     Login(email: 'git@pcl.ovh', password: 'password'), | ||||
|   ); | ||||
|   final accountList = await api.getAccountList(); | ||||
|   print(accountList); | ||||
| } | ||||
							
								
								
									
										162
									
								
								packages/wyatt_http_client/example/pipeline.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								packages/wyatt_http_client/example/pipeline.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,162 @@ | ||||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||
| // 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/middlewares/body_to_json_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/unsafe_auth_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart'; | ||||
| import 'package:wyatt_http_client/src/pipeline.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/protocols.dart'; | ||||
| 
 | ||||
| // class RequestMutatorMiddleware implements Middleware { | ||||
| //   @override | ||||
| //   Middleware? parent; | ||||
| 
 | ||||
| //   @override | ||||
| //   Middleware? child; | ||||
| 
 | ||||
| //   RequestMutatorMiddleware({ | ||||
| //     this.parent, | ||||
| //     this.child, | ||||
| //   }); | ||||
| 
 | ||||
| //   @override | ||||
| //   BaseRequest onRequest(BaseRequest request) { | ||||
| //     print('RequestMutator::OnRequest: ${request.method} -> ${request.url}'); | ||||
| //     return child?.onRequest(request) ?? request; | ||||
| //   } | ||||
| 
 | ||||
| //   @override | ||||
| //   BaseResponse onResponse(BaseResponse response) { | ||||
| //     final res = child?.onResponse(response) ?? response; | ||||
| //     print( | ||||
| //       'RequestMutator::OnResponse: ${res.statusCode} -> ${res.contentLength} bytes', | ||||
| //     ); | ||||
| //     return res; | ||||
| //   } | ||||
| // } | ||||
| 
 | ||||
| // typedef Middleware = Handler Function(Handler innerHandler); | ||||
| 
 | ||||
| // Middleware createMiddleware({ | ||||
| //   FutureOr<Response?> Function(Request)? requestHandler, | ||||
| //   FutureOr<Response> Function(Response)? responseHandler, | ||||
| //   FutureOr<Response> Function(Object error, StackTrace)? errorHandler, | ||||
| // }) { | ||||
| //   requestHandler ??= (request) => null; | ||||
| //   responseHandler ??= (response) => response; | ||||
| 
 | ||||
| //   FutureOr<Response> Function(Object, StackTrace)? onError; | ||||
| //   if (errorHandler != null) { | ||||
| //     onError = (error, stackTrace) { | ||||
| //       if (error is Exception) throw error; | ||||
| //       return errorHandler(error, stackTrace); | ||||
| //     }; | ||||
| //   } | ||||
| 
 | ||||
| //   return (Handler innerHandler) { | ||||
| //     return (request) { | ||||
| //       return Future.sync(() => requestHandler!(request)).then((response) { | ||||
| //         if (response != null) return response; | ||||
| 
 | ||||
| //         return Future.sync(() => innerHandler(request)) | ||||
| //             .then((response) => responseHandler!(response), onError: onError); | ||||
| //       }); | ||||
| //     }; | ||||
| //   }; | ||||
| // } | ||||
| 
 | ||||
| // extension MiddlewareX on Middleware { | ||||
| //   Middleware addMiddleware(Middleware other) => | ||||
| //       (Handler handler) => this(other(handler)); | ||||
| //   Handler addHandler(Handler handler) => this(handler); | ||||
| // } | ||||
| 
 | ||||
| // typedef Handler = FutureOr<Response> Function(Request request); | ||||
| 
 | ||||
| // final headerMutator = createMiddleware( | ||||
| //   responseHandler: (response) { | ||||
| //     print(response.headers); | ||||
| //     return response; | ||||
| //   },); | ||||
| 
 | ||||
| // class Pipeline { | ||||
| //   const Pipeline(); | ||||
| 
 | ||||
| //   Pipeline addMiddleware(Middleware middleware) => | ||||
| //       _Pipeline(middleware, addHandler); | ||||
| 
 | ||||
| //   Handler addHandler(Handler handler) => handler; | ||||
| 
 | ||||
| //   Middleware get middleware => addHandler; | ||||
| // } | ||||
| 
 | ||||
| // class _Pipeline extends Pipeline { | ||||
| //   final Middleware _middleware; | ||||
| //   final Middleware _parent; | ||||
| 
 | ||||
| //   _Pipeline(this._middleware, this._parent); | ||||
| 
 | ||||
| //   @override | ||||
| //   Handler addHandler(Handler handler) => _parent(_middleware(handler)); | ||||
| // } | ||||
| 
 | ||||
| Future<void> main(List<String> args) async { | ||||
|   final UnsafeAuthMiddleware auth = UnsafeAuthMiddleware(); | ||||
|   final Pipeline pipeline = Pipeline() | ||||
|       .addMiddleware( | ||||
|         UriPrefixMiddleware( | ||||
|           protocol: Protocols.http, | ||||
|           authority: 'localhost:80', | ||||
|         ), | ||||
|       ) | ||||
|       .addMiddleware(BodyToJsonMiddleware()) | ||||
|       .addMiddleware( | ||||
|         UnsafeAuthMiddleware( | ||||
|           username: 'wyatt', | ||||
|           password: 'motdepasse', | ||||
|         ), | ||||
|       ) | ||||
|       .addMiddleware(SimpleLoggerMiddleware()); | ||||
|   // .addMiddleware( | ||||
|   //   RefreshTokenMiddleware( | ||||
|   //     authorizationEndpoint: '/api/v1/account/test?action=authorize', | ||||
|   //     tokenEndpoint: '/api/v1/account/test?action=refresh', | ||||
|   //     accessTokenParser: (body) => body['access_token']! as String, | ||||
|   //     refreshTokenParser: (body) => body['refresh_token']! as String, | ||||
|   //   ), | ||||
|   // ); | ||||
| 
 | ||||
|   print(pipeline); | ||||
|   final client = MiddlewareClient(pipeline: pipeline); | ||||
|   await client.post( | ||||
|     Uri.parse('/api/v1/account/test'), | ||||
|     body: <String, String>{ | ||||
|       'email': 'test@test.fr', | ||||
|     }, | ||||
|   ); | ||||
|   auth | ||||
|     ..username = 'username' | ||||
|     ..password = 'password'; | ||||
|   await client.post( | ||||
|     Uri.parse('/api/v1/account/test'), | ||||
|     body: <String, String>{ | ||||
|       'email': 'test@test.fr', | ||||
|     }, | ||||
|   ); | ||||
| } | ||||
| @ -1,67 +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/authentication/interfaces/header_authentication_client.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/authentication_methods.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/utils.dart'; | ||||
| 
 | ||||
| class BasicAuthenticationClient extends HeaderAuthenticationClient { | ||||
|   final String username; | ||||
|   final String password; | ||||
|   final bool preemptive; | ||||
|   final String authenticationHeader; | ||||
| 
 | ||||
|   BasicAuthenticationClient({ | ||||
|     required this.username, | ||||
|     required this.password, | ||||
|     this.preemptive = true, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     BaseClient? inner, | ||||
|   }) : super(inner); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, String> modifyHeader( | ||||
|     Map<String, String> header, [ | ||||
|     BaseRequest? request, | ||||
|   ]) { | ||||
|     header[authenticationHeader] = '${AuthenticationMethods.basic} ' | ||||
|         '${base64Encode(utf8.encode('$username:$password'))}'; | ||||
|     return header; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) async { | ||||
|     if (preemptive) { | ||||
|       // Just send request with modified header. | ||||
|       return super.send(request); | ||||
|     } | ||||
| 
 | ||||
|     // Try to send request without modified header, | ||||
|     // and if it fails, send it with. | ||||
|     final response = await inner.send(request); | ||||
|     if (response.statusCode == HttpStatus.unauthorized) { | ||||
|       // TODO(hpcl): save realm. | ||||
|       final newRequest = Utils.copyRequest(request); | ||||
|       return super.send(newRequest); | ||||
|     } else { | ||||
|       return response; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -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/authentication/interfaces/header_authentication_client.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/authentication_methods.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/utils.dart'; | ||||
| 
 | ||||
| class BearerAuthenticationClient extends HeaderAuthenticationClient { | ||||
|   final String token; | ||||
|   final bool preemptive; | ||||
|   final String authenticationHeader; | ||||
|   final String authenticationMethod; | ||||
| 
 | ||||
|   BearerAuthenticationClient({ | ||||
|     required this.token, | ||||
|     this.preemptive = true, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     this.authenticationMethod = AuthenticationMethods.bearer, | ||||
|     BaseClient? inner, | ||||
|   }) : super(inner); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, String> modifyHeader( | ||||
|     Map<String, String> header, [ | ||||
|     BaseRequest? request, | ||||
|   ]) { | ||||
|     header[authenticationHeader] = '$authenticationMethod $token'; | ||||
|     return header; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) async { | ||||
|     if (preemptive) { | ||||
|       // Just send request with modified header. | ||||
|       return super.send(request); | ||||
|     } | ||||
| 
 | ||||
|     // Try to send request without modified header, | ||||
|     final response = await inner.send(request); | ||||
|     if (response.statusCode == HttpStatus.unauthorized) { | ||||
|       final newRequest = Utils.copyRequest(request); | ||||
|       return super.send(newRequest); | ||||
|     } else { | ||||
|       return response; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,75 +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/authentication/interfaces/header_authentication_client.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'; | ||||
| import 'package:wyatt_http_client/src/utils/utils.dart'; | ||||
| 
 | ||||
| class DigestAuthenticationClient extends HeaderAuthenticationClient { | ||||
|   final String username; | ||||
|   final String password; | ||||
|   final DigestAuth _digestAuth; | ||||
|   final String authenticationHeader; | ||||
|   final String wwwAuthenticateHeader; | ||||
| 
 | ||||
|   DigestAuthenticationClient({ | ||||
|     required this.username, | ||||
|     required this.password, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     this.wwwAuthenticateHeader = HeaderKeys.wwwAuthenticate, | ||||
|     BaseClient? inner, | ||||
|   })  : _digestAuth = DigestAuth(username, password), | ||||
|         super(inner); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, String> modifyHeader( | ||||
|     Map<String, String> header, [ | ||||
|     BaseRequest? request, | ||||
|   ]) { | ||||
|     if ((_digestAuth.isReady()) && request != null) { | ||||
|       header[authenticationHeader] = _digestAuth.getAuthString( | ||||
|         request.method, | ||||
|         request.url, | ||||
|       ); | ||||
|     } | ||||
|     return header; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) async { | ||||
|     // Check if our DigestAuth is ready. | ||||
|     if (_digestAuth.isReady()) { | ||||
|       // If it is, try to send the request with the modified header. | ||||
|       return super.send(request); | ||||
|     } | ||||
| 
 | ||||
|     // If it isn't, try to send the request without the modified header. | ||||
|     final response = await inner.send(request); | ||||
| 
 | ||||
|     if (response.statusCode == HttpStatus.unauthorized) { | ||||
|       final newRequest = Utils.copyRequest(request); | ||||
|       final authInfo = | ||||
|           response.headers[HeaderKeys.wwwAuthenticate.toLowerCase()]; | ||||
|       _digestAuth.initFromAuthenticateHeader(authInfo); | ||||
|       return super.send(newRequest); | ||||
|     } else { | ||||
|       return response; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,115 +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/authentication/interfaces/oauth2_client.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/authentication_methods.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/header_keys.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/http_status.dart'; | ||||
| import 'package:wyatt_http_client/src/utils/utils.dart'; | ||||
| 
 | ||||
| class RefreshTokenClient extends Oauth2Client { | ||||
|   final String authorizationEndpoint; | ||||
|   final String tokenEndpoint; | ||||
| 
 | ||||
|   String? accessToken; | ||||
|   final TokenParser accessTokenParser; | ||||
|   String? refreshToken; | ||||
|   final TokenParser refreshTokenParser; | ||||
| 
 | ||||
|   final String authenticationHeader; | ||||
|   final String authenticationMethod; | ||||
| 
 | ||||
|   RefreshTokenClient({ | ||||
|     required this.authorizationEndpoint, | ||||
|     required this.tokenEndpoint, | ||||
|     required this.accessTokenParser, | ||||
|     required this.refreshTokenParser, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     this.authenticationMethod = AuthenticationMethods.bearer, | ||||
|     BaseClient? inner, | ||||
|   }) : super(inner); | ||||
| 
 | ||||
|   @override | ||||
|   Map<String, String> modifyHeader( | ||||
|     Map<String, String> header, [ | ||||
|     BaseRequest? request, | ||||
|   ]) { | ||||
|     if (accessToken != null && request != null) { | ||||
|       header[authenticationHeader] = '$authenticationMethod $accessToken'; | ||||
|       return header; | ||||
|     } | ||||
|     return header; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> authorize( | ||||
|     Map<String, dynamic> body, { | ||||
|     Map<String, String>? headers, | ||||
|   }) async { | ||||
|     final response = await inner.post( | ||||
|       Uri.parse(authorizationEndpoint), | ||||
|       body: jsonEncode(body), | ||||
|       headers: headers, | ||||
|     ); | ||||
| 
 | ||||
|     if (response.statusCode == HttpStatus.ok) { | ||||
|       final body = json.decode(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; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> refresh() async { | ||||
|     if (refreshToken != null) { | ||||
|       final Map<String, String> header = { | ||||
|         authenticationHeader: '$authenticationHeader $refreshToken', | ||||
|       }; | ||||
| 
 | ||||
|       final response = await inner.get( | ||||
|         Uri.parse(tokenEndpoint), | ||||
|         headers: header, | ||||
|       ); | ||||
| 
 | ||||
|       if (response.statusCode == HttpStatus.ok) { | ||||
|         final body = json.decode(response.body) as Map<String, dynamic>; | ||||
|         accessToken = accessTokenParser(body); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) async { | ||||
|     final response = await super.send(request); | ||||
| 
 | ||||
|     if (response.statusCode == HttpStatus.unauthorized) { | ||||
|       await refresh(); | ||||
|       return super.send(Utils.copyRequest(request)); | ||||
|     } | ||||
| 
 | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @ -14,23 +14,25 @@ | ||||
| // 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/authentication/interfaces/authentication_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'; | ||||
| 
 | ||||
| abstract class HeaderAuthenticationClient extends AuthenticationClient { | ||||
|   HeaderAuthenticationClient(super.inner); | ||||
| 
 | ||||
|   Map<String, String> modifyHeader( | ||||
|     Map<String, String> header, [ | ||||
|     BaseRequest? request, | ||||
|   ]) => | ||||
|       header; | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) { | ||||
|     final newHeader = modifyHeader(Map.from(request.headers), request); | ||||
|     request.headers.clear(); | ||||
|     request.headers.addAll(newHeader); | ||||
|     return super.send(request); | ||||
| abstract class Middleware { | ||||
|   Middleware(); | ||||
|   String getName(); | ||||
| } | ||||
| 
 | ||||
| mixin OnRequestMiddleware { | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| mixin OnResponseMiddleware { | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										129
									
								
								packages/wyatt_http_client/lib/src/middleware_client.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								packages/wyatt_http_client/lib/src/middleware_client.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class MiddlewareClient extends BaseClient { | ||||
|   final Client inner; | ||||
|   final Pipeline pipeline; | ||||
| 
 | ||||
|   MiddlewareClient({ | ||||
|     Pipeline? pipeline, | ||||
|     Client? inner, | ||||
|   })  : pipeline = pipeline ?? Pipeline(), | ||||
|         inner = inner ?? Client() { | ||||
|     print('Using Pipeline:\n$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) { | ||||
|     return 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; | ||||
|   } | ||||
| } | ||||
| @ -14,24 +14,3 @@ | ||||
| // 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/rest_client.dart'; | ||||
| 
 | ||||
| abstract class AuthenticationClient extends BaseClient { | ||||
|   final BaseClient _inner; | ||||
| 
 | ||||
|   BaseClient get inner => _inner; | ||||
| 
 | ||||
|   AuthenticationClient(BaseClient? inner) : _inner = inner ?? RestClient(); | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) { | ||||
|     return _inner.send(request); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void close() { | ||||
|     _inner.close(); | ||||
|     return super.close(); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,59 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class BasicAuthMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   String? username; | ||||
|   String? password; | ||||
|   final String authenticationHeader; | ||||
| 
 | ||||
|   BasicAuthMiddleware({ | ||||
|     this.username, | ||||
|     this.password, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'BasicAuth'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     if (username == null || password == null) { | ||||
|       return request; | ||||
|     } | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> Basic: ${base64Encode(utf8.encode('$username:$password'))}', | ||||
|     ); | ||||
|     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; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,49 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class BodyToJsonMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   @override | ||||
|   String getName() => 'BodyToJson'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> Transforms body in json if Map then update ' | ||||
|       'headers with right content-type', | ||||
|     ); | ||||
|     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; | ||||
|   } | ||||
| } | ||||
| @ -14,17 +14,9 @@ | ||||
| // 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/authentication/interfaces/authentication_client.dart'; | ||||
| 
 | ||||
| abstract class UrlAuthenticationClient extends AuthenticationClient { | ||||
|   UrlAuthenticationClient(super.inner); | ||||
| 
 | ||||
|   BaseRequest modifyRequest(BaseRequest request) => request; | ||||
| import 'package:wyatt_http_client/src/middleware.dart'; | ||||
| 
 | ||||
| class DefaultMiddleware implements Middleware { | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) { | ||||
|     final newRequest = modifyRequest(request); | ||||
|     return super.send(newRequest); | ||||
|   } | ||||
|   String getName() => 'DefaultMiddleware'; | ||||
| } | ||||
| @ -0,0 +1,92 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class DigestAuthMiddleware | ||||
|     with OnRequestMiddleware, OnResponseMiddleware | ||||
|     implements Middleware { | ||||
|   final String username; | ||||
|   final String password; | ||||
|   final DigestAuth _digestAuth; | ||||
|   final String authenticationHeader; | ||||
|   final String wwwAuthenticateHeader; | ||||
|   final HttpStatus unauthorized; | ||||
| 
 | ||||
|   DigestAuthMiddleware({ | ||||
|     required this.username, | ||||
|     required this.password, | ||||
|     this.authenticationHeader = HeaderKeys.authorization, | ||||
|     this.wwwAuthenticateHeader = HeaderKeys.wwwAuthenticate, | ||||
|     this.unauthorized = HttpStatus.unauthorized, | ||||
|   }) : _digestAuth = DigestAuth(username, password); | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'DigestAuth'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> Digest ready: ${_digestAuth.isReady()}', | ||||
|     ); | ||||
|     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); | ||||
|       } | ||||
|     } | ||||
|     print( | ||||
|       '${getName()}::OnResponse\n' | ||||
|       '>> Digest ready: ${_digestAuth.isReady()}', | ||||
|     ); | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| // 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 'access_token_auth_middleware.dart'; | ||||
| 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'; | ||||
| @ -0,0 +1,191 @@ | ||||
| // 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>); | ||||
| 
 | ||||
| class RefreshTokenAuthMiddleware | ||||
|     with OnRequestMiddleware, OnResponseMiddleware | ||||
|     implements Middleware { | ||||
|   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; | ||||
| 
 | ||||
|   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, | ||||
|   }); | ||||
| 
 | ||||
|   @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 { | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> accessToken: $accessToken\n' | ||||
|       '>> refreshToken: $refreshToken', | ||||
|     ); | ||||
|     // 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; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     print( | ||||
|       '${getName()}::OnResponse\n' | ||||
|       '>> accessToken: $accessToken\n' | ||||
|       '>> 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; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class SimpleLoggerMiddleware | ||||
|     with OnRequestMiddleware, OnResponseMiddleware | ||||
|     implements Middleware { | ||||
|   @override | ||||
|   String getName() => 'SimpleLogger'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> ${request.method} ${request.url}\n' | ||||
|       '>> Headers: ${request.headers}\n>> Body: ${request.encodedBody}', | ||||
|     ); | ||||
|     return request; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     print( | ||||
|       '${getName()}::OnResponse\n' | ||||
|       '>> Status: ${response.status.name.toUpperCase()}\n' | ||||
|       '>> Length: ${response.contentLength ?? '0'} bytes', | ||||
|       // '>> Body: ${response.body}', | ||||
|     ); | ||||
|     return response; | ||||
|   } | ||||
| } | ||||
| @ -14,30 +14,43 @@ | ||||
| // 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/authentication/interfaces/url_authentication_client.dart'; | ||||
| 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'; | ||||
| import 'package:wyatt_http_client/src/utils/utils.dart'; | ||||
| 
 | ||||
| class UnsafeAuthenticationClient extends UrlAuthenticationClient { | ||||
|   final String username; | ||||
|   final String password; | ||||
| class UnsafeAuthMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   String? username; | ||||
|   String? password; | ||||
| 
 | ||||
|   final String usernameField; | ||||
|   final String passwordField; | ||||
| 
 | ||||
|   UnsafeAuthenticationClient({ | ||||
|     required this.username, | ||||
|     required this.password, | ||||
|   UnsafeAuthMiddleware({ | ||||
|     this.username, | ||||
|     this.password, | ||||
|     this.usernameField = 'username', | ||||
|     this.passwordField = 'password', | ||||
|     BaseClient? inner, | ||||
|   }) : super(inner); | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   BaseRequest modifyRequest(BaseRequest request) { | ||||
|     final url = | ||||
|   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'; | ||||
|     return Utils.copyRequestWith(request, url: url); | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> Append: ?$usernameField=$username&$passwordField=$password', | ||||
|     ); | ||||
|     request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); | ||||
|     return request; | ||||
|   } | ||||
| } | ||||
| @ -14,25 +14,35 @@ | ||||
| // 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/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'; | ||||
| import 'package:wyatt_http_client/src/utils/utils.dart'; | ||||
| 
 | ||||
| class RestClient extends BaseClient { | ||||
| class UriPrefixMiddleware with OnRequestMiddleware implements Middleware { | ||||
|   final Protocols protocol; | ||||
|   final String? authority; | ||||
| 
 | ||||
|   final Client _inner; | ||||
| 
 | ||||
|   RestClient({ | ||||
|     this.protocol = Protocols.https, | ||||
|     this.authority = '', | ||||
|     Client? inner, | ||||
|   }) : _inner = inner ?? Client(); | ||||
|   UriPrefixMiddleware({ | ||||
|     required this.protocol, | ||||
|     required this.authority, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   Future<StreamedResponse> send(BaseRequest request) { | ||||
|   String getName() => 'UriPrefix'; | ||||
| 
 | ||||
|   @override | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); | ||||
|     return _inner.send(Utils.copyRequestWith(request, url: uri)); | ||||
|     print( | ||||
|       '${getName()}::OnRequest\n' | ||||
|       '>> From: ${request.url}\n' | ||||
|       '>> To: $uri', | ||||
|     ); | ||||
|     request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); | ||||
|     return request; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||
| // 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'; | ||||
| 
 | ||||
| class MiddlewareContext { | ||||
|   Pipeline pipeline; | ||||
|   MiddlewareClient client; | ||||
|   MiddlewareRequest? originalRequest; | ||||
|   MiddlewareRequest? lastRequest; | ||||
|   MiddlewareResponse? originalResponse; | ||||
|   MiddlewareResponse? lastResponse; | ||||
| 
 | ||||
|   MiddlewareContext({ | ||||
|     required this.pipeline, | ||||
|     required this.client, | ||||
|     this.originalRequest, | ||||
|     this.lastRequest, | ||||
|     this.originalResponse, | ||||
|     this.lastResponse, | ||||
|   }); | ||||
| 
 | ||||
|   MiddlewareContext copyWith({ | ||||
|     Pipeline? pipeline, | ||||
|     MiddlewareClient? client, | ||||
|     MiddlewareRequest? originalRequest, | ||||
|     MiddlewareRequest? lastRequest, | ||||
|     MiddlewareResponse? originalResponse, | ||||
|     MiddlewareResponse? lastResponse, | ||||
|   }) { | ||||
|     return 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() { | ||||
|     return 'MiddlewareContext(pipeline: $pipeline, client: $client, originalRequest: $originalRequest, lastRequest: $lastRequest, originalResponse: $originalResponse, lastResponse: $lastResponse)'; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,82 @@ | ||||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||
| // 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'; | ||||
| 
 | ||||
| class MiddlewareRequest { | ||||
|   UnfreezedRequest unfreezedRequest; | ||||
|   Request _httpRequest; | ||||
| 
 | ||||
|   Request get request => _httpRequest; | ||||
| 
 | ||||
|   // Proxy | ||||
|   String get method => _httpRequest.method; | ||||
|   Uri get url => _httpRequest.url; | ||||
|   Map<String, String> get headers => _httpRequest.headers; | ||||
|   Encoding get encoding => _httpRequest.encoding; | ||||
|   String get encodedBody => _httpRequest.body; | ||||
|   Object? get body => unfreezedRequest.body; | ||||
| 
 | ||||
|   MiddlewareRequest({ | ||||
|     required this.unfreezedRequest, | ||||
|   }) : _httpRequest = Request(unfreezedRequest.method, unfreezedRequest.url); | ||||
| 
 | ||||
|   MiddlewareRequest copyWith({ | ||||
|     UnfreezedRequest? unfreezedRequest, | ||||
|   }) { | ||||
|     return MiddlewareRequest( | ||||
|       unfreezedRequest: unfreezedRequest ?? this.unfreezedRequest, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   void modifyRequest(UnfreezedRequest unfreezedRequest) { | ||||
|     String? _body; | ||||
|     if (unfreezedRequest.body != null) { | ||||
|       final 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; | ||||
|   } | ||||
| 
 | ||||
|   void apply() { | ||||
|     modifyRequest(unfreezedRequest); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'MiddlewareRequest(unfreezedRequest: $unfreezedRequest)'; | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||
| // 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'; | ||||
| 
 | ||||
| class MiddlewareResponse { | ||||
|   BaseResponse httpResponse; | ||||
| 
 | ||||
|   // Proxy | ||||
|   int get statusCode => httpResponse.statusCode; | ||||
|   HttpStatus get status => HttpStatus.from(statusCode); | ||||
|   String get body { | ||||
|     if (httpResponse is Response) { | ||||
|       return (httpResponse as Response).body; | ||||
|     } else { | ||||
|       return ''; | ||||
|     } | ||||
|   } | ||||
|   int? get contentLength => httpResponse.contentLength; | ||||
|   Map<String, String> get headers => httpResponse.headers; | ||||
| 
 | ||||
|   MiddlewareResponse({ | ||||
|     required this.httpResponse, | ||||
|   }); | ||||
| 
 | ||||
|   MiddlewareResponse copyWith({ | ||||
|     BaseResponse? httpResponse, | ||||
|   }) { | ||||
|     return MiddlewareResponse( | ||||
|       httpResponse: httpResponse ?? this.httpResponse, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => | ||||
|       'MiddlewareResponse(httpResponse: $httpResponse)'; | ||||
| } | ||||
							
								
								
									
										20
									
								
								packages/wyatt_http_client/lib/src/models/models.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/wyatt_http_client/lib/src/models/models.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| // 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 'middleware_context.dart'; | ||||
| export 'middleware_request.dart'; | ||||
| export 'middleware_response.dart'; | ||||
| export 'unfreezed_request.dart'; | ||||
| @ -0,0 +1,55 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class UnfreezedRequest { | ||||
|   final String method; | ||||
|   final Uri url; | ||||
|   final Map<String, String>? headers; | ||||
|   final Object? body; | ||||
|   final Encoding? encoding; | ||||
| 
 | ||||
|   UnfreezedRequest({ | ||||
|     required this.method, | ||||
|     required this.url, | ||||
|     this.headers, | ||||
|     this.body, | ||||
|     this.encoding, | ||||
|   }); | ||||
| 
 | ||||
|   UnfreezedRequest copyWith({ | ||||
|     String? method, | ||||
|     Uri? url, | ||||
|     Map<String, String>? headers, | ||||
|     Object? body, | ||||
|     Encoding? encoding, | ||||
|   }) { | ||||
|     return UnfreezedRequest( | ||||
|       method: method ?? this.method, | ||||
|       url: url ?? this.url, | ||||
|       headers: headers ?? this.headers, | ||||
|       body: body ?? this.body, | ||||
|       encoding: encoding ?? this.encoding, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'UnfreezedRequest(method: $method, url: $url, headers: ' | ||||
|         '$headers, body: $body, encoding: $encoding)'; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										107
									
								
								packages/wyatt_http_client/lib/src/pipeline.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								packages/wyatt_http_client/lib/src/pipeline.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class Pipeline { | ||||
|   final List<Middleware> _middlewares; | ||||
| 
 | ||||
|   int get length => _middlewares.length; | ||||
| 
 | ||||
|   Pipeline() : _middlewares = <Middleware>[]; | ||||
|   Pipeline.fromIterable(Iterable<Middleware> middlewares) | ||||
|       : _middlewares = middlewares.toList(); | ||||
| 
 | ||||
|   /// Add a [Middleware] to this [Pipeline] | ||||
|   Pipeline addMiddleware(Middleware middleware) { | ||||
|     _middlewares.add(middleware); | ||||
|     return this; | ||||
|   } | ||||
| 
 | ||||
|   /// 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); | ||||
|   } | ||||
| 
 | ||||
|   Future<MiddlewareRequest> onRequest( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareRequest request, | ||||
|   ) async { | ||||
|     print('\n\nNEW REQUEST\n'); | ||||
|     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; | ||||
|   } | ||||
| 
 | ||||
|   Future<MiddlewareResponse> onResponse( | ||||
|     MiddlewareContext context, | ||||
|     MiddlewareResponse response, | ||||
|   ) async { | ||||
|     print('\n\nNEW RESPONSE\n'); | ||||
|     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(' -> ')}'; | ||||
|   } | ||||
| } | ||||
| @ -14,6 +14,8 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| class Convert { | ||||
|   static String toHex(List<int> bytes, {bool upperCase = false}) { | ||||
|     final buffer = StringBuffer(); | ||||
| @ -29,6 +31,15 @@ class Convert { | ||||
|       return buffer.toString(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   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 { | ||||
|  | ||||
							
								
								
									
										36
									
								
								packages/wyatt_http_client/lib/src/utils/delay.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								packages/wyatt_http_client/lib/src/utils/delay.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| abstract class Delay { | ||||
|   static Duration getRetryDelay(int attempt) { | ||||
|     assert(attempt >= 0, 'attempt cannot be negative'); | ||||
|     if (attempt <= 0) { | ||||
|       return Duration.zero; | ||||
|     } | ||||
|     final rand = Random(); | ||||
|     final Duration delayFactor = const Duration(milliseconds: 200); | ||||
|     final double randomizationFactor = 0.25; | ||||
|     final Duration maxDelay = const 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; | ||||
|   } | ||||
| } | ||||
| @ -14,21 +14,15 @@ | ||||
| // 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/authentication/interfaces/header_authentication_client.dart'; | ||||
| enum HttpMethods { | ||||
|   head('HEAD'), | ||||
|   get('GET'), | ||||
|   post('POST'), | ||||
|   put('PUT'), | ||||
|   patch('PATCH'), | ||||
|   delete('DELETE'); | ||||
| 
 | ||||
| typedef TokenParser = String Function(Map<String, dynamic>); | ||||
|   final String method; | ||||
| 
 | ||||
| abstract class Oauth2Client extends HeaderAuthenticationClient { | ||||
|   Oauth2Client(super.inner); | ||||
| 
 | ||||
|   Future<void> refresh() { | ||||
|     return Future.value(); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> authorize( | ||||
|     Map<String, dynamic> body, { | ||||
|     Map<String, String>? headers, | ||||
|   }) { | ||||
|     return Future.value(); | ||||
|   } | ||||
|   const HttpMethods(this.method); | ||||
| } | ||||
| @ -15,69 +15,109 @@ | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Status codes for HTTP responses. Extracted from dart:io | ||||
| abstract class HttpStatus { | ||||
|   static const int continue_ = 100; | ||||
|   static const int switchingProtocols = 101; | ||||
|   static const int processing = 102; | ||||
|   static const int ok = 200; | ||||
|   static const int created = 201; | ||||
|   static const int accepted = 202; | ||||
|   static const int nonAuthoritativeInformation = 203; | ||||
|   static const int noContent = 204; | ||||
|   static const int resetContent = 205; | ||||
|   static const int partialContent = 206; | ||||
|   static const int multiStatus = 207; | ||||
|   static const int alreadyReported = 208; | ||||
|   static const int imUsed = 226; | ||||
|   static const int multipleChoices = 300; | ||||
|   static const int movedPermanently = 301; | ||||
|   static const int found = 302; | ||||
|   static const int movedTemporarily = 302; // Common alias for found. | ||||
|   static const int seeOther = 303; | ||||
|   static const int notModified = 304; | ||||
|   static const int useProxy = 305; | ||||
|   static const int temporaryRedirect = 307; | ||||
|   static const int permanentRedirect = 308; | ||||
|   static const int badRequest = 400; | ||||
|   static const int unauthorized = 401; | ||||
|   static const int paymentRequired = 402; | ||||
|   static const int forbidden = 403; | ||||
|   static const int notFound = 404; | ||||
|   static const int methodNotAllowed = 405; | ||||
|   static const int notAcceptable = 406; | ||||
|   static const int proxyAuthenticationRequired = 407; | ||||
|   static const int requestTimeout = 408; | ||||
|   static const int conflict = 409; | ||||
|   static const int gone = 410; | ||||
|   static const int lengthRequired = 411; | ||||
|   static const int preconditionFailed = 412; | ||||
|   static const int requestEntityTooLarge = 413; | ||||
|   static const int requestUriTooLong = 414; | ||||
|   static const int unsupportedMediaType = 415; | ||||
|   static const int requestedRangeNotSatisfiable = 416; | ||||
|   static const int expectationFailed = 417; | ||||
|   static const int misdirectedRequest = 421; | ||||
|   static const int unprocessableEntity = 422; | ||||
|   static const int locked = 423; | ||||
|   static const int failedDependency = 424; | ||||
|   static const int upgradeRequired = 426; | ||||
|   static const int preconditionRequired = 428; | ||||
|   static const int tooManyRequests = 429; | ||||
|   static const int requestHeaderFieldsTooLarge = 431; | ||||
|   static const int connectionClosedWithoutResponse = 444; | ||||
|   static const int unavailableForLegalReasons = 451; | ||||
|   static const int clientClosedRequest = 499; | ||||
|   static const int internalServerError = 500; | ||||
|   static const int notImplemented = 501; | ||||
|   static const int badGateway = 502; | ||||
|   static const int serviceUnavailable = 503; | ||||
|   static const int gatewayTimeout = 504; | ||||
|   static const int httpVersionNotSupported = 505; | ||||
|   static const int variantAlsoNegotiates = 506; | ||||
|   static const int insufficientStorage = 507; | ||||
|   static const int loopDetected = 508; | ||||
|   static const int notExtended = 510; | ||||
|   static const int networkAuthenticationRequired = 511; | ||||
| 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. | ||||
|   static const int networkConnectTimeoutError = 599; | ||||
|   networkConnectTimeoutError(599); | ||||
| 
 | ||||
|   final int statusCode; | ||||
| 
 | ||||
|   const HttpStatus(this.statusCode); | ||||
| 
 | ||||
|   bool equals(Object other) { | ||||
|     if (other is HttpStatus) { | ||||
|       return statusCode == other.statusCode; | ||||
|     } | ||||
|     if (other is int) { | ||||
|       return statusCode == other; | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   bool isInfo() { | ||||
|     return statusCode >= 100 && statusCode < 200;  | ||||
|   } | ||||
| 
 | ||||
|   bool isSuccess() { | ||||
|     return statusCode >= 200 && statusCode < 300;  | ||||
|   } | ||||
| 
 | ||||
|   bool isRedirection() { | ||||
|     return statusCode >= 300 && statusCode < 400;  | ||||
|   } | ||||
| 
 | ||||
|   bool isClientError() { | ||||
|     return statusCode >= 400 && statusCode < 500;  | ||||
|   } | ||||
| 
 | ||||
|   bool isServerError() { | ||||
|     return statusCode >= 500 && statusCode < 600;  | ||||
|   } | ||||
| 
 | ||||
|   factory HttpStatus.from(int status) { | ||||
|     return HttpStatus.values | ||||
|         .firstWhere((element) => element.statusCode == status); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,5 @@ enum Protocols { | ||||
|   http, | ||||
|   https; | ||||
| 
 | ||||
|   String get name => toString().split('.').last; | ||||
|   String get scheme => '$name://'; | ||||
| } | ||||
|  | ||||
							
								
								
									
										89
									
								
								packages/wyatt_http_client/lib/src/utils/request_utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								packages/wyatt_http_client/lib/src/utils/request_utils.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| 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; | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|   } | ||||
| 
 | ||||
|   static BaseRequest copyRequest(BaseRequest original) { | ||||
|     if (original is Request) { | ||||
|       return _copyNormalRequest(original); | ||||
|     } else { | ||||
|       throw UnimplementedError( | ||||
|         'Cannot handle requests of type ${original.runtimeType}', | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -14,76 +14,10 @@ | ||||
| // 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'; | ||||
| 
 | ||||
| abstract class Utils { | ||||
|   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; | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|   } | ||||
| 
 | ||||
|   static BaseRequest copyRequest(BaseRequest original) { | ||||
|     if (original is Request) { | ||||
|       return _copyNormalRequest(original); | ||||
|     } else { | ||||
|       throw UnimplementedError( | ||||
|         'Cannot handle requests of type ${original.runtimeType}', | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   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}', | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 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'; | ||||
|  | ||||
| @ -15,3 +15,10 @@ | ||||
| // 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'; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user