From 58f3e938a6fa43ee3ab7214bdd437ae5d5846cbc Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Wed, 3 Jan 2024 21:15:55 +0330 Subject: [PATCH] Change app http client --- lib/core/http_client/dio_http_client.dart | 95 +++++++++++++++++++ .../http_client/http_client_provider.dart | 25 ++--- .../data/app_update_data_providers.dart | 2 +- .../data/app_update_repository.dart | 9 +- .../data/geo_asset_data_providers.dart | 2 +- .../geo_asset/data/geo_asset_repository.dart | 10 +- .../profile/data/profile_data_providers.dart | 2 +- .../profile/data/profile_repository.dart | 13 +-- pubspec.lock | 16 ++++ pubspec.yaml | 2 + 10 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 lib/core/http_client/dio_http_client.dart diff --git a/lib/core/http_client/dio_http_client.dart b/lib/core/http_client/dio_http_client.dart new file mode 100644 index 00000000..a19c3f6b --- /dev/null +++ b/lib/core/http_client/dio_http_client.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:dio_smart_retry/dio_smart_retry.dart'; +import 'package:flutter_loggy_dio/flutter_loggy_dio.dart'; +import 'package:hiddify/utils/custom_loggers.dart'; + +class DioHttpClient with InfraLogger { + DioHttpClient({ + required Duration timeout, + required String userAgent, + required bool debug, + }) { + _dio = Dio( + BaseOptions( + connectTimeout: timeout, + sendTimeout: timeout, + receiveTimeout: timeout, + headers: {"User-Agent": userAgent}, + ), + ); + + _dio.interceptors.add( + RetryInterceptor( + dio: _dio, + retryDelays: const [ + Duration(seconds: 1), + Duration(seconds: 2), + Duration(seconds: 3), + ], + ), + ); + + if (debug) { + _dio.interceptors.add(LoggyDioInterceptor(requestHeader: true)); + } + } + + late final Dio _dio; + + Future> get( + String url, { + CancelToken? cancelToken, + String? userAgent, + ({String username, String password})? credentials, + }) async { + return _dio.get( + url, + cancelToken: cancelToken, + options: _options(url, userAgent: userAgent, credentials: credentials), + ); + } + + Future download( + String url, + String path, { + CancelToken? cancelToken, + String? userAgent, + ({String username, String password})? credentials, + }) async { + return _dio.download( + url, + path, + cancelToken: cancelToken, + options: _options(url, userAgent: userAgent, credentials: credentials), + ); + } + + Options _options( + String url, { + String? userAgent, + ({String username, String password})? credentials, + }) { + final uri = Uri.parse(url); + + String? userInfo; + if (credentials != null) { + userInfo = "${credentials.username}:${credentials.password}"; + } else if (uri.userInfo.isNotEmpty) { + userInfo = uri.userInfo; + } + + String? basicAuth; + if (userInfo != null) { + basicAuth = "Basic ${base64.encode(utf8.encode(userInfo))}"; + } + + return Options( + headers: { + if (userAgent != null) "User-Agent": userAgent, + if (basicAuth != null) "authorization": basicAuth, + }, + ); + } +} diff --git a/lib/core/http_client/http_client_provider.dart b/lib/core/http_client/http_client_provider.dart index d9edbf3c..a70df92d 100644 --- a/lib/core/http_client/http_client_provider.dart +++ b/lib/core/http_client/http_client_provider.dart @@ -1,26 +1,15 @@ -import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; import 'package:hiddify/core/app_info/app_info_provider.dart'; +import 'package:hiddify/core/http_client/dio_http_client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'http_client_provider.g.dart'; @Riverpod(keepAlive: true) -Dio httpClient(HttpClientRef ref) { - final dio = Dio( - BaseOptions( - connectTimeout: const Duration(seconds: 15), - sendTimeout: const Duration(seconds: 15), - receiveTimeout: const Duration(seconds: 15), - headers: { - "User-Agent": ref.watch(appInfoProvider).requireValue.userAgent, - }, - ), +DioHttpClient httpClient(HttpClientRef ref) { + return DioHttpClient( + timeout: const Duration(seconds: 15), + userAgent: ref.watch(appInfoProvider).requireValue.userAgent, + debug: kDebugMode, ); - // https://github.com/dart-lang/http/issues/1047 - // https://github.com/cfug/dio/issues/2042 - // final debug = ref.read(debugModeNotifierProvider); - // if (debug && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) { - // dio.httpClientAdapter = NativeAdapter(); - // } - return dio; } diff --git a/lib/features/app_update/data/app_update_data_providers.dart b/lib/features/app_update/data/app_update_data_providers.dart index 834c4c10..c115c669 100644 --- a/lib/features/app_update/data/app_update_data_providers.dart +++ b/lib/features/app_update/data/app_update_data_providers.dart @@ -8,5 +8,5 @@ part 'app_update_data_providers.g.dart'; AppUpdateRepository appUpdateRepository( AppUpdateRepositoryRef ref, ) { - return AppUpdateRepositoryImpl(dio: ref.watch(httpClientProvider)); + return AppUpdateRepositoryImpl(httpClient: ref.watch(httpClientProvider)); } diff --git a/lib/features/app_update/data/app_update_repository.dart b/lib/features/app_update/data/app_update_repository.dart index 6242b271..24d421cf 100644 --- a/lib/features/app_update/data/app_update_repository.dart +++ b/lib/features/app_update/data/app_update_repository.dart @@ -1,5 +1,5 @@ -import 'package:dio/dio.dart'; import 'package:fpdart/fpdart.dart'; +import 'package:hiddify/core/http_client/dio_http_client.dart'; import 'package:hiddify/core/model/constants.dart'; import 'package:hiddify/core/model/environment.dart'; import 'package:hiddify/core/utils/exception_handler.dart'; @@ -18,9 +18,9 @@ abstract interface class AppUpdateRepository { class AppUpdateRepositoryImpl with ExceptionHandler, InfraLogger implements AppUpdateRepository { - AppUpdateRepositoryImpl({required this.dio}); + AppUpdateRepositoryImpl({required this.httpClient}); - final Dio dio; + final DioHttpClient httpClient; @override TaskEither getLatestVersion({ @@ -32,7 +32,8 @@ class AppUpdateRepositoryImpl if (!release.allowCustomUpdateChecker) { throw Exception("custom update checkers are not supported"); } - final response = await dio.get(Constants.githubReleasesApiUrl); + final response = + await httpClient.get(Constants.githubReleasesApiUrl); if (response.statusCode != 200 || response.data == null) { loggy.warning("failed to fetch latest version info"); return left(const AppUpdateFailure.unexpected()); diff --git a/lib/features/geo_asset/data/geo_asset_data_providers.dart b/lib/features/geo_asset/data/geo_asset_data_providers.dart index be5c952d..2290ab6a 100644 --- a/lib/features/geo_asset/data/geo_asset_data_providers.dart +++ b/lib/features/geo_asset/data/geo_asset_data_providers.dart @@ -13,7 +13,7 @@ Future geoAssetRepository(GeoAssetRepositoryRef ref) async { final repo = GeoAssetRepositoryImpl( geoAssetDataSource: ref.watch(geoAssetDataSourceProvider), geoAssetPathResolver: ref.watch(geoAssetPathResolverProvider), - dio: ref.watch(httpClientProvider), + httpClient: ref.watch(httpClientProvider), ); await repo.init().getOrElse((l) => throw l).run(); return repo; diff --git a/lib/features/geo_asset/data/geo_asset_repository.dart b/lib/features/geo_asset/data/geo_asset_repository.dart index 3d39e13f..a8f7a68b 100644 --- a/lib/features/geo_asset/data/geo_asset_repository.dart +++ b/lib/features/geo_asset/data/geo_asset_repository.dart @@ -1,11 +1,11 @@ import 'dart:io'; import 'package:dartx/dartx_io.dart'; -import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; import 'package:flutter/services.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/core/database/app_database.dart'; +import 'package:hiddify/core/http_client/dio_http_client.dart'; import 'package:hiddify/core/utils/exception_handler.dart'; import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart'; import 'package:hiddify/features/geo_asset/data/geo_asset_data_source.dart'; @@ -35,12 +35,12 @@ class GeoAssetRepositoryImpl GeoAssetRepositoryImpl({ required this.geoAssetDataSource, required this.geoAssetPathResolver, - required this.dio, + required this.httpClient, }); final GeoAssetDataSource geoAssetDataSource; final GeoAssetPathResolver geoAssetPathResolver; - final Dio dio; + final DioHttpClient httpClient; @override TaskEither init() { @@ -141,7 +141,7 @@ class GeoAssetRepositoryImpl loggy.debug( "checking latest release of [${geoAsset.name}] on [${geoAsset.repositoryUrl}]", ); - final response = await dio.get(geoAsset.repositoryUrl); + final response = await httpClient.get(geoAsset.repositoryUrl); if (response.statusCode != 200 || response.data == null) { return left( GeoAssetUnexpectedFailure.new( @@ -180,7 +180,7 @@ class GeoAssetRepositoryImpl loggy.debug("[${geoAsset.name}] download url: [$downloadUrl]"); final tempPath = "${file.path}.tmp"; await file.parent.create(recursive: true); - await dio.download(downloadUrl, tempPath); + await httpClient.download(downloadUrl, tempPath); await File(tempPath).rename(file.path); await geoAssetDataSource.patch( diff --git a/lib/features/profile/data/profile_data_providers.dart b/lib/features/profile/data/profile_data_providers.dart index 3c046be8..26755634 100644 --- a/lib/features/profile/data/profile_data_providers.dart +++ b/lib/features/profile/data/profile_data_providers.dart @@ -15,7 +15,7 @@ Future profileRepository(ProfileRepositoryRef ref) async { profileDataSource: ref.watch(profileDataSourceProvider), profilePathResolver: ref.watch(profilePathResolverProvider), singbox: ref.watch(singboxServiceProvider), - dio: ref.watch(httpClientProvider), + httpClient: ref.watch(httpClientProvider), ); await repo.init().getOrElse((l) => throw l).run(); return repo; diff --git a/lib/features/profile/data/profile_repository.dart b/lib/features/profile/data/profile_repository.dart index 8b65806f..96214e25 100644 --- a/lib/features/profile/data/profile_repository.dart +++ b/lib/features/profile/data/profile_repository.dart @@ -1,9 +1,9 @@ import 'dart:io'; -import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/core/database/app_database.dart'; +import 'package:hiddify/core/http_client/dio_http_client.dart'; import 'package:hiddify/core/utils/exception_handler.dart'; import 'package:hiddify/features/profile/data/profile_data_mapper.dart'; import 'package:hiddify/features/profile/data/profile_data_source.dart'; @@ -16,7 +16,6 @@ import 'package:hiddify/singbox/service/singbox_service.dart'; import 'package:hiddify/utils/custom_loggers.dart'; import 'package:hiddify/utils/link_parsers.dart'; import 'package:meta/meta.dart'; -import 'package:retry/retry.dart'; import 'package:uuid/uuid.dart'; abstract interface class ProfileRepository { @@ -63,13 +62,13 @@ class ProfileRepositoryImpl required this.profileDataSource, required this.profilePathResolver, required this.singbox, - required this.dio, + required this.httpClient, }); final ProfileDataSource profileDataSource; final ProfilePathResolver profilePathResolver; final SingboxService singbox; - final Dio dio; + final DioHttpClient httpClient; @override TaskEither init() { @@ -366,11 +365,9 @@ class ProfileRepositoryImpl () async { final file = profilePathResolver.file(fileName); final tempFile = profilePathResolver.tempFile(fileName); + try { - final response = await retry( - () async => dio.download(url.trim(), tempFile.path), - maxAttempts: 3, - ); + final response = await httpClient.download(url.trim(), tempFile.path); final headers = await _populateHeaders(response.headers.map, tempFile.path); return await validateConfig(file.path, tempFile.path, false) diff --git a/pubspec.lock b/pubspec.lock index f088be12..495d2bc0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -353,6 +353,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + dio_smart_retry: + dependency: "direct main" + description: + name: dio_smart_retry + sha256: "3d71450c19b4d91ef4c7d726a55a284bfc11eb3634f1f25006cdfab3f8595653" + url: "https://pub.dev" + source: hosted + version: "6.0.0" drift: dependency: "direct main" description: @@ -499,6 +507,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + flutter_loggy_dio: + dependency: "direct main" + description: + name: flutter_loggy_dio + sha256: "682fe1fe136ae11cc9094364a4256d831b8cbe627b1370bb8fb9f135415265e2" + url: "https://pub.dev" + source: hosted + version: "3.0.1" flutter_native_splash: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bb81bbda..9c88d27d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,8 @@ dependencies: native_dio_adapter: ^1.2.0 flutter_displaymode: ^0.6.0 windows_single_instance: ^1.0.1 + flutter_loggy_dio: ^3.0.1 + dio_smart_retry: ^6.0.0 dev_dependencies: flutter_test: