diff --git a/packages/wyatt_http_client/README.md b/packages/wyatt_http_client/README.md index 8b55e735..ee2b78d5 100644 --- a/packages/wyatt_http_client/README.md +++ b/packages/wyatt_http_client/README.md @@ -1,39 +1,182 @@ - -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 +

+ + Style: Wyatt Analysis + + SDK: Dart & Flutter +

-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 onRequest( + MiddlewareContext context, + MiddlewareRequest request, + ) async { + print('${getName()}::OnRequest'); + return request; + } + + @override + Future onResponse( + MiddlewareContext context, + MiddlewareResponse response, + ) async { + print('${getName()}::OnResponse'); + return response; + } +} +``` diff --git a/packages/wyatt_http_client/example/http_client_example.dart b/packages/wyatt_http_client/example/http_client_example.dart index e8766148..57b16bee 100644 --- a/packages/wyatt_http_client/example/http_client_example.dart +++ b/packages/wyatt_http_client/example/http_client_example.dart @@ -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 handleBasic(HttpRequest req) async { Future 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 handleBearer(HttpRequest req) async { Future 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 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 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 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 server() async { Future 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({ - 'username': 'username', - 'password': 'password', - }); + // Login + await refreshToken.post( + Uri.parse('/test/oauth2-test'), + body: { + '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); } diff --git a/packages/wyatt_http_client/example/http_client_fastapi_example.dart b/packages/wyatt_http_client/example/http_client_fastapi_example.dart index 93e40d3e..5393611c 100644 --- a/packages/wyatt_http_client/example/http_client_fastapi_example.dart +++ b/packages/wyatt_http_client/example/http_client_fastapi_example.dart @@ -19,7 +19,7 @@ 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_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'; @@ -354,7 +354,6 @@ class FastAPI { void main(List args) async { final Pipeline pipeline = Pipeline() - .addMiddleware(SimpleLoggerMiddleware()) .addMiddleware( UriPrefixMiddleware( protocol: Protocols.http, @@ -363,39 +362,37 @@ void main(List args) async { ) .addMiddleware(BodyToJsonMiddleware()) .addMiddleware( - RefreshTokenMiddleware( + 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.getLogic()); + 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, - // ), - // ); - // print(verifiedAccount); - // final registeredAccount = await api.signUp( - // SignUp(sessionId: verifiedAccount.sessionId ?? '', password: 'password'), - // ); - // print(registeredAccount); + 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'), ); - // print(signedInAccount); final accountList = await api.getAccountList(); print(accountList); } diff --git a/packages/wyatt_http_client/example/http_client_test.dart b/packages/wyatt_http_client/example/http_client_test.dart deleted file mode 100644 index 36a66c15..00000000 --- a/packages/wyatt_http_client/example/http_client_test.dart +++ /dev/null @@ -1,41 +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 . - - -Future main(List args) async { - // final client = Oauth2Client( - // accessToken: 'test-token', - // inner: RestClient(protocol: Protocols.http, authority: 'localhost:80'), - // ); - // final client = RestClient( - // protocol: Protocols.http, - // authority: 'localhost:80', - // inner: Oauth2Client( - // authorizationEndpoint: '/api/v1/account/test', - // tokenEndpoint: '/api/v1/account/test', - // accessToken: 'test-token', - // refreshToken: 'refresh-token', - // ), - // ); - // final client = RestClient(protocol: Protocols.http, authority: 'localhost:80'); - // final client =AwesomeRestClient(protocol: Protocols.http, authority: 'localhost:80'); - // var r = await client.post( - // Uri.parse('/api/v1/account/test'), - // body: { - // 'email': 'test@test.fr', - // }, - // ); -} diff --git a/packages/wyatt_http_client/example/pipeline.dart b/packages/wyatt_http_client/example/pipeline.dart index 4b9fa9fe..334fc509 100644 --- a/packages/wyatt_http_client/example/pipeline.dart +++ b/packages/wyatt_http_client/example/pipeline.dart @@ -17,8 +17,8 @@ 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_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'; @@ -117,8 +117,8 @@ import 'package:wyatt_http_client/src/utils/protocols.dart'; // } Future main(List args) async { + final UnsafeAuthMiddleware auth = UnsafeAuthMiddleware(); final Pipeline pipeline = Pipeline() - .addMiddleware(SimpleLoggerMiddleware()) .addMiddleware( UriPrefixMiddleware( protocol: Protocols.http, @@ -127,17 +127,33 @@ Future main(List args) async { ) .addMiddleware(BodyToJsonMiddleware()) .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, + 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.getLogic()); + print(pipeline); final client = MiddlewareClient(pipeline: pipeline); - final r = await client.post( + await client.post( + Uri.parse('/api/v1/account/test'), + body: { + 'email': 'test@test.fr', + }, + ); + auth + ..username = 'username' + ..password = 'password'; + await client.post( Uri.parse('/api/v1/account/test'), body: { 'email': 'test@test.fr', diff --git a/packages/wyatt_http_client/lib/src/authentication/basic_authentication_client.dart b/packages/wyatt_http_client/lib/src/authentication/basic_authentication_client.dart deleted file mode 100644 index 79bdef1b..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/basic_authentication_client.dart +++ /dev/null @@ -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 . - -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 modifyHeader( - Map header, [ - BaseRequest? request, - ]) { - header[authenticationHeader] = '${AuthenticationMethods.basic} ' - '${base64Encode(utf8.encode('$username:$password'))}'; - return header; - } - - @override - Future 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; - } - } -} diff --git a/packages/wyatt_http_client/lib/src/authentication/bearer_authentication_client.dart b/packages/wyatt_http_client/lib/src/authentication/bearer_authentication_client.dart deleted file mode 100644 index cf16c730..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/bearer_authentication_client.dart +++ /dev/null @@ -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 . - -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 modifyHeader( - Map header, [ - BaseRequest? request, - ]) { - header[authenticationHeader] = '$authenticationMethod $token'; - return header; - } - - @override - Future 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; - } - } -} diff --git a/packages/wyatt_http_client/lib/src/authentication/digest_authentication_client.dart b/packages/wyatt_http_client/lib/src/authentication/digest_authentication_client.dart deleted file mode 100644 index cc5b96ea..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/digest_authentication_client.dart +++ /dev/null @@ -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 . - -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 modifyHeader( - Map header, [ - BaseRequest? request, - ]) { - if ((_digestAuth.isReady()) && request != null) { - header[authenticationHeader] = _digestAuth.getAuthString( - request.method, - request.url, - ); - } - return header; - } - - @override - Future 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; - } - } -} diff --git a/packages/wyatt_http_client/lib/src/authentication/interfaces/authenticated_client.dart b/packages/wyatt_http_client/lib/src/authentication/interfaces/authenticated_client.dart deleted file mode 100644 index 0c8cc7f1..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/interfaces/authenticated_client.dart +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2022 WYATT GROUP -// Please see the AUTHORS file for details. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/implemented_base_client.dart'; - -abstract class AuthenticatedClient implements ImplementedBaseClient { - final Client _inner; - - AuthenticatedClient({ - Client? inner, - }) : _inner = inner ?? Client(); - - @override - void close() => _inner.close(); - - @override - Future head(Uri url, {Map? headers}) => - _inner.head(url, headers: headers); - - @override - Future get(Uri url, {Map? headers}) => - _inner.get(url, headers: headers); - - @override - Future post( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.post(url, headers: headers, body: body, encoding: encoding); - - @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.put(url, headers: headers, body: body, encoding: encoding); - - @override - Future patch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.patch(url, headers: headers, body: body, encoding: encoding); - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.delete(url, headers: headers, body: body, encoding: encoding); - - @override - Future read(Uri url, {Map? headers}) => - _inner.read(url, headers: headers); - - @override - Future readBytes(Uri url, {Map? headers}) => - _inner.readBytes(url, headers: headers); - - @override - Future send(BaseRequest request) => _inner.send(request); -} diff --git a/packages/wyatt_http_client/lib/src/authentication/interfaces/authentication_client.dart b/packages/wyatt_http_client/lib/src/authentication/interfaces/authentication_client.dart deleted file mode 100644 index 94c844d4..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/interfaces/authentication_client.dart +++ /dev/null @@ -1,234 +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 . - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/mixins/body_transformer.dart'; -import 'package:wyatt_http_client/src/mixins/headers_transformer.dart'; -import 'package:wyatt_http_client/src/rest_client.dart'; -import 'package:wyatt_http_client/src/utils/protocols.dart'; -import 'package:wyatt_http_client/src/utils/utils.dart'; - -class AwesomeClient extends BaseClient { - final Client _inner; - - AwesomeClient({ - Client? inner, - }) : _inner = inner ?? Client(); - - @override - Future send(BaseRequest request) { - return _inner.send(request); - } -} - -class AwesomeRestClient extends AwesomeClient - with HeadersTransformer, BodyTransformer { - final Protocols protocol; - final String? authority; - - AwesomeRestClient({ - this.protocol = Protocols.https, - this.authority = '', - super.inner, - }); - - @override - Object? bodyMutator(Object? body) { - print('bodyMutator: Json encoding'); - return jsonEncode(body); - } - - @override - Map? headersMutator( - Object? body, - Map? headers, - ) { - print('headerMutator: Json encoding'); - final mutation = { - 'content-type': 'application/json; charset=utf-8', - }; - if (headers != null) { - headers.addAll(mutation); - return headers; - } else { - return mutation; - } - } - - @override - BaseRequest requestMutator(BaseRequest request) { - print('requestMutator: scheme + authority'); - final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); - return Utils.copyRequestWith(request, url: uri); - } -} - -class AwesomeUrlClient extends AuthenticatedClient { - AwesomeUrlClient(super.inner); -} - -class AwesomeOauth2Client extends AuthenticatedClient with HeadersTransformer { - AwesomeOauth2Client(super.inner); - - @override - Map? headersMutator( - Object? body, - Map? headers, - ) { - print('headersMutator: Token manager'); - final mutation = { - 'authorization': 'Bearer TOKEN', - }; - if (headers != null) { - headers.addAll(mutation); - return headers; - } else { - return mutation; - } - } -} - -abstract class AuthenticatedClient implements Client { - final Client _inner; - - Client get inner => _inner; - - AuthenticatedClient(BaseClient? inner) : _inner = inner ?? RestClient(); - - @override - void close() => _inner.close(); - - @override - Future head(Uri url, {Map? headers}) => - _inner.head(url, headers: headers); - - @override - Future get(Uri url, {Map? headers}) => - _inner.get(url, headers: headers); - - @override - Future post( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.post(url, headers: headers, body: body, encoding: encoding); - - @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.put(url, headers: headers, body: body, encoding: encoding); - - @override - Future patch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.patch(url, headers: headers, body: body, encoding: encoding); - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.delete(url, headers: headers, body: body, encoding: encoding); - - @override - Future read(Uri url, {Map? headers}) => - _inner.read(url, headers: headers); - - @override - Future readBytes(Uri url, {Map? headers}) => - _inner.readBytes(url, headers: headers); - - @override - Future send(BaseRequest request) => _inner.send(request); -} - -abstract class AuthenticationClient extends BaseClient { - final BaseClient _inner; - - BaseClient get inner => _inner; - - AuthenticationClient(BaseClient? inner) : _inner = inner ?? RestClient(); - - @override - Future head(Uri url, {Map? headers}) => - _inner.head(url, headers: headers); - - @override - Future get(Uri url, {Map? headers}) => - _inner.get(url, headers: headers); - - @override - Future post( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.post(url, headers: headers, body: body, encoding: encoding); - - @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.put(url, headers: headers, body: body, encoding: encoding); - - @override - Future patch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.patch(url, headers: headers, body: body, encoding: encoding); - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.delete(url, headers: headers, body: body, encoding: encoding); - - @override - Future send(BaseRequest request) { - return _inner.send(request); - } - - @override - void close() { - _inner.close(); - return super.close(); - } -} diff --git a/packages/wyatt_http_client/lib/src/authentication/interfaces/header_authentication_client.dart b/packages/wyatt_http_client/lib/src/authentication/interfaces/header_authentication_client.dart deleted file mode 100644 index ef8aae8d..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/interfaces/header_authentication_client.dart +++ /dev/null @@ -1,37 +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 . - -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/authentication/interfaces/authentication_client.dart'; - -abstract class HeaderAuthenticationClient extends AuthenticationClient { - HeaderAuthenticationClient(super.inner); - - Map modifyHeader( - Map header, [ - BaseRequest? request, - ]) => - header; - - @override - Future send(BaseRequest request) { - final newHeader = modifyHeader(Map.from(request.headers), request); - request.headers.clear(); - request.headers.addAll(newHeader); - print(newHeader); - return super.send(request); - } -} diff --git a/packages/wyatt_http_client/lib/src/authentication/refresh_token_client.dart b/packages/wyatt_http_client/lib/src/authentication/refresh_token_client.dart deleted file mode 100644 index 01201966..00000000 --- a/packages/wyatt_http_client/lib/src/authentication/refresh_token_client.dart +++ /dev/null @@ -1,195 +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 . - -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 Oauth2Client extends ImplementedBaseClient with RequestTransformer { -// final String authorizationEndpoint; -// final String tokenEndpoint; -// String? accessToken; -// String? refreshToken; -// String? tokenToUse; - -// Oauth2Client({ -// required this.authorizationEndpoint, -// required this.tokenEndpoint, -// this.accessToken, -// this.refreshToken, -// super.inner, -// }) : tokenToUse = accessToken; - -// @override -// BaseRequest requestMutator(BaseRequest request) { -// print('Oauth2Client::requestMutator -> add authorization: $accessToken'); -// final headers = request.headers; -// final mutation = { -// 'Authorization': 'Bearer $tokenToUse', -// }; -// if (tokenToUse?.isNotEmpty ?? false) { -// headers.addAll(mutation); -// return Utils.copyRequestWith(request, headers: headers); -// } -// return request; -// } - -// Future refresh() async { -// if (refreshToken?.isNotEmpty ?? false) { -// tokenToUse = refreshToken; - -// final response = await get( -// Uri.parse(tokenEndpoint), -// ); - -// if (response.statusCode == HttpStatus.ok) { -// // final body = json.decode(response.body) as Map; -// // accessToken = accessTokenParser(body); -// print('Oauth2Client::refresh -> ok'); -// } -// return response; -// } -// return null; -// } - -// @override -// Map? headersMutator( -// Object? body, -// Map? headers, -// ) { -// print( -// 'Oauth2Client::headersMutator -> add authorization: $accessToken', -// ); -// final mutation = { -// 'Authorization': 'Bearer $accessToken', -// }; -// if (accessToken.isNotEmpty) { -// if (headers != null) { -// headers.addAll(mutation); -// return headers; -// } else { -// return mutation; -// } -// } else { -// return headers; -// } -// } - -// } - -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 modifyHeader( - Map header, [ - BaseRequest? request, - ]) { - print('accessToken $accessToken'); - print('request $request'); - if (accessToken != null && request != null) { - header[authenticationHeader] = '$authenticationMethod $accessToken'; - return header; - } - return header; - } - - @override - Future authorize( - Map body, { - Map? headers, - }) async { - final response = await inner.post( - Uri.parse(authorizationEndpoint), - body: body, - headers: headers, - ); - - if (response.statusCode == HttpStatus.ok) { - final body = json.decode(response.body) as Map; - final accessToken = accessTokenParser(body); - final refreshToken = refreshTokenParser(body); - - if (accessToken.isNotEmpty) { - this.accessToken = accessToken; - } - if (refreshToken.isNotEmpty) { - this.refreshToken = refreshToken; - } - } - return response; - } - - @override - Future refresh() async { - if (refreshToken != null) { - final Map 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; - accessToken = accessTokenParser(body); - } - return response; - } - return null; - } - - @override - Future send(BaseRequest request) async { - final newHeader = modifyHeader(Map.from(request.headers), request); - request.headers.clear(); - request.headers.addAll(newHeader); - final response = await super.send(request); - - if (response.statusCode == HttpStatus.unauthorized) { - await refresh(); - return super.send(Utils.copyRequest(request)); - } - - return response; - } -} diff --git a/packages/wyatt_http_client/lib/src/implemented_client.dart b/packages/wyatt_http_client/lib/src/implemented_client.dart deleted file mode 100644 index a9790c33..00000000 --- a/packages/wyatt_http_client/lib/src/implemented_client.dart +++ /dev/null @@ -1,86 +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 . - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:http/http.dart'; - -abstract class ImplementedClient extends BaseClient { - final Client _inner; - - ImplementedClient({ - Client? inner, - }) : _inner = inner ?? Client(); - - @override - void close() => _inner.close(); - - @override - Future head(Uri url, {Map? headers}) => - _inner.head(url, headers: headers); - - @override - Future get(Uri url, {Map? headers}) => - _inner.get(url, headers: headers); - - @override - Future post( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.post(url, headers: headers, body: body, encoding: encoding); - - @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.put(url, headers: headers, body: body, encoding: encoding); - - @override - Future patch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.patch(url, headers: headers, body: body, encoding: encoding); - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) => - _inner.delete(url, headers: headers, body: body, encoding: encoding); - - @override - Future read(Uri url, {Map? headers}) => - _inner.read(url, headers: headers); - - @override - Future readBytes(Uri url, {Map? headers}) => - _inner.readBytes(url, headers: headers); - - @override - Future send(BaseRequest request) => _inner.send(request); -} diff --git a/packages/wyatt_http_client/lib/src/middleware.dart b/packages/wyatt_http_client/lib/src/middleware.dart index 4295a278..07cf6323 100644 --- a/packages/wyatt_http_client/lib/src/middleware.dart +++ b/packages/wyatt_http_client/lib/src/middleware.dart @@ -14,31 +14,25 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -part of 'pipeline.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 Middleware { - /// The http [MiddlewareClient] used by this [Middleware] - MiddlewareClient? _client; - - String getName() => 'MiddlewareNode'; - - // ignore: avoid_setters_without_getters - set httpClient(MiddlewareClient? client) => _client = client; - - Client? get client => _client?.inner; - - Future onRequest( - MiddlewareRequest request, - ) async { - return request; - } - - Future onResponse(MiddlewareResponse response) async { - return response; - } - - @override - String toString() { - return getName(); - } +abstract class Middleware { + Middleware(); + String getName(); +} + +mixin OnRequestMiddleware { + Future onRequest( + MiddlewareContext context, + MiddlewareRequest request, + ); +} + +mixin OnResponseMiddleware { + Future onResponse( + MiddlewareContext context, + MiddlewareResponse response, + ); } diff --git a/packages/wyatt_http_client/lib/src/middleware_client.dart b/packages/wyatt_http_client/lib/src/middleware_client.dart index e6b0dd43..0f646f70 100644 --- a/packages/wyatt_http_client/lib/src/middleware_client.dart +++ b/packages/wyatt_http_client/lib/src/middleware_client.dart @@ -33,7 +33,7 @@ class MiddlewareClient extends BaseClient { Client? inner, }) : pipeline = pipeline ?? Pipeline(), inner = inner ?? Client() { - this.pipeline.setClient(this); + print('Using Pipeline:\n$pipeline'); } @override @@ -100,27 +100,30 @@ class MiddlewareClient extends BaseClient { body: body, encoding: encoding, ), - httpRequest: Request(method, url), - context: MiddlewareContext(pipeline: pipeline), ); + final requestContext = MiddlewareContext( + pipeline: pipeline, + client: this, + originalRequest: originalRequest, + ); + final modifiedRequest = await pipeline.onRequest( + requestContext, originalRequest.copyWith(), ); - final res = await Response.fromStream( - await send(modifiedRequest.httpRequest), - ); - final response = await pipeline.onResponse( - MiddlewareResponse( - httpResponse: res, - middlewareRequest: modifiedRequest, - context: MiddlewareContext( - pipeline: pipeline, - originalRequest: originalRequest, - ), + final originalResponse = MiddlewareResponse( + httpResponse: await Response.fromStream( + await send(modifiedRequest.request), ), ); - return response.httpResponse as Response; + final responseContext = + requestContext.copyWith(originalResponse: originalResponse); + + final modifiedResponse = + await pipeline.onResponse(responseContext, originalResponse.copyWith()); + + return modifiedResponse.httpResponse as Response; } } diff --git a/packages/wyatt_http_client/lib/src/middleware_node.dart b/packages/wyatt_http_client/lib/src/middleware_node.dart deleted file mode 100644 index 32697e40..00000000 --- a/packages/wyatt_http_client/lib/src/middleware_node.dart +++ /dev/null @@ -1,103 +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 . - -part of 'pipeline.dart'; - -class MiddlewareNode { - final Pipeline pipeline; - - Middleware? middleware; - - late MiddlewareNode _parent; - late MiddlewareNode _child; - final bool _isEnd; - - /// Reference to the previous [MiddlewareNode] in the [Pipeline] - MiddlewareNode get parent => _parent; - - /// Reference to the next [MiddlewareNode] in the [Pipeline] - MiddlewareNode get child => _child; - - /// Whether this is the begin [MiddlewareNode] - bool get isBegin => _parent == this; - - /// Whether this is the end [MiddlewareNode] - bool get isEnd => _child == this; - - /// Whether this is the first [MiddlewareNode] - bool get isFirst => !isBegin && _parent == pipeline.begin; - - /// Whether this is the last [MiddlewareNode] - bool get isLast => !isEnd && _child == pipeline.end; - - MiddlewareNode._( - this.pipeline, - this.middleware, { - MiddlewareNode? parent, - MiddlewareNode? child, - }) : _isEnd = false { - _parent = parent ?? this; - _child = child ?? this; - } - - MiddlewareNode._end(this.pipeline) : _isEnd = true { - _child = this; - } - - MiddlewareNode._begin(this.pipeline) : _isEnd = true { - _parent = this; - } - - /// Creates a new [MiddlewareNode] right **before** this in [pipeline] - MiddlewareNode insertBefore(Middleware middleware) { - if (isBegin) { - throw StateError( - 'A MiddlewareNode cannot be inserted ' - 'before begin MiddlewareNode', - ); - } - final newMiddlewareNode = - MiddlewareNode._(pipeline, middleware, parent: _parent, child: this); - _parent._child = newMiddlewareNode; - _parent = newMiddlewareNode; - pipeline._length++; - return newMiddlewareNode; - } - - /// Creates a new [MiddlewareNode] right **after** this in [pipeline] - MiddlewareNode insertAfter(Middleware middleware) { - if (isEnd) { - throw StateError( - 'A MiddlewareNode cannot be inserted ' - 'after end MiddlewareNode', - ); - } - final newMiddlewareNode = - MiddlewareNode._(pipeline, middleware, parent: this, child: _child); - _child._parent = newMiddlewareNode; - _child = newMiddlewareNode; - pipeline._length++; - return newMiddlewareNode; - } - - MiddlewareNode remove() { - if (_isEnd) throw StateError('Cannot remove end MiddlewareNode'); - _child._parent = _parent; - _parent._child = _child; - pipeline._length--; - return child; - } -} diff --git a/packages/wyatt_http_client/lib/src/implemented_base_client.dart b/packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart similarity index 69% rename from packages/wyatt_http_client/lib/src/implemented_base_client.dart rename to packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart index e42ed84f..ece1af01 100644 --- a/packages/wyatt_http_client/lib/src/implemented_base_client.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/access_token_auth_middleware.dart @@ -1,31 +1,16 @@ // 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 . -import 'package:http/http.dart'; - -class ImplementedBaseClient extends BaseClient { - final Client inner; - - ImplementedBaseClient({ - Client? inner, - }) : inner = inner ?? Client(); - - @override - Future send(BaseRequest request) { - return inner.send(request); - } - -} diff --git a/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart new file mode 100644 index 00000000..027ad964 --- /dev/null +++ b/packages/wyatt_http_client/lib/src/middlewares/basic_auth_middleware.dart @@ -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 . + +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 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 headers = request.headers..addAll(mutation); + request.modifyRequest(request.unfreezedRequest.copyWith(headers: headers)); + return request; + } +} diff --git a/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart index af124dc3..a77048d8 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/body_to_json_middleware.dart @@ -16,33 +16,34 @@ 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/pipeline.dart'; -class BodyToJsonMiddleware extends Middleware { +class BodyToJsonMiddleware with OnRequestMiddleware implements Middleware { @override String getName() => 'BodyToJson'; @override - Future onRequest(MiddlewareRequest request) { + Future onRequest( + MiddlewareContext context, + MiddlewareRequest request, + ) async { print( - '${getName()}::OnRequest: transforms body in json if Map then update ' + '${getName()}::OnRequest\n' + '>> Transforms body in json if Map then update ' 'headers with right content-type', ); - var newReq = request.unfreezedRequest; final mutation = { 'content-type': 'application/json; charset=utf-8', }; - if (newReq.body is Map) { - Map? headers = newReq.headers; - if (headers != null) { - headers.addAll(mutation); - } else { - headers = mutation; - } - newReq = newReq.copyWith(body: jsonEncode(newReq.body), headers: headers); - request.updateUnfreezedRequest(newReq); + if (request.body is Map) { + final Map headers = request.headers..addAll(mutation); + request.modifyRequest( + request.unfreezedRequest + .copyWith(headers: headers, body: jsonEncode(request.body)), + ); } - return super.onRequest(request); + return request; } } diff --git a/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart index 67c65728..65b766ca 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/default_middleware.dart @@ -14,9 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:wyatt_http_client/src/pipeline.dart'; +import 'package:wyatt_http_client/src/middleware.dart'; -class DefaultMiddleware extends Middleware { +class DefaultMiddleware implements Middleware { @override String getName() => 'DefaultMiddleware'; } diff --git a/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart new file mode 100644 index 00000000..538f6eb2 --- /dev/null +++ b/packages/wyatt_http_client/lib/src/middlewares/digest_auth_middleware.dart @@ -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 . + +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 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 headers = request.headers..addAll(mutation); + request + .modifyRequest(request.unfreezedRequest.copyWith(headers: headers)); + } + return request; + } + + @override + Future 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; + } +} diff --git a/packages/wyatt_http_client/lib/src/authentication/interfaces/url_authentication_client.dart b/packages/wyatt_http_client/lib/src/middlewares/middlewares.dart similarity index 60% rename from packages/wyatt_http_client/lib/src/authentication/interfaces/url_authentication_client.dart rename to packages/wyatt_http_client/lib/src/middlewares/middlewares.dart index 9d2dad13..724b3ac9 100644 --- a/packages/wyatt_http_client/lib/src/authentication/interfaces/url_authentication_client.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/middlewares.dart @@ -1,30 +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 . -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; - - @override - Future send(BaseRequest request) { - final newRequest = modifyRequest(request); - return super.send(newRequest); - } -} +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'; diff --git a/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_middleware.dart new file mode 100644 index 00000000..f470699a --- /dev/null +++ b/packages/wyatt_http_client/lib/src/middlewares/refresh_token_auth_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 . + +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); + +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 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; + accessToken = accessTokenParser(body); + + // Then modify current request with accessToken + final mutation = { + authenticationHeader: '$authenticationMethod $accessToken', + }; + final Map? headers = context.lastRequest?.headers + ?..addAll(mutation); + final newRequest = context.lastRequest?.copyWith( + unfreezedRequest: + context.lastRequest?.unfreezedRequest.copyWith(headers: headers), + ); + + return newRequest; + } + return null; + } + + Future retry(MiddlewareContext context) async { + // Retry + int attempt = 1; + while (attempt <= maxAttempts) { + // Delayed before retry + await Future.delayed(Delay.getRetryDelay(attempt)); + + final newRequest = await refresh(context); + if (newRequest != null) { + return newRequest; + } + attempt++; + } + return null; + } + + @override + Future 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 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 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; + 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; + } +} diff --git a/packages/wyatt_http_client/lib/src/middlewares/refresh_token_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/refresh_token_middleware.dart deleted file mode 100644 index 02391f61..00000000 --- a/packages/wyatt_http_client/lib/src/middlewares/refresh_token_middleware.dart +++ /dev/null @@ -1,213 +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 . - -import 'dart:convert'; - -import 'package:http/http.dart'; -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'; -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'; - -typedef TokenParser = String Function(Map); - -class RefreshTokenMiddleware extends 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 maxRetries; - - RefreshTokenMiddleware({ - 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.maxRetries = 3, - }); - - @override - String getName() => 'RefreshToken'; - - @override - Future onRequest(MiddlewareRequest request) async { - print( - '${getName()}::OnRequest: accessToken: $accessToken', - ); - if (request.context.originalRequest?.unfreezedRequest.url == - Uri.parse(authorizationEndpoint)) { - return super.onRequest(request); - } - if (accessToken != null) { - // Modify header with accessToken - var newReq = request.unfreezedRequest; - final mutation = { - authenticationHeader: '$authenticationMethod $accessToken', - }; - Map? headers = newReq.headers; - if (headers != null) { - headers.addAll(mutation); - } else { - headers = mutation; - } - newReq = newReq.copyWith(headers: headers); - request.updateUnfreezedRequest(newReq); - return super.onRequest(request); - } - if (refreshToken != null) { - // Refresh accessToken with refreshToken before perform request - final subPipeline = request.context.pipeline.fromUntil(this); - final httpClient = MiddlewareClient( - pipeline: subPipeline, - inner: client, - ); - final Map headers = { - authenticationHeader: '$authenticationHeader $refreshToken', - }; - final response = - await httpClient.get(Uri.parse(tokenEndpoint), headers: headers); - final status = HttpStatus.from(response.statusCode); - if (status.isSuccess()) { - final body = jsonDecode(response.body) as Map; - accessToken = accessTokenParser(body); - - // Then modify current request with accessToken - var newReq = request.unfreezedRequest; - final mutation = { - authenticationHeader: '$authenticationMethod $accessToken', - }; - Map? headers = newReq.headers; - if (headers != null) { - headers.addAll(mutation); - } else { - headers = mutation; - } - newReq = newReq.copyWith(headers: headers); - request.updateUnfreezedRequest(newReq); - } else { - // Retry - int retries = 0; - while (retries < maxRetries) { - final Map headers = { - authenticationHeader: '$authenticationHeader $refreshToken', - }; - final response = - await httpClient.get(Uri.parse(tokenEndpoint), headers: headers); - final status = HttpStatus.from(response.statusCode); - if (status.isSuccess()) { - final body = jsonDecode(response.body) as Map; - accessToken = accessTokenParser(body); - - // Then modify current request with accessToken - var newReq = request.unfreezedRequest; - final mutation = { - authenticationHeader: '$authenticationMethod $accessToken', - }; - Map? headers = newReq.headers; - if (headers != null) { - headers.addAll(mutation); - } else { - headers = mutation; - } - newReq = newReq.copyWith(headers: headers); - request.updateUnfreezedRequest(newReq); - break; - } - retries++; - } - } - return super.onRequest(request); - } - // Pass - return super.onRequest(request); - } - - @override - Future onResponse(MiddlewareResponse response) async { - final res = await super.onResponse(response); - final status = HttpStatus.from(res.httpResponse.statusCode); - if (res.context.originalRequest?.unfreezedRequest.url == - Uri.parse(authorizationEndpoint)) { - if (status.isSuccess()) { - final body = jsonDecode((res.httpResponse as Response).body) as Map; - final accessToken = accessTokenParser(body); - final refreshToken = refreshTokenParser(body); - - if (accessToken.isNotEmpty) { - this.accessToken = accessToken; - } - if (refreshToken.isNotEmpty) { - this.refreshToken = refreshToken; - } - } - return res; - } - if (status == unauthorized) { - print( - '${getName()}::OnResponse: $unauthorized', - ); - // Refresh token then retry - final subPipeline = res.context.pipeline.fromUntil(this); - final httpClient = MiddlewareClient( - pipeline: subPipeline, - inner: client, - ); - final Map headers = { - authenticationHeader: '$authenticationHeader $refreshToken', - }; - final response = - await httpClient.get(Uri.parse(tokenEndpoint), headers: headers); - final refreshstatus = HttpStatus.from(response.statusCode); - if (refreshstatus.isSuccess()) { - print( - '${getName()}::OnResponse: refresh successfuly', - ); - final body = jsonDecode(response.body) as Map; - accessToken = accessTokenParser(body); - - // Then modify current request with accessToken - final midReq = res.middlewareRequest; - final newReq = midReq.httpRequest; - final mutation = { - authenticationHeader: '$authenticationMethod $accessToken', - }; - Map? headers = newReq.headers; - if (headers != null) { - headers.addAll(mutation); - } else { - headers = mutation; - } - midReq.updateHttpRequest(headers: headers); - final newRes = await httpClient.send(midReq.httpRequest); - return res.copyWith(httpResponse: res as Response); - } - } - return res; - } -} diff --git a/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart index 6118e03b..8e2d2637 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/simple_logger_middleware.dart @@ -14,31 +14,41 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +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/pipeline.dart'; -class SimpleLoggerMiddleware extends Middleware { +class SimpleLoggerMiddleware + with OnRequestMiddleware, OnResponseMiddleware + implements Middleware { @override String getName() => 'SimpleLogger'; @override - Future onRequest(MiddlewareRequest request) { + Future onRequest( + MiddlewareContext context, + MiddlewareRequest request, + ) async { print( - '${getName()}::OnRequest: ${request.httpRequest.method} ' - '${request.httpRequest.url}\n${request.unfreezedRequest.headers}' - '\n>> ${request.unfreezedRequest.body}', + '${getName()}::OnRequest\n' + '>> ${request.method} ${request.url}\n' + '>> Headers: ${request.headers}\n>> Body: ${request.encodedBody}', ); - return super.onRequest(request); + return request; } @override - Future onResponse(MiddlewareResponse response) async { - final res = await super.onResponse(response); + Future onResponse( + MiddlewareContext context, + MiddlewareResponse response, + ) async { print( - '${getName()}::OnResponse: ${res.httpResponse.statusCode} -> ' - 'received ${res.httpResponse.contentLength} bytes', + '${getName()}::OnResponse\n' + '>> Status: ${response.status.name.toUpperCase()}\n' + '>> Length: ${response.contentLength ?? '0'} bytes', + // '>> Body: ${response.body}', ); - return res; + return response; } } diff --git a/packages/wyatt_http_client/lib/src/authentication/unsafe_authentication_client.dart b/packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart similarity index 53% rename from packages/wyatt_http_client/lib/src/authentication/unsafe_authentication_client.dart rename to packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart index 650497f1..f929f623 100644 --- a/packages/wyatt_http_client/lib/src/authentication/unsafe_authentication_client.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/unsafe_auth_middleware.dart @@ -14,30 +14,43 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -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 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; } } diff --git a/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart b/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart index b83e995f..c52f3fdf 100644 --- a/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart +++ b/packages/wyatt_http_client/lib/src/middlewares/uri_prefix_middleware.dart @@ -14,11 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +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/pipeline.dart'; import 'package:wyatt_http_client/src/utils/protocols.dart'; -class UriPrefixMiddleware extends Middleware { +class UriPrefixMiddleware with OnRequestMiddleware implements Middleware { final Protocols protocol; final String? authority; @@ -31,11 +32,17 @@ class UriPrefixMiddleware extends Middleware { String getName() => 'UriPrefix'; @override - Future onRequest(MiddlewareRequest request) { - final Uri uri = - Uri.parse('${protocol.scheme}$authority${request.httpRequest.url}'); - print('${getName()}::OnRequest: ${request.httpRequest.url} -> $uri'); - request.updateHttpRequest(url: uri); - return super.onRequest(request); + Future onRequest( + MiddlewareContext context, + MiddlewareRequest request, + ) async { + final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); + print( + '${getName()}::OnRequest\n' + '>> From: ${request.url}\n' + '>> To: $uri', + ); + request.modifyRequest(request.unfreezedRequest.copyWith(url: uri)); + return request; } } diff --git a/packages/wyatt_http_client/lib/src/mixins/body_transformer.dart b/packages/wyatt_http_client/lib/src/mixins/body_transformer.dart deleted file mode 100644 index b5931d48..00000000 --- a/packages/wyatt_http_client/lib/src/mixins/body_transformer.dart +++ /dev/null @@ -1,83 +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 . - -import 'dart:convert'; - -import 'package:http/http.dart'; - -mixin BodyTransformer on Client { - Object? bodyMutator(Object? body); - - @override - Future post( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.post( - url, - headers: headers, - body: bodyMutator(body), - encoding: encoding, - ); - } - - @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.put( - url, - headers: headers, - body: bodyMutator(body), - encoding: encoding, - ); - } - - @override - Future patch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.patch( - url, - headers: headers, - body: bodyMutator(body), - encoding: encoding, - ); - } - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.delete( - url, - headers: headers, - body: bodyMutator(body), - encoding: encoding, - ); - } -} diff --git a/packages/wyatt_http_client/lib/src/mixins/headers_transformer.dart b/packages/wyatt_http_client/lib/src/mixins/headers_transformer.dart deleted file mode 100644 index 42cc270b..00000000 --- a/packages/wyatt_http_client/lib/src/mixins/headers_transformer.dart +++ /dev/null @@ -1,131 +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 . - -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:http/http.dart'; - -mixin HeadersTransformer on Client { - Map? headersMutator( - Object? body, - Map? headers, - ); - - @override - Future head( - Uri url, { - Map? headers, - }) { - return super.head( - url, - headers: headersMutator(null, headers), - ); - } - - @override - Future get( - Uri url, { - Map? headers, - }) { - return super.get( - url, - headers: headersMutator(null, headers), - ); - } - - @override - Future post( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.post( - url, - headers: headersMutator(body, headers), - body: body, - encoding: encoding, - ); - } - - @override - Future put( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.put( - url, - headers: headersMutator(body, headers), - body: body, - encoding: encoding, - ); - } - - @override - Future patch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.patch( - url, - headers: headersMutator(body, headers), - body: body, - encoding: encoding, - ); - } - - @override - Future delete( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) { - return super.delete( - url, - headers: headersMutator(body, headers), - body: body, - encoding: encoding, - ); - } - - @override - Future read( - Uri url, { - Map? headers, - }) { - return super.read( - url, - headers: headersMutator(null, headers), - ); - } - - @override - Future readBytes( - Uri url, { - Map? headers, - }) { - return super.readBytes( - url, - headers: headersMutator(null, headers), - ); - } -} diff --git a/packages/wyatt_http_client/lib/src/mixins/oauth2_transformer.dart b/packages/wyatt_http_client/lib/src/mixins/oauth2_transformer.dart deleted file mode 100644 index a1abf27a..00000000 --- a/packages/wyatt_http_client/lib/src/mixins/oauth2_transformer.dart +++ /dev/null @@ -1,33 +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 . - -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/implemented_base_client.dart'; - -mixin Oauth2Transformer on ImplementedBaseClient { - late final String authorizationEndpoint; - late final String tokenEndpoint; - String? accessToken; - String? refreshToken; - - BaseRequest requestAuthenticator(BaseRequest request); - - @override - Future send(BaseRequest request) { - final req = requestAuthenticator(request); - return super.send(req); - } -} diff --git a/packages/wyatt_http_client/lib/src/models/middleware_context.dart b/packages/wyatt_http_client/lib/src/models/middleware_context.dart index e00f6395..19877109 100644 --- a/packages/wyatt_http_client/lib/src/models/middleware_context.dart +++ b/packages/wyatt_http_client/lib/src/models/middleware_context.dart @@ -15,34 +15,48 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +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() => 'MiddlewareContext(pipeline: $pipeline, ' - 'originalRequest: $originalRequest, originalResponse: $originalResponse)'; + String toString() { + return 'MiddlewareContext(pipeline: $pipeline, client: $client, originalRequest: $originalRequest, lastRequest: $lastRequest, originalResponse: $originalResponse, lastResponse: $lastResponse)'; + } } diff --git a/packages/wyatt_http_client/lib/src/models/middleware_request.dart b/packages/wyatt_http_client/lib/src/models/middleware_request.dart index b1355774..42c9ac34 100644 --- a/packages/wyatt_http_client/lib/src/models/middleware_request.dart +++ b/packages/wyatt_http_client/lib/src/models/middleware_request.dart @@ -15,83 +15,68 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:http/http.dart'; +import 'dart:convert'; -import 'package:wyatt_http_client/src/models/middleware_context.dart'; +import 'package:http/http.dart'; import 'package:wyatt_http_client/src/models/unfreezed_request.dart'; -import 'package:wyatt_http_client/src/utils/utils.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; - MiddlewareContext context; + Request _httpRequest; + + Request get request => _httpRequest; + + // Proxy + String get method => _httpRequest.method; + Uri get url => _httpRequest.url; + Map get headers => _httpRequest.headers; + Encoding get encoding => _httpRequest.encoding; + String get encodedBody => _httpRequest.body; + Object? get body => unfreezedRequest.body; MiddlewareRequest({ required this.unfreezedRequest, - required this.httpRequest, - required this.context, - }) { - context = context.copyWith(originalRequest: this); - } + }) : _httpRequest = Request(unfreezedRequest.method, unfreezedRequest.url); MiddlewareRequest copyWith({ UnfreezedRequest? unfreezedRequest, - Request? httpRequest, - MiddlewareContext? context, }) { return MiddlewareRequest( unfreezedRequest: unfreezedRequest ?? this.unfreezedRequest, - httpRequest: httpRequest ?? this.httpRequest, - context: context ?? this.context, ); } - void updateUnfreezedRequest(UnfreezedRequest unfreezedRequest) { - final request = httpRequest; - if (unfreezedRequest.headers != null) { - request.headers.addAll(unfreezedRequest.headers!); - } - if (unfreezedRequest.encoding != null) { - request.encoding = unfreezedRequest.encoding!; - } + void modifyRequest(UnfreezedRequest unfreezedRequest) { + String? _body; if (unfreezedRequest.body != null) { final body = unfreezedRequest.body; if (body is String) { - request.body = body; + _body = body; } else if (body is List) { - request.bodyBytes = body.cast(); + _body = String.fromCharCodes(body.cast()); } else if (body is Map) { - request.bodyFields = body.cast(); - } else { - throw ArgumentError('Invalid request body "$body".'); + _body = Convert.mapToQuery(body.cast()); } } + _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; - httpRequest = request; } - void updateHttpRequest({ - String? method, - Uri? url, - Map? headers, - int? maxRedirects, - bool? followRedirects, - bool? persistentConnection, - String? body, - }) { - httpRequest = Utils.copyRequestWith( - httpRequest, - method: method, - url: url, - headers: headers, - maxRedirects: maxRedirects, - followRedirects: followRedirects, - persistentConnection: persistentConnection, - body: body, - ) as Request; + void apply() { + modifyRequest(unfreezedRequest); } @override - String toString() => 'MiddlewareRequest(unfreezedRequest: ' - '$unfreezedRequest, httpRequest: $httpRequest, context: $context)'; + String toString() => 'MiddlewareRequest(unfreezedRequest: $unfreezedRequest)'; } diff --git a/packages/wyatt_http_client/lib/src/models/middleware_response.dart b/packages/wyatt_http_client/lib/src/models/middleware_response.dart index 0645e776..9404fa3a 100644 --- a/packages/wyatt_http_client/lib/src/models/middleware_response.dart +++ b/packages/wyatt_http_client/lib/src/models/middleware_response.dart @@ -16,35 +16,37 @@ // along with this program. If not, see . 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/utils/http_status.dart'; class MiddlewareResponse { BaseResponse httpResponse; - MiddlewareRequest middlewareRequest; - MiddlewareContext context; + + // 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 get headers => httpResponse.headers; MiddlewareResponse({ required this.httpResponse, - required this.middlewareRequest, - required this.context, - }) { - context = context.copyWith(originalResponse: this); - } + }); MiddlewareResponse copyWith({ BaseResponse? httpResponse, - MiddlewareRequest? middlewareRequest, - MiddlewareContext? context, }) { return MiddlewareResponse( httpResponse: httpResponse ?? this.httpResponse, - middlewareRequest: middlewareRequest ?? this.middlewareRequest, - context: context ?? this.context, ); } @override - String toString() => 'MiddlewareResponse(httpResponse: $httpResponse, ' - 'middlewareRequest: $middlewareRequest, context: $context)'; + String toString() => + 'MiddlewareResponse(httpResponse: $httpResponse)'; } diff --git a/packages/wyatt_http_client/lib/src/mixins/request_transformer.dart b/packages/wyatt_http_client/lib/src/models/models.dart similarity index 67% rename from packages/wyatt_http_client/lib/src/mixins/request_transformer.dart rename to packages/wyatt_http_client/lib/src/models/models.dart index 24db0982..4170d54b 100644 --- a/packages/wyatt_http_client/lib/src/mixins/request_transformer.dart +++ b/packages/wyatt_http_client/lib/src/models/models.dart @@ -1,27 +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 . -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/implemented_base_client.dart'; - -mixin RequestTransformer on ImplementedBaseClient { - BaseRequest requestMutator(BaseRequest request); - - @override - Future send(BaseRequest request) { - return super.send(requestMutator(request)); - } -} +export 'middleware_context.dart'; +export 'middleware_request.dart'; +export 'middleware_response.dart'; +export 'unfreezed_request.dart'; diff --git a/packages/wyatt_http_client/lib/src/pipeline.dart b/packages/wyatt_http_client/lib/src/pipeline.dart index 0d2b1477..aa0b8a1b 100644 --- a/packages/wyatt_http_client/lib/src/pipeline.dart +++ b/packages/wyatt_http_client/lib/src/pipeline.dart @@ -14,140 +14,94 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/middleware_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/models/middleware_response.dart'; -part 'middleware.dart'; -part 'middleware_node.dart'; - class Pipeline { - int _length = 0; - late final MiddlewareNode begin; - late final MiddlewareNode end; + final List _middlewares; - MiddlewareNode get first => begin.child; - MiddlewareNode get last => end.parent; - bool get isEmpty => _length == 0; - bool get isNotEmpty => !isEmpty; - int get length => _length; + int get length => _middlewares.length; - Pipeline() { - _initialize(); + Pipeline() : _middlewares = []; + Pipeline.fromIterable(Iterable middlewares) + : _middlewares = middlewares.toList(); + + /// Add a [Middleware] to this [Pipeline] + Pipeline addMiddleware(Middleware middleware) { + _middlewares.add(middleware); + return this; } - Pipeline.fromIterable(Iterable list) { - _initialize(); - MiddlewareNode parent = begin; - for (final element in list) { - if (element != null) { - parent = parent.insertAfter(element); + /// Create new [Pipeline] from the start or end to a specified [Middleware]. + Pipeline sub( + Middleware middleware, { + bool include = false, + bool fromEnd = false, + }) { + final nodes = []; + final list = fromEnd ? _middlewares.reversed : _middlewares; + for (final m in list) { + if (m != middleware) { + nodes.add(m); } - } - } - - Pipeline.from(Pipeline pipeline) - : this.fromIterable(pipeline.middlewares.map((node) => node.middleware)); - - void _initialize() { - _length = 0; - begin = MiddlewareNode._begin(this); - end = MiddlewareNode._end(this); - begin._child = end; - end._parent = begin; - } - - Iterable get middlewares sync* { - for (var middleware = first; - middleware != end; - middleware = middleware.child) { - yield middleware; - } - } - - int indexOf(MiddlewareNode node) { - int i = -1; - int j = -1; - for (final element in middlewares) { - j++; - if (element == node) { - i = j; - continue; - } - } - return i; - } - - int indexOfChild(Middleware middleware) { - int i = -1; - int j = -1; - for (final element in middlewares) { - j++; - if (element.middleware == middleware) { - i = j; - continue; - } - } - return i; - } - - /// Create new [Pipeline] from first [Middleware] to the specified one. - Pipeline fromUntil(Middleware middleware, {bool include = false}) { - final nodes = []; - for (final element in middlewares) { - if (element.middleware != middleware) { - nodes.add(element.middleware); - } - if (element.middleware == middleware) { + if (m == middleware) { if (include) { - nodes.add(element.middleware); + nodes.add(m); } break; } } - return Pipeline.fromIterable(nodes); + return Pipeline.fromIterable(fromEnd ? nodes.reversed : nodes); } - Pipeline addMiddleware(Middleware middleware) { - last.insertAfter(middleware); - return this; - } - - void setClient(MiddlewareClient? client) { - for (var node = first; node != end; node = node.child) { - node.middleware?.httpClient = client; - } - } - - Future onRequest(MiddlewareRequest request) async { - MiddlewareRequest req = request; - for (var node = first; node != end; node = node.child) { - req = await node.middleware?.onRequest(req) ?? req; + Future 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 onResponse(MiddlewareResponse response) async { + Future onResponse( + MiddlewareContext context, + MiddlewareResponse response, + ) async { + print('\n\nNEW RESPONSE\n'); MiddlewareResponse res = response; - for (var node = last; node != begin; node = node.parent) { - res = await node.middleware?.onResponse(res) ?? res; + 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; } - String getLogic() { + @override + String toString() { final req = []; final res = []; - for (final m in middlewares) { - req.add('${m.middleware}'); - res.insert(0, '${m.middleware}'); + 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(' -> ')}'; } - - @override - String toString() { - return getLogic(); - } } diff --git a/packages/wyatt_http_client/lib/src/rest_client.dart b/packages/wyatt_http_client/lib/src/rest_client.dart deleted file mode 100644 index 1925eef1..00000000 --- a/packages/wyatt_http_client/lib/src/rest_client.dart +++ /dev/null @@ -1,190 +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 . - -import 'dart:convert'; - -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/implemented_base_client.dart'; -import 'package:wyatt_http_client/src/mixins/body_transformer.dart'; -import 'package:wyatt_http_client/src/mixins/headers_transformer.dart'; -import 'package:wyatt_http_client/src/mixins/request_transformer.dart'; -import 'package:wyatt_http_client/src/utils/protocols.dart'; -import 'package:wyatt_http_client/src/utils/utils.dart'; - -class RestClient extends ImplementedBaseClient - with BodyTransformer, HeadersTransformer, RequestTransformer { - final Protocols protocol; - final String? authority; - - RestClient({ - this.protocol = Protocols.https, - this.authority = '', - super.inner, - }); - - @override - Object? bodyMutator(Object? body) { - print( - 'RestClient::bodyMutator -> encode in json if body is Map: ${body is Map}', - ); - if (body is Map) { - return jsonEncode(body); - } - return body; - } - - @override - Map? headersMutator( - Object? body, - Map? headers, - ) { - print( - 'RestClient::headersMutator -> add json content-type if body is Map: ${body is Map}', - ); - final mutation = { - 'content-type': 'application/json; charset=utf-8', - }; - if (body is Map) { - if (headers != null) { - headers.addAll(mutation); - return headers; - } else { - return mutation; - } - } - return headers; - } - - @override - BaseRequest requestMutator(BaseRequest request) { - print( - 'RestClient::requestMutator -> add prefix path: ${protocol.scheme}$authority', - ); - final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); - return Utils.copyRequestWith(request, url: uri); - } -} - -// class RestClient extends BaseClient { -// final Protocols protocol; -// final String? authority; - -// final Client _inner; - -// RestClient({ -// this.protocol = Protocols.https, -// this.authority = '', -// Client? inner, -// }) : _inner = inner ?? Client(); - -// String? forceJson(Object? body) { -// String? b; -// if (body != null && body is Map) { -// b = jsonEncode(body); -// } -// return b; -// } - -// Map? forceJsonHeader( -// Object? body, Map? headers,) { -// final Map h = headers ?? {}; -// if (body != null && body is Map) { -// h['Content-Type'] = 'application/json'; -// } -// return h; -// } - -// // @override -// // Future post( -// // Uri url, { -// // Map? headers, -// // Object? body, -// // Encoding? encoding, -// // }) { -// // final b = forceJson(body) ?? body; -// // final h = forceJsonHeader(body, headers) ?? headers; -// // print(b); -// // print(h); -// // return super.post( -// // url, -// // headers: h, -// // body: b, -// // encoding: encoding, -// // ); -// // } - -// @override -// Future put( -// Uri url, { -// Map? headers, -// Object? body, -// Encoding? encoding, -// }) { -// final b = forceJson(body) ?? body; -// final h = forceJsonHeader(body, headers) ?? headers; -// return super.put( -// url, -// headers: h, -// body: b, -// encoding: encoding, -// ); -// } - -// @override -// Future patch( -// Uri url, { -// Map? headers, -// Object? body, -// Encoding? encoding, -// }) { -// final b = forceJson(body) ?? body; -// final h = forceJsonHeader(body, headers) ?? headers; -// return super.patch( -// url, -// headers: h, -// body: b, -// encoding: encoding, -// ); -// } - -// @override -// Future delete( -// Uri url, { -// Map? headers, -// Object? body, -// Encoding? encoding, -// }) { -// final b = forceJson(body) ?? body; -// final h = forceJsonHeader(body, headers) ?? headers; -// return super.delete( -// url, -// headers: h, -// body: b, -// encoding: encoding, -// ); -// } - -// @override -// Future send(BaseRequest request) { -// final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}'); -// return _inner.send( -// Utils.copyRequestWith( -// request, -// url: uri, -// ), -// ); -// } -// } diff --git a/packages/wyatt_http_client/lib/src/utils/convert.dart b/packages/wyatt_http_client/lib/src/utils/convert.dart index 30f1718d..96746448 100644 --- a/packages/wyatt_http_client/lib/src/utils/convert.dart +++ b/packages/wyatt_http_client/lib/src/utils/convert.dart @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import 'dart:convert'; + class Convert { static String toHex(List bytes, {bool upperCase = false}) { final buffer = StringBuffer(); @@ -29,6 +31,15 @@ class Convert { return buffer.toString(); } } + + static String mapToQuery(Map map, {Encoding? encoding}) { + final pairs = >[]; + 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 { diff --git a/packages/wyatt_http_client/lib/src/authentication/interfaces/oauth2_client.dart b/packages/wyatt_http_client/lib/src/utils/delay.dart similarity index 52% rename from packages/wyatt_http_client/lib/src/authentication/interfaces/oauth2_client.dart rename to packages/wyatt_http_client/lib/src/utils/delay.dart index 5704788c..af89f3ac 100644 --- a/packages/wyatt_http_client/lib/src/authentication/interfaces/oauth2_client.dart +++ b/packages/wyatt_http_client/lib/src/utils/delay.dart @@ -14,22 +14,23 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -import 'package:http/http.dart'; -import 'package:wyatt_http_client/src/authentication/interfaces/header_authentication_client.dart'; +import 'dart:core'; +import 'dart:math'; -typedef TokenParser = String Function(Map); +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); -abstract class Oauth2Client extends HeaderAuthenticationClient { - Oauth2Client(super.inner); - - Future refresh() { - return Future.value(); - } - - Future authorize( - Map body, { - Map? headers, - }) { - return Future.value(); + 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; } } diff --git a/packages/wyatt_http_client/lib/src/utils/protocols.dart b/packages/wyatt_http_client/lib/src/utils/protocols.dart index 6e483a42..97b9d5f3 100644 --- a/packages/wyatt_http_client/lib/src/utils/protocols.dart +++ b/packages/wyatt_http_client/lib/src/utils/protocols.dart @@ -18,6 +18,5 @@ enum Protocols { http, https; - String get name => toString().split('.').last; String get scheme => '$name://'; } diff --git a/packages/wyatt_http_client/lib/src/utils/request_utils.dart b/packages/wyatt_http_client/lib/src/utils/request_utils.dart new file mode 100644 index 00000000..b50c0063 --- /dev/null +++ b/packages/wyatt_http_client/lib/src/utils/request_utils.dart @@ -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 . + +import 'package:http/http.dart'; + +abstract class RequestUtils { + static Request _copyNormalRequestWith( + Request original, { + String? method, + Uri? url, + Map? 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? 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}', + ); + } + } +} diff --git a/packages/wyatt_http_client/lib/src/utils/utils.dart b/packages/wyatt_http_client/lib/src/utils/utils.dart index 9a7a4d20..5dfc082c 100644 --- a/packages/wyatt_http_client/lib/src/utils/utils.dart +++ b/packages/wyatt_http_client/lib/src/utils/utils.dart @@ -1,89 +1,23 @@ // 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 . -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? 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? 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'; diff --git a/packages/wyatt_http_client/lib/wyatt_http_client.dart b/packages/wyatt_http_client/lib/wyatt_http_client.dart index c71cd239..e728cf6a 100644 --- a/packages/wyatt_http_client/lib/wyatt_http_client.dart +++ b/packages/wyatt_http_client/lib/wyatt_http_client.dart @@ -1,17 +1,24 @@ // 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 . 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';