feat: retry before sending (#489)

* feat: retry before sending

* reduce idleTimeout
This commit is contained in:
My-Responsitories
2025-03-23 12:09:11 +08:00
committed by GitHub
parent 99b14d0f0e
commit 3881b3dc74
6 changed files with 152 additions and 36 deletions

View File

@@ -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',
@@ -121,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) {
@@ -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(

View File

@@ -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<DioException>((error, _) => handler.reject(error)));
} else {
handler.next(err);
}
return;
default:
return handler.next(err);
}
}
}

View File

@@ -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;

View File

@@ -160,7 +160,7 @@ List<SettingsModel> get styleSettings => [
double? result = await showDialog(
context: Get.context!,
builder: (context) {
return SlideDialog<double>(
return SlideDialog(
title: '小卡最大列宽度默认240dp',
value: GStorage.smallCardWidth,
min: 150.0,
@@ -186,7 +186,7 @@ List<SettingsModel> get styleSettings => [
double? result = await showDialog(
context: Get.context!,
builder: (context) {
return SlideDialog<double>(
return SlideDialog(
title: '中卡最大列宽度默认280dp',
value: GStorage.mediumCardWidth,
min: 150.0,
@@ -449,7 +449,7 @@ List<SettingsModel> get styleSettings => [
double? result = await showDialog(
context: Get.context!,
builder: (context) {
return SlideDialog<double>(
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<SettingsModel> get extraSettings => [
double? result = await showDialog(
context: Get.context!,
builder: (context) {
return SlideDialog<double>(
return SlideDialog(
title: '刷新滑动距离',
min: 0.1,
max: 0.5,
@@ -1825,7 +1825,7 @@ List<SettingsModel> get extraSettings => [
double? result = await showDialog(
context: Get.context!,
builder: (context) {
return SlideDialog<double>(
return SlideDialog(
title: '刷新指示器高度',
min: 10.0,
max: 100.0,
@@ -2216,10 +2216,66 @@ List<SettingsModel> get extraSettings => [
SettingsModel(
settingsType: SettingsType.sw1tch,
title: '启用HTTP/2',
subtitle: '测试中',
leading: Icon(Icons.swap_horizontal_circle_outlined),
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<double>(
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<double>(
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,

View File

@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:get/get_utils/get_utils.dart';
class SlideDialog<T extends num> 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<T extends num> extends StatefulWidget {
required this.min,
required this.max,
this.divisions,
this.suffix,
this.suffix = '',
this.precise = 1,
});
@override
State<SlideDialog<T>> createState() => _SlideDialogState<T>();
State<SlideDialog> createState() => _SlideDialogState();
}
class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
class _SlideDialogState extends State<SlideDialog> {
late double _tempValue;
@override
void initState() {
super.initState();
_tempValue = widget.value.toDouble();
_tempValue = widget.value;
}
@override
@@ -47,7 +47,8 @@ class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
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<T extends num> extends State<SlideDialog<T>> {
),
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('确定'),
)
],

View File

@@ -422,6 +422,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<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -691,6 +697,8 @@ class SettingBoxKey {
showDynActionBar = 'showDynActionBar',
darkVideoPage = 'darkVideoPage',
enableSlideVolumeBrightness = 'enableSlideVolumeBrightness',
retryCount = 'retryCount',
retryDelay = 'retryDelay',
// Sponsor Block
enableSponsorBlock = 'enableSponsorBlock',