mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: retry before sending (#489)
* feat: retry before sending * reduce idleTimeout
This commit is contained in:
committed by
GitHub
parent
99b14d0f0e
commit
3881b3dc74
@@ -4,6 +4,7 @@ import 'dart:developer';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' show Random;
|
import 'dart:math' show Random;
|
||||||
import 'package:PiliPlus/build_config.dart';
|
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.dart';
|
||||||
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
|
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
|
||||||
import 'package:archive/archive.dart';
|
import 'package:archive/archive.dart';
|
||||||
@@ -96,9 +97,9 @@ class Request {
|
|||||||
//请求基地址,可以包含子路径
|
//请求基地址,可以包含子路径
|
||||||
baseUrl: HttpString.apiBaseUrl,
|
baseUrl: HttpString.apiBaseUrl,
|
||||||
//连接服务器超时时间,单位是毫秒.
|
//连接服务器超时时间,单位是毫秒.
|
||||||
connectTimeout: const Duration(milliseconds: 12000),
|
connectTimeout: const Duration(milliseconds: 4000),
|
||||||
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||||
receiveTimeout: const Duration(milliseconds: 12000),
|
receiveTimeout: const Duration(milliseconds: 4000),
|
||||||
//Http请求头.
|
//Http请求头.
|
||||||
headers: {
|
headers: {
|
||||||
'connection': 'keep-alive',
|
'connection': 'keep-alive',
|
||||||
@@ -121,7 +122,7 @@ class Request {
|
|||||||
|
|
||||||
final http11Adapter = IOHttpClientAdapter(createHttpClient: () {
|
final http11Adapter = IOHttpClientAdapter(createHttpClient: () {
|
||||||
final client = HttpClient()
|
final client = HttpClient()
|
||||||
..idleTimeout = const Duration(seconds: 30)
|
..idleTimeout = const Duration(seconds: 15)
|
||||||
..autoUncompress = false; // Http2Adapter没有自动解压, 统一行为
|
..autoUncompress = false; // Http2Adapter没有自动解压, 统一行为
|
||||||
// 设置代理
|
// 设置代理
|
||||||
if (enableSystemProxy) {
|
if (enableSystemProxy) {
|
||||||
@@ -132,24 +133,40 @@ class Request {
|
|||||||
return client;
|
return client;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
late Uri proxy;
|
||||||
|
if (enableSystemProxy) {
|
||||||
|
proxy = Uri(
|
||||||
|
scheme: 'http',
|
||||||
|
host: systemProxyHost,
|
||||||
|
port: int.parse(systemProxyPort));
|
||||||
|
}
|
||||||
|
|
||||||
dio = Dio(options)
|
dio = Dio(options)
|
||||||
..httpClientAdapter =
|
..httpClientAdapter =
|
||||||
GStorage.setting.get(SettingBoxKey.enableHttp2, defaultValue: false)
|
GStorage.setting.get(SettingBoxKey.enableHttp2, defaultValue: false)
|
||||||
? Http2Adapter(
|
? Http2Adapter(
|
||||||
ConnectionManager(
|
ConnectionManager(
|
||||||
idleTimeout: const Duration(seconds: 30),
|
idleTimeout: const Duration(seconds: 15),
|
||||||
onClientCreate: (_, ClientSetting config) {
|
onClientCreate: enableSystemProxy
|
||||||
config.onBadCertificate = (_) => true;
|
? (_, config) {
|
||||||
if (enableSystemProxy) {
|
config
|
||||||
config.proxy = Uri(
|
..proxy = proxy
|
||||||
scheme: 'http',
|
..onBadCertificate = (_) => true;
|
||||||
host: systemProxyHost,
|
|
||||||
port: int.parse(systemProxyPort));
|
|
||||||
}
|
}
|
||||||
}),
|
: GStorage.badCertificateCallback
|
||||||
|
? (_, config) {
|
||||||
|
config.onBadCertificate = (_) => true;
|
||||||
|
}
|
||||||
|
: null),
|
||||||
fallbackAdapter: http11Adapter)
|
fallbackAdapter: http11Adapter)
|
||||||
: http11Adapter;
|
: http11Adapter;
|
||||||
|
|
||||||
|
// 先于其他Interceptor
|
||||||
|
if (GStorage.retryCount > 0) {
|
||||||
|
dio.interceptors
|
||||||
|
.add(RetryInterceptor(GStorage.retryCount, GStorage.retryDelay));
|
||||||
|
}
|
||||||
|
|
||||||
// 日志拦截器 输出请求、响应内容
|
// 日志拦截器 输出请求、响应内容
|
||||||
if (BuildConfig.isDebug) {
|
if (BuildConfig.isDebug) {
|
||||||
dio.interceptors.add(LogInterceptor(
|
dio.interceptors.add(LogInterceptor(
|
||||||
|
|||||||
34
lib/http/retry_interceptor.dart
Normal file
34
lib/http/retry_interceptor.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -236,7 +236,7 @@ class _CustomHttpOverrides extends HttpOverrides {
|
|||||||
HttpClient createHttpClient(SecurityContext? context) {
|
HttpClient createHttpClient(SecurityContext? context) {
|
||||||
final client = super.createHttpClient(context)
|
final client = super.createHttpClient(context)
|
||||||
..maxConnectionsPerHost = 32
|
..maxConnectionsPerHost = 32
|
||||||
..idleTimeout = const Duration(seconds: 30);
|
..idleTimeout = const Duration(seconds: 15);
|
||||||
if (badCertificateCallback) {
|
if (badCertificateCallback) {
|
||||||
client.badCertificateCallback =
|
client.badCertificateCallback =
|
||||||
(X509Certificate cert, String host, int port) => true;
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
double? result = await showDialog(
|
double? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SlideDialog<double>(
|
return SlideDialog(
|
||||||
title: '小卡最大列宽度(默认240dp)',
|
title: '小卡最大列宽度(默认240dp)',
|
||||||
value: GStorage.smallCardWidth,
|
value: GStorage.smallCardWidth,
|
||||||
min: 150.0,
|
min: 150.0,
|
||||||
@@ -186,7 +186,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
double? result = await showDialog(
|
double? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SlideDialog<double>(
|
return SlideDialog(
|
||||||
title: '中卡最大列宽度(默认280dp)',
|
title: '中卡最大列宽度(默认280dp)',
|
||||||
value: GStorage.mediumCardWidth,
|
value: GStorage.mediumCardWidth,
|
||||||
min: 150.0,
|
min: 150.0,
|
||||||
@@ -449,7 +449,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
double? result = await showDialog(
|
double? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SlideDialog<double>(
|
return SlideDialog(
|
||||||
title: 'Toast不透明度',
|
title: 'Toast不透明度',
|
||||||
value: GStorage.toastOpacity,
|
value: GStorage.toastOpacity,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
@@ -691,7 +691,7 @@ void _showQualityDialog({
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: Get.back,
|
||||||
child: Text('取消',
|
child: Text('取消',
|
||||||
style:
|
style:
|
||||||
TextStyle(color: Theme.of(context).colorScheme.outline))),
|
TextStyle(color: Theme.of(context).colorScheme.outline))),
|
||||||
@@ -1795,7 +1795,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
double? result = await showDialog(
|
double? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SlideDialog<double>(
|
return SlideDialog(
|
||||||
title: '刷新滑动距离',
|
title: '刷新滑动距离',
|
||||||
min: 0.1,
|
min: 0.1,
|
||||||
max: 0.5,
|
max: 0.5,
|
||||||
@@ -1825,7 +1825,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
double? result = await showDialog(
|
double? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SlideDialog<double>(
|
return SlideDialog(
|
||||||
title: '刷新指示器高度',
|
title: '刷新指示器高度',
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 100.0,
|
max: 100.0,
|
||||||
@@ -2216,10 +2216,66 @@ List<SettingsModel> get extraSettings => [
|
|||||||
SettingsModel(
|
SettingsModel(
|
||||||
settingsType: SettingsType.sw1tch,
|
settingsType: SettingsType.sw1tch,
|
||||||
title: '启用HTTP/2',
|
title: '启用HTTP/2',
|
||||||
subtitle: '测试中',
|
leading: const Icon(Icons.swap_horizontal_circle_outlined),
|
||||||
leading: Icon(Icons.swap_horizontal_circle_outlined),
|
|
||||||
setKey: SettingBoxKey.enableHttp2,
|
setKey: SettingBoxKey.enableHttp2,
|
||||||
defaultVal: false,
|
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(
|
SettingsModel(
|
||||||
settingsType: SettingsType.normal,
|
settingsType: SettingsType.normal,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get_utils/get_utils.dart';
|
import 'package:get/get_utils/get_utils.dart';
|
||||||
|
|
||||||
class SlideDialog<T extends num> extends StatefulWidget {
|
class SlideDialog extends StatefulWidget {
|
||||||
final T value;
|
final double value;
|
||||||
final String title;
|
final String title;
|
||||||
final double min;
|
final double min;
|
||||||
final double max;
|
final double max;
|
||||||
final int? divisions;
|
final int? divisions;
|
||||||
final String? suffix;
|
final String suffix;
|
||||||
final int precise;
|
final int precise;
|
||||||
|
|
||||||
const SlideDialog({
|
const SlideDialog({
|
||||||
@@ -17,21 +17,21 @@ class SlideDialog<T extends num> extends StatefulWidget {
|
|||||||
required this.min,
|
required this.min,
|
||||||
required this.max,
|
required this.max,
|
||||||
this.divisions,
|
this.divisions,
|
||||||
this.suffix,
|
this.suffix = '',
|
||||||
this.precise = 1,
|
this.precise = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@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;
|
late double _tempValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tempValue = widget.value.toDouble();
|
_tempValue = widget.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -47,7 +47,8 @@ class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
|
|||||||
min: widget.min,
|
min: widget.min,
|
||||||
max: widget.max,
|
max: widget.max,
|
||||||
divisions: widget.divisions,
|
divisions: widget.divisions,
|
||||||
label: '$_tempValue${widget.suffix ?? ''}',
|
label:
|
||||||
|
'${_tempValue.toStringAsFixed(widget.precise)}${widget.suffix}',
|
||||||
onChanged: (double value) {
|
onChanged: (double value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_tempValue = value.toPrecision(widget.precise);
|
_tempValue = value.toPrecision(widget.precise);
|
||||||
@@ -57,14 +58,14 @@ class _SlideDialogState<T extends num> extends State<SlideDialog<T>> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: Navigator.of(context).pop,
|
||||||
child: Text(
|
child: Text(
|
||||||
'取消',
|
'取消',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, _tempValue as T),
|
onPressed: () => Navigator.pop(context, _tempValue),
|
||||||
child: const Text('确定'),
|
child: const Text('确定'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -422,6 +422,12 @@ class GStorage {
|
|||||||
static bool get enableSlideVolumeBrightness => GStorage.setting
|
static bool get enableSlideVolumeBrightness => GStorage.setting
|
||||||
.get(SettingBoxKey.enableSlideVolumeBrightness, defaultValue: true);
|
.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
|
static List<double> get dynamicDetailRatio => List<double>.from(setting
|
||||||
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
|
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
|
||||||
|
|
||||||
@@ -691,6 +697,8 @@ class SettingBoxKey {
|
|||||||
showDynActionBar = 'showDynActionBar',
|
showDynActionBar = 'showDynActionBar',
|
||||||
darkVideoPage = 'darkVideoPage',
|
darkVideoPage = 'darkVideoPage',
|
||||||
enableSlideVolumeBrightness = 'enableSlideVolumeBrightness',
|
enableSlideVolumeBrightness = 'enableSlideVolumeBrightness',
|
||||||
|
retryCount = 'retryCount',
|
||||||
|
retryDelay = 'retryDelay',
|
||||||
|
|
||||||
// Sponsor Block
|
// Sponsor Block
|
||||||
enableSponsorBlock = 'enableSponsorBlock',
|
enableSponsorBlock = 'enableSponsorBlock',
|
||||||
|
|||||||
Reference in New Issue
Block a user