From 1bcdcc04c7a66ac8d21e9104029fa573b04b936c Mon Sep 17 00:00:00 2001 From: Alejandro Ulate Date: Thu, 11 Jul 2024 09:35:02 -0600 Subject: [PATCH] fix: update docs and improve multipart support --- README.md | 21 +++++++++------ example/lib/common.dart | 8 +++--- example/lib/multipart_app.dart | 5 ++-- example/lib/weather_app.dart | 14 +++++----- lib/extensions/multipart_request.dart | 35 +++++++++++++++--------- lib/extensions/streamed_request.dart | 38 ++++++++++++++------------- lib/http/intercepted_client.dart | 8 +++++- lib/models/interceptor_contract.dart | 4 +-- 8 files changed, 80 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index ca656b0..2974285 100644 --- a/README.md +++ b/README.md @@ -69,14 +69,16 @@ import 'package:http_interceptor/http_interceptor.dart'; In order to implement `http_interceptor` you need to implement the `InterceptorContract` and create your own interceptor. This abstract class has two methods: `interceptRequest`, which triggers before the http request is called; and `interceptResponse`, which triggers after the request is called, it has a response attached to it which the corresponding to said request. You could use this to do logging, adding headers, error handling, or many other cool stuff. It is important to note that after you proccess the request/response objects you need to return them so that `http` can continue the execute. +`interceptRequest` and `interceptResponse` use `FutureOr` syntax, which makes it easier to support both synchronous and asynchronous behaviors. + - Logging with interceptor: ```dart class LoggerInterceptor extends InterceptorContract { @override - Future interceptRequest({ + BaseRequest interceptRequest({ required BaseRequest request, - }) async { + }) { print('----- Request -----'); print(request.toString()); print(request.headers.toString()); @@ -84,9 +86,9 @@ class LoggerInterceptor extends InterceptorContract { } @override - Future interceptResponse({ + BaseResponse interceptResponse({ required BaseResponse response, - }) async { + }) { log('----- Response -----'); log('Code: ${response.statusCode}'); if (response is Response) { @@ -102,7 +104,7 @@ class LoggerInterceptor extends InterceptorContract { ```dart class WeatherApiInterceptor implements InterceptorContract { @override - Future interceptRequest({required BaseRequest request}) async { + FutureOr interceptRequest({required BaseRequest request}) async { try { request.url.queryParameters['appid'] = OPEN_WEATHER_API_KEY; request.url.queryParameters['units'] = 'metric'; @@ -114,7 +116,10 @@ class WeatherApiInterceptor implements InterceptorContract { } @override - Future interceptResponse({required BaseResponse response}) async => response; + BaseResponse interceptResponse({ + required BaseResponse response, + }) => + response; } ``` @@ -123,7 +128,7 @@ class WeatherApiInterceptor implements InterceptorContract { ```dart class MultipartRequestInterceptor implements InterceptorContract { @override - Future interceptRequest({required BaseRequest request}) async { + FutureOr interceptRequest({required BaseRequest request}) async { if(request is MultipartRequest){ request.fields['app_version'] = await PackageInfo.fromPlatform().version; } @@ -131,7 +136,7 @@ class MultipartRequestInterceptor implements InterceptorContract { } @override - Future interceptResponse({required BaseResponse response}) async { + FutureOr interceptResponse({required BaseResponse response}) async { if(response is StreamedResponse){ response.stream.asBroadcastStream().listen((data){ print(data); diff --git a/example/lib/common.dart b/example/lib/common.dart index 7cd099b..d3d83e7 100644 --- a/example/lib/common.dart +++ b/example/lib/common.dart @@ -4,9 +4,9 @@ import 'package:http_interceptor/http_interceptor.dart'; class LoggerInterceptor extends InterceptorContract { @override - Future interceptRequest({ + BaseRequest interceptRequest({ required BaseRequest request, - }) async { + }) { log('----- Request -----'); log(request.toString()); log(request.headers.toString()); @@ -15,9 +15,9 @@ class LoggerInterceptor extends InterceptorContract { } @override - Future interceptResponse({ + BaseResponse interceptResponse({ required BaseResponse response, - }) async { + }) { log('----- Response -----'); log('Code: ${response.statusCode}'); log('Response type: ${response.runtimeType}'); diff --git a/example/lib/multipart_app.dart b/example/lib/multipart_app.dart index 43a594b..f623378 100644 --- a/example/lib/multipart_app.dart +++ b/example/lib/multipart_app.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; @@ -171,8 +172,8 @@ class RemoveBgApiInterceptor extends InterceptorContract { } @override - Future interceptResponse({ + BaseResponse interceptResponse({ required BaseResponse response, - }) async => + }) => response; } diff --git a/example/lib/weather_app.dart b/example/lib/weather_app.dart index 65c6d2d..d59a0cb 100644 --- a/example/lib/weather_app.dart +++ b/example/lib/weather_app.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io'; @@ -267,15 +268,15 @@ class WeatherRepository { // var parsedWeather; // try { // var response = await InterceptedHttp.build( - // interceptors: [WeatherApiInterceptor()]) - // .get("$baseUrl/weather", params: {'id': "$id"}); + // interceptors: [WeatherApiInterceptor()], + // ).get('$baseUrl/weather', params: {'id': '$id'}); // if (response.statusCode == 200) { // parsedWeather = json.decode(response.body); // } else { - // throw Exception("Error while fetching. \n ${response.body}"); + // throw Exception('Error while fetching. \n ${response.body}'); // } // } catch (e) { - // log(e); + // log(e.toString()); // } // return parsedWeather; // } @@ -326,8 +327,9 @@ class WeatherApiInterceptor extends InterceptorContract { } @override - Future interceptResponse( - {required BaseResponse response}) async => + BaseResponse interceptResponse({ + required BaseResponse response, + }) => response; } diff --git a/lib/extensions/multipart_request.dart b/lib/extensions/multipart_request.dart index 34e5b9a..acb676f 100644 --- a/lib/extensions/multipart_request.dart +++ b/lib/extensions/multipart_request.dart @@ -14,16 +14,27 @@ extension MultipartRequestCopyWith on MultipartRequest { bool? followRedirects, int? maxRedirects, bool? persistentConnection, - }) => - MultipartRequest( - method?.asString ?? this.method, - url ?? this.url, - ) - ..headers.addAll(headers ?? this.headers) - ..fields.addAll(fields ?? this.fields) - ..files.addAll(files ?? this.files) - ..followRedirects = followRedirects ?? this.followRedirects - ..maxRedirects = maxRedirects ?? this.maxRedirects - ..persistentConnection = - persistentConnection ?? this.persistentConnection; + }) { + var clonedRequest = + MultipartRequest(method?.asString ?? this.method, url ?? this.url) + ..headers.addAll(headers ?? this.headers) + ..fields.addAll(fields ?? this.fields); + + for (var file in this.files) { + clonedRequest.files.add(MultipartFile( + file.field, + file.finalize(), + file.length, + filename: file.filename, + contentType: file.contentType, + )); + } + + this.persistentConnection = + persistentConnection ?? this.persistentConnection; + this.followRedirects = followRedirects ?? this.followRedirects; + this.maxRedirects = maxRedirects ?? this.maxRedirects; + + return clonedRequest; + } } diff --git a/lib/extensions/streamed_request.dart b/lib/extensions/streamed_request.dart index 47112e9..411d28e 100644 --- a/lib/extensions/streamed_request.dart +++ b/lib/extensions/streamed_request.dart @@ -14,25 +14,27 @@ extension StreamedRequestCopyWith on StreamedRequest { int? maxRedirects, bool? persistentConnection, }) { - final req = StreamedRequest( - method?.asString ?? this.method, - url ?? this.url, - ) - ..headers.addAll(headers ?? this.headers) - ..followRedirects = followRedirects ?? this.followRedirects - ..maxRedirects = maxRedirects ?? this.maxRedirects - ..persistentConnection = - persistentConnection ?? this.persistentConnection; + // Create a new StreamedRequest with the same method and URL + var clonedRequest = + StreamedRequest(method?.asString ?? this.method, url ?? this.url) + ..headers.addAll(headers ?? this.headers); - if (stream != null) { - stream.listen((data) { - req.sink.add(data); - }); - finalize().listen((data) { - req.sink.add(data); - }); - } + // Use a broadcast stream to allow multiple listeners + var broadcastStream = + stream?.asBroadcastStream() ?? finalize().asBroadcastStream(); - return req; + // Pipe the broadcast stream into the cloned request's sink + broadcastStream.listen((data) { + clonedRequest.sink.add(data); + }, onDone: () { + clonedRequest.sink.close(); + }); + + this.persistentConnection = + persistentConnection ?? this.persistentConnection; + this.followRedirects = followRedirects ?? this.followRedirects; + this.maxRedirects = maxRedirects ?? this.maxRedirects; + + return clonedRequest; } } diff --git a/lib/http/intercepted_client.dart b/lib/http/intercepted_client.dart index 52178a5..5afac3a 100644 --- a/lib/http/intercepted_client.dart +++ b/lib/http/intercepted_client.dart @@ -219,7 +219,13 @@ class InterceptedClient extends BaseClient { final interceptedResponse = await _interceptResponse(response); - return interceptedResponse as StreamedResponse; + if (interceptedResponse is StreamedResponse) { + return interceptedResponse; + } + + throw ClientException( + 'Expected `StreamedResponse`, got ${interceptedResponse.runtimeType}.', + ); } Future _sendUnstreamed({ diff --git a/lib/models/interceptor_contract.dart b/lib/models/interceptor_contract.dart index 3ca99df..6da1aa9 100644 --- a/lib/models/interceptor_contract.dart +++ b/lib/models/interceptor_contract.dart @@ -14,13 +14,13 @@ import 'package:http/http.dart'; ///```dart /// class LoggingInterceptor implements InterceptorContract { /// @override -/// Future interceptRequest({required BaseRequest request}) async { +/// FutureOr interceptRequest({required BaseRequest request}) async { /// print(request.toString()); /// return data; /// } /// /// @override -/// Future interceptResponse({required BaseResponse response}) async { +/// FutureOr interceptResponse({required BaseResponse response}) async { /// print(response.toString()); /// return data; /// }