From 60fc49ccc3a0889b060a838eeffef65695b51939 Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Sat, 22 Mar 2025 11:58:19 +0800 Subject: [PATCH 1/2] feat: retry before sending --- lib/http/init.dart | 41 +++++++---- lib/http/retry_interceptor.dart | 34 +++++++++ lib/pages/setting/widgets/model.dart | 80 +++++++++++++++++---- lib/pages/setting/widgets/slide_dialog.dart | 21 +++--- lib/utils/storage.dart | 8 +++ 5 files changed, 150 insertions(+), 34 deletions(-) create mode 100644 lib/http/retry_interceptor.dart diff --git a/lib/http/init.dart b/lib/http/init.dart index 80c37e4c0..22e41431d 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'dart:io'; import 'dart:math' show Random; import 'package:PiliPlus/build_config.dart'; +import 'package:PiliPlus/http/retry_interceptor.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart'; import 'package:archive/archive.dart'; @@ -96,9 +97,9 @@ class Request { //请求基地址,可以包含子路径 baseUrl: HttpString.apiBaseUrl, //连接服务器超时时间,单位是毫秒. - connectTimeout: const Duration(milliseconds: 12000), + connectTimeout: const Duration(milliseconds: 4000), //响应流上前后两次接受到数据的间隔,单位为毫秒。 - receiveTimeout: const Duration(milliseconds: 12000), + receiveTimeout: const Duration(milliseconds: 4000), //Http请求头. headers: { 'connection': 'keep-alive', @@ -132,24 +133,40 @@ class Request { return client; }); + late Uri proxy; + if (enableSystemProxy) { + proxy = Uri( + scheme: 'http', + host: systemProxyHost, + port: int.parse(systemProxyPort)); + } + dio = Dio(options) ..httpClientAdapter = GStorage.setting.get(SettingBoxKey.enableHttp2, defaultValue: false) ? Http2Adapter( ConnectionManager( - idleTimeout: const Duration(seconds: 30), - onClientCreate: (_, ClientSetting config) { - config.onBadCertificate = (_) => true; - if (enableSystemProxy) { - config.proxy = Uri( - scheme: 'http', - host: systemProxyHost, - port: int.parse(systemProxyPort)); - } - }), + idleTimeout: const Duration(seconds: 15), + onClientCreate: enableSystemProxy + ? (_, config) { + config + ..proxy = proxy + ..onBadCertificate = (_) => true; + } + : GStorage.badCertificateCallback + ? (_, config) { + config.onBadCertificate = (_) => true; + } + : null), fallbackAdapter: http11Adapter) : http11Adapter; + // 先于其他Interceptor + if (GStorage.retryCount > 0) { + dio.interceptors + .add(RetryInterceptor(GStorage.retryCount, GStorage.retryDelay)); + } + // 日志拦截器 输出请求、响应内容 if (BuildConfig.isDebug) { dio.interceptors.add(LogInterceptor( diff --git a/lib/http/retry_interceptor.dart b/lib/http/retry_interceptor.dart new file mode 100644 index 000000000..b04e7814d --- /dev/null +++ b/lib/http/retry_interceptor.dart @@ -0,0 +1,34 @@ +import 'package:dio/dio.dart'; + +import 'index.dart'; + +class RetryInterceptor extends Interceptor { + final int _count; + final int _delay; + + RetryInterceptor(this._count, this._delay); + + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (err.response != null) return handler.next(err); + switch (err.type) { + case DioExceptionType.connectionError: + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + if ((err.requestOptions.extra['_rt'] ??= 0) < _count) { + Future.delayed( + Duration( + milliseconds: ++err.requestOptions.extra['_rt'] * _delay), + () => Request.dio + .fetch(err.requestOptions) + .then(handler.resolve) + .onError((error, _) => handler.reject(error))); + } else { + handler.next(err); + } + return; + default: + return handler.next(err); + } + } +} diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 58e4f978d..97610bb9b 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -160,7 +160,7 @@ List get styleSettings => [ double? result = await showDialog( context: Get.context!, builder: (context) { - return SlideDialog( + return SlideDialog( title: '小卡最大列宽度(默认240dp)', value: GStorage.smallCardWidth, min: 150.0, @@ -186,7 +186,7 @@ List get styleSettings => [ double? result = await showDialog( context: Get.context!, builder: (context) { - return SlideDialog( + return SlideDialog( title: '中卡最大列宽度(默认280dp)', value: GStorage.mediumCardWidth, min: 150.0, @@ -449,7 +449,7 @@ List get styleSettings => [ double? result = await showDialog( context: Get.context!, builder: (context) { - return SlideDialog( + return SlideDialog( title: 'Toast不透明度', value: GStorage.toastOpacity, min: 0.0, @@ -691,7 +691,7 @@ void _showQualityDialog({ ), actions: [ TextButton( - onPressed: () => Get.back(), + onPressed: Get.back, child: Text('取消', style: TextStyle(color: Theme.of(context).colorScheme.outline))), @@ -1795,7 +1795,7 @@ List get extraSettings => [ double? result = await showDialog( context: Get.context!, builder: (context) { - return SlideDialog( + return SlideDialog( title: '刷新滑动距离', min: 0.1, max: 0.5, @@ -1825,7 +1825,7 @@ List get extraSettings => [ double? result = await showDialog( context: Get.context!, builder: (context) { - return SlideDialog( + return SlideDialog( title: '刷新指示器高度', min: 10.0, max: 100.0, @@ -2214,12 +2214,68 @@ List get extraSettings => [ defaultVal: false, ), SettingsModel( - settingsType: SettingsType.sw1tch, - title: '启用HTTP/2', - subtitle: '测试中', - leading: Icon(Icons.swap_horizontal_circle_outlined), - setKey: SettingBoxKey.enableHttp2, - defaultVal: false, + settingsType: SettingsType.sw1tch, + title: '启用HTTP/2', + leading: const Icon(Icons.swap_horizontal_circle_outlined), + setKey: SettingBoxKey.enableHttp2, + defaultVal: false, + onChanged: (_) { + SmartDialog.showToast('重启生效'); + }), + SettingsModel( + settingsType: SettingsType.normal, + title: '连接重试次数', + subtitle: '为0时禁用', + leading: const Icon(Icons.repeat), + onTap: (setState) async { + final result = await showDialog( + context: Get.context!, + builder: (context) { + return SlideDialog( + title: '连接重试次数', + min: 0, + max: 8, + divisions: 8, + precise: 0, + value: GStorage.retryCount.toDouble(), + ); + }, + ); + if (result != null) { + await GStorage.setting + .put(SettingBoxKey.retryCount, result.toInt()); + setState(); + SmartDialog.showToast('重启生效'); + } + }, + ), + SettingsModel( + settingsType: SettingsType.normal, + title: '连接重试间隔', + subtitle: '实际间隔 = 间隔 * 第x次重试', + leading: const Icon(Icons.more_time_outlined), + onTap: (setState) async { + final result = await showDialog( + context: Get.context!, + builder: (context) { + return SlideDialog( + title: '连接重试间隔', + min: 0, + max: 1000, + divisions: 10, + precise: 0, + value: GStorage.retryDelay.toDouble(), + suffix: 'ms', + ); + }, + ); + if (result != null) { + await GStorage.setting + .put(SettingBoxKey.retryDelay, result.toInt()); + setState(); + SmartDialog.showToast('重启生效'); + } + }, ), SettingsModel( settingsType: SettingsType.normal, diff --git a/lib/pages/setting/widgets/slide_dialog.dart b/lib/pages/setting/widgets/slide_dialog.dart index df1f25d28..06d7f7880 100644 --- a/lib/pages/setting/widgets/slide_dialog.dart +++ b/lib/pages/setting/widgets/slide_dialog.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:get/get_utils/get_utils.dart'; -class SlideDialog extends StatefulWidget { - final T value; +class SlideDialog extends StatefulWidget { + final double value; final String title; final double min; final double max; final int? divisions; - final String? suffix; + final String suffix; final int precise; const SlideDialog({ @@ -17,21 +17,21 @@ class SlideDialog extends StatefulWidget { required this.min, required this.max, this.divisions, - this.suffix, + this.suffix = '', this.precise = 1, }); @override - State> createState() => _SlideDialogState(); + State createState() => _SlideDialogState(); } -class _SlideDialogState extends State> { +class _SlideDialogState extends State { late double _tempValue; @override void initState() { super.initState(); - _tempValue = widget.value.toDouble(); + _tempValue = widget.value; } @override @@ -47,7 +47,8 @@ class _SlideDialogState extends State> { min: widget.min, max: widget.max, divisions: widget.divisions, - label: '$_tempValue${widget.suffix ?? ''}', + label: + '${_tempValue.toStringAsFixed(widget.precise)}${widget.suffix}', onChanged: (double value) { setState(() { _tempValue = value.toPrecision(widget.precise); @@ -57,14 +58,14 @@ class _SlideDialogState extends State> { ), actions: [ TextButton( - onPressed: () => Navigator.pop(context), + onPressed: Navigator.of(context).pop, child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), ), ), TextButton( - onPressed: () => Navigator.pop(context, _tempValue as T), + onPressed: () => Navigator.pop(context, _tempValue), child: const Text('确定'), ) ], diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index ae925a791..b0bdee3c2 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -420,6 +420,12 @@ class GStorage { static bool get enableSlideVolumeBrightness => GStorage.setting .get(SettingBoxKey.enableSlideVolumeBrightness, defaultValue: true); + static int get retryCount => + GStorage.setting.get(SettingBoxKey.retryCount, defaultValue: 0); + + static int get retryDelay => + GStorage.setting.get(SettingBoxKey.retryDelay, defaultValue: 500); + static List get dynamicDetailRatio => List.from(setting .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); @@ -688,6 +694,8 @@ class SettingBoxKey { showDynActionBar = 'showDynActionBar', darkVideoPage = 'darkVideoPage', enableSlideVolumeBrightness = 'enableSlideVolumeBrightness', + retryCount = 'retryCount', + retryDelay = 'retryDelay', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock', From 2db3842df932eea90b35e713dfcca418e760662d Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Sat, 22 Mar 2025 12:01:24 +0800 Subject: [PATCH 2/2] reduce idleTimeout --- lib/http/init.dart | 2 +- lib/main.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/http/init.dart b/lib/http/init.dart index 22e41431d..689abe95f 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -122,7 +122,7 @@ class Request { final http11Adapter = IOHttpClientAdapter(createHttpClient: () { final client = HttpClient() - ..idleTimeout = const Duration(seconds: 30) + ..idleTimeout = const Duration(seconds: 15) ..autoUncompress = false; // Http2Adapter没有自动解压, 统一行为 // 设置代理 if (enableSystemProxy) { diff --git a/lib/main.dart b/lib/main.dart index 1b81af761..b495361d9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -236,7 +236,7 @@ class _CustomHttpOverrides extends HttpOverrides { HttpClient createHttpClient(SecurityContext? context) { final client = super.createHttpClient(context) ..maxConnectionsPerHost = 32 - ..idleTimeout = const Duration(seconds: 30); + ..idleTimeout = const Duration(seconds: 15); if (badCertificateCallback) { client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;