Feature/middlewares #9

Merged
hugo merged 4 commits from Feature/middlewares into master 2022-06-24 14:00:03 +00:00
12 changed files with 820 additions and 0 deletions
Showing only changes of commit 58d3b70bee - Show all commits

View File

@ -0,0 +1,149 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_http_client/src/middleware_client.dart';
import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart';
import 'package:wyatt_http_client/src/middlewares/refresh_token_middleware.dart';
import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart';
import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart';
import 'package:wyatt_http_client/src/pipeline.dart';
import 'package:wyatt_http_client/src/utils/protocols.dart';
// class RequestMutatorMiddleware implements Middleware {
// @override
// Middleware? parent;
// @override
// Middleware? child;
// RequestMutatorMiddleware({
// this.parent,
// this.child,
// });
// @override
// BaseRequest onRequest(BaseRequest request) {
// print('RequestMutator::OnRequest: ${request.method} -> ${request.url}');
// return child?.onRequest(request) ?? request;
// }
// @override
// BaseResponse onResponse(BaseResponse response) {
// final res = child?.onResponse(response) ?? response;
// print(
// 'RequestMutator::OnResponse: ${res.statusCode} -> ${res.contentLength} bytes',
// );
// return res;
// }
// }
// typedef Middleware = Handler Function(Handler innerHandler);
// Middleware createMiddleware({
// FutureOr<Response?> Function(Request)? requestHandler,
// FutureOr<Response> Function(Response)? responseHandler,
// FutureOr<Response> Function(Object error, StackTrace)? errorHandler,
// }) {
// requestHandler ??= (request) => null;
// responseHandler ??= (response) => response;
// FutureOr<Response> Function(Object, StackTrace)? onError;
// if (errorHandler != null) {
// onError = (error, stackTrace) {
// if (error is Exception) throw error;
// return errorHandler(error, stackTrace);
// };
// }
// return (Handler innerHandler) {
// return (request) {
// return Future.sync(() => requestHandler!(request)).then((response) {
// if (response != null) return response;
// return Future.sync(() => innerHandler(request))
// .then((response) => responseHandler!(response), onError: onError);
// });
// };
// };
// }
// extension MiddlewareX on Middleware {
// Middleware addMiddleware(Middleware other) =>
// (Handler handler) => this(other(handler));
// Handler addHandler(Handler handler) => this(handler);
// }
// typedef Handler = FutureOr<Response> Function(Request request);
// final headerMutator = createMiddleware(
// responseHandler: (response) {
// print(response.headers);
// return response;
// },);
// class Pipeline {
// const Pipeline();
// Pipeline addMiddleware(Middleware middleware) =>
// _Pipeline(middleware, addHandler);
// Handler addHandler(Handler handler) => handler;
// Middleware get middleware => addHandler;
// }
// class _Pipeline extends Pipeline {
// final Middleware _middleware;
// final Middleware _parent;
// _Pipeline(this._middleware, this._parent);
// @override
// Handler addHandler(Handler handler) => _parent(_middleware(handler));
// }
Future<void> main(List<String> args) async {
final Pipeline pipeline1 = Pipeline()
.addMiddleware(SimpleLoggerMiddleware())
.addMiddleware(
UriPrefixMiddleware(
protocol: Protocols.http,
authority: 'localhost:80',
),
)
.addMiddleware(BodyToJsonMiddleware());
final Pipeline pipeline2 = Pipeline().addMiddleware(
RefreshTokenMiddleware(
authorizationEndpoint:
'http://localhost:80/api/v1/account/test?action=authorize',
tokenEndpoint: 'http://localhost:80/api/v1/account/test?action=refresh',
innerClientMiddlewares: pipeline1.middleware,
),
);
final Pipeline pipeline = pipeline1 + pipeline2;
print(pipeline.getLogic());
final client = MiddlewareClient(pipeline);
final r = await client.post(
Uri.parse('/api/v1/account/test'),
body: <String, String>{
'email': 'test@test.fr',
},
);
}

View File

@ -0,0 +1,77 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:http/http.dart';
import 'package:wyatt_http_client/src/middleware_client.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
import 'package:wyatt_http_client/src/models/middleware_response.dart';
class Middleware {
Middleware? child;
MiddlewareClient? _client;
Middleware({
this.child,
});
Middleware._({
this.child,
MiddlewareClient? client,
}) : _client = client;
String getName() => 'Middleware';
void setClient(MiddlewareClient? client) {
_client = client;
child?.setClient(client);
}
Client? getClient() {
return _client?.inner;
}
Middleware deepCopy() {
if (child != null) {
return Middleware._(child: child?.deepCopy(), client: _client);
} else {
return Middleware._(client: _client);
}
}
void addChild(Middleware middleware) {
if (child != null) {
child?.addChild(middleware);
} else {
child = middleware;
}
}
MiddlewareRequest onRequest(
MiddlewareRequest request,
) {
return child?.onRequest(request) ?? request;
}
MiddlewareResponse onResponse(MiddlewareResponse response) {
return child?.onResponse(response) ?? response;
}
@override
String toString() {
return getName();
}
}

View File

@ -0,0 +1,117 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:convert';
import 'package:http/http.dart';
import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
import 'package:wyatt_http_client/src/models/middleware_response.dart';
import 'package:wyatt_http_client/src/models/unfreezed_request.dart';
import 'package:wyatt_http_client/src/pipeline.dart';
class MiddlewareClient extends BaseClient {
final Client inner;
final Middleware middleware;
final Pipeline pipeline;
MiddlewareClient(
this.pipeline, {
Middleware? middleware,
Client? inner,
}) : inner = inner ?? Client(),
middleware = middleware ?? pipeline.middleware {
this.middleware.setClient(this);
}
@override
Future<Response> head(Uri url, {Map<String, String>? headers}) =>
_sendUnstreamed('HEAD', url, headers);
@override
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
_sendUnstreamed('GET', url, headers);
@override
Future<Response> post(
Uri url, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
}) =>
_sendUnstreamed('POST', url, headers, body, encoding);
@override
Future<Response> put(
Uri url, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
}) =>
_sendUnstreamed('PUT', url, headers, body, encoding);
@override
Future<Response> patch(
Uri url, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
}) =>
_sendUnstreamed('PATCH', url, headers, body, encoding);
@override
Future<Response> delete(
Uri url, {
Map<String, String>? headers,
Object? body,
Encoding? encoding,
}) =>
_sendUnstreamed('DELETE', url, headers, body, encoding);
@override
Future<StreamedResponse> send(BaseRequest request) {
return inner.send(request);
}
Future<Response> _sendUnstreamed(
String method,
Uri url,
Map<String, String>? headers, [
Object? body,
Encoding? encoding,
]) async {
final modifiedRequest = middleware.onRequest(
MiddlewareRequest(
unfreezedRequest: UnfreezedRequest(
method: method,
url: url,
headers: headers,
body: body,
encoding: encoding,
),
httpRequest: Request(method, url),
),
);
final res = await Response.fromStream(
await send(modifiedRequest.httpRequest),
);
final response =
middleware.onResponse(MiddlewareResponse(httpResponse: res));
return response.httpResponse as Response;
}
}

View File

@ -0,0 +1,52 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:convert';
import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
class BodyToJsonMiddleware extends Middleware {
BodyToJsonMiddleware({
super.child,
});
@override
String getName() => 'BodyToJsonMiddleware';
@override
MiddlewareRequest onRequest(MiddlewareRequest request) {
print(
'BodyToJson::OnRequest: 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<String, String>? headers = newReq.headers;
if (headers != null) {
headers.addAll(mutation);
} else {
headers = mutation;
}
newReq = newReq.copyWith(body: jsonEncode(newReq.body), headers: headers);
request.updateUnfreezedRequest(newReq);
}
return super.onRequest(request);
}
}

View File

@ -0,0 +1,26 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_http_client/src/middleware.dart';
class DefaultMiddleware extends Middleware {
DefaultMiddleware({
super.child,
});
@override
String getName() => 'DefaultMiddleware';
}

View File

@ -0,0 +1,72 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/middleware_client.dart';
import 'package:wyatt_http_client/src/middlewares/default_middleware.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
import 'package:wyatt_http_client/src/pipeline.dart';
class RefreshTokenMiddleware extends Middleware {
final String authorizationEndpoint;
final String tokenEndpoint;
String? accessToken;
String? refreshToken;
Middleware innerClientMiddlewares;
RefreshTokenMiddleware({
required this.authorizationEndpoint,
required this.tokenEndpoint,
Middleware? innerClientMiddlewares,
super.child,
}) : innerClientMiddlewares = innerClientMiddlewares ?? DefaultMiddleware();
@override
String getName() => 'RefreshTokenMiddleware';
@override
MiddlewareRequest onRequest(MiddlewareRequest request) {
print(
'RefreshToken::OnRequest: accessToken: $accessToken',
);
if (accessToken == null) {
// Refresh token
final pipeline = Pipeline().addMiddleware(innerClientMiddlewares);
print(pipeline.getLogic());
final client = MiddlewareClient(
pipeline,
inner: getClient(),
);
final _ = client.post(Uri.parse(tokenEndpoint));
}
var newReq = request.unfreezedRequest;
final mutation = {
'authorization': accessToken ?? '',
};
Map<String, String>? headers = newReq.headers;
if (headers != null) {
headers.addAll(mutation);
} else {
headers = mutation;
}
newReq = newReq.copyWith(headers: headers);
request.updateUnfreezedRequest(newReq);
return super.onRequest(request);
}
}

View File

@ -0,0 +1,47 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
import 'package:wyatt_http_client/src/models/middleware_response.dart';
class SimpleLoggerMiddleware extends Middleware {
SimpleLoggerMiddleware({
super.child,
});
@override
String getName() => 'SimpleLoggerMiddleware';
@override
MiddlewareRequest onRequest(MiddlewareRequest request) {
print(
'Logger::OnRequest: ${request.httpRequest.method} '
'${request.httpRequest.url}',
);
return super.onRequest(request);
}
@override
MiddlewareResponse onResponse(MiddlewareResponse response) {
final res = super.onResponse(response);
print(
'Logger::OnResponse: ${res.httpResponse.statusCode} -> '
'received ${res.httpResponse.contentLength} bytes',
);
return res;
}
}

View File

@ -0,0 +1,42 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/models/middleware_request.dart';
import 'package:wyatt_http_client/src/utils/protocols.dart';
class UriPrefixMiddleware extends Middleware {
final Protocols protocol;
final String? authority;
UriPrefixMiddleware({
required this.protocol,
required this.authority,
super.child,
});
@override
String getName() => 'UriPrefixMiddleware';
@override
MiddlewareRequest onRequest(MiddlewareRequest request) {
final Uri uri =
Uri.parse('${protocol.scheme}$authority${request.httpRequest.url}');
print('UriPrefix::OnRequest: ${request.httpRequest.url} -> $uri');
request.updateHttpRequest(url: uri);
return super.onRequest(request);
}
}

View File

@ -0,0 +1,89 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:http/http.dart';
import 'package:wyatt_http_client/src/models/unfreezed_request.dart';
import 'package:wyatt_http_client/src/utils/utils.dart';
class MiddlewareRequest {
UnfreezedRequest unfreezedRequest;
Request httpRequest;
MiddlewareRequest({
required this.unfreezedRequest,
required this.httpRequest,
});
MiddlewareRequest copyWith({
UnfreezedRequest? unfreezedRequest,
Request? httpRequest,
}) {
return MiddlewareRequest(
unfreezedRequest: unfreezedRequest ?? this.unfreezedRequest,
httpRequest: httpRequest ?? this.httpRequest,
);
}
void updateUnfreezedRequest(UnfreezedRequest unfreezedRequest) {
final request = httpRequest;
if (unfreezedRequest.headers != null) {
request.headers.addAll(unfreezedRequest.headers!);
}
if (unfreezedRequest.encoding != null) {
request.encoding = unfreezedRequest.encoding!;
}
if (unfreezedRequest.body != null) {
final body = unfreezedRequest.body;
if (body is String) {
request.body = body;
} else if (body is List) {
request.bodyBytes = body.cast<int>();
} else if (body is Map) {
request.bodyFields = body.cast<String, String>();
} else {
throw ArgumentError('Invalid request body "$body".');
}
}
this.unfreezedRequest = unfreezedRequest;
httpRequest = request;
}
void updateHttpRequest({
String? method,
Uri? url,
Map<String, String>? 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;
}
@override
String toString() => 'MiddlewareRequest(unfreezedRequest: '
'$unfreezedRequest, httpRequest: $httpRequest)';
}

View File

@ -0,0 +1,37 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:http/http.dart';
class MiddlewareResponse {
BaseResponse httpResponse;
MiddlewareResponse({
required this.httpResponse,
});
MiddlewareResponse copyWith({
BaseResponse? httpResponse,
}) {
return MiddlewareResponse(
httpResponse: httpResponse ?? this.httpResponse,
);
}
@override
String toString() => 'MiddlewareResponse(httpResponse: $httpResponse)';
}

View File

@ -0,0 +1,55 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:convert';
class UnfreezedRequest {
final String method;
final Uri url;
final Map<String, String>? headers;
final Object? body;
final Encoding? encoding;
UnfreezedRequest({
required this.method,
required this.url,
this.headers,
this.body,
this.encoding,
});
UnfreezedRequest copyWith({
String? method,
Uri? url,
Map<String, String>? headers,
Object? body,
Encoding? encoding,
}) {
return UnfreezedRequest(
method: method ?? this.method,
url: url ?? this.url,
headers: headers ?? this.headers,
body: body ?? this.body,
encoding: encoding ?? this.encoding,
);
}
@override
String toString() {
return 'UnfreezedRequest(method: $method, url: $url, headers: '
'$headers, body: $body, encoding: $encoding)';
}
}

View File

@ -0,0 +1,57 @@
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_http_client/src/middleware.dart';
import 'package:wyatt_http_client/src/middlewares/default_middleware.dart';
class Pipeline {
final Middleware _middleware;
Pipeline() : _middleware = DefaultMiddleware();
Pipeline addMiddleware(Middleware middleware) {
_middleware.addChild(middleware);
return this;
}
Middleware get middleware {
return _middleware;
}
Pipeline operator +(Pipeline other) {
final copy = _middleware.deepCopy()..addChild(other.middleware);
return Pipeline()..addMiddleware(copy);
}
String getLogic() {
final req = <String>[];
final res = <String>[];
Middleware? m = _middleware;
while (m != null) {
if (m is! DefaultMiddleware) {
req.add('$m');
res.insert(0, '$m');
}
m = m.child;
}
return '[Req] -> ${req.join(' -> ')}\n[Res] -> ${res.join(' -> ')}';
}
@override
String toString() {
return middleware.toString();
}
}