mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
* fix reset Dio adapters on iOS network switch * improve iOS connectivity adapter reset safeguards * refa --------- Co-authored-by: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com>
355 lines
10 KiB
Dart
355 lines
10 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:PiliPlus/http/api.dart';
|
|
import 'package:PiliPlus/http/constants.dart';
|
|
import 'package:PiliPlus/http/loading_state.dart';
|
|
import 'package:PiliPlus/http/retry_interceptor.dart';
|
|
import 'package:PiliPlus/http/user.dart';
|
|
import 'package:PiliPlus/utils/accounts.dart';
|
|
import 'package:PiliPlus/utils/accounts/account.dart';
|
|
import 'package:PiliPlus/utils/accounts/account_manager/account_mgr.dart';
|
|
import 'package:PiliPlus/utils/global_data.dart';
|
|
import 'package:PiliPlus/utils/login_utils.dart';
|
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
|
import 'package:PiliPlus/utils/utils.dart';
|
|
import 'package:archive/archive.dart';
|
|
import 'package:brotli/brotli.dart';
|
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:dio/io.dart';
|
|
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
|
|
|
class Request {
|
|
static const _gzipDecoder = GZipDecoder();
|
|
static const _brotliDecoder = BrotliDecoder();
|
|
|
|
static final Request _instance = Request._internal();
|
|
static late AccountManager accountManager;
|
|
static final _enableHttp2 = Pref.enableHttp2;
|
|
static late final Dio dio;
|
|
static Dio? _http11Dio;
|
|
static Dio get http11Dio =>
|
|
_http11Dio ??= _enableHttp2 ? _cloneHttp11Dio() : dio;
|
|
factory Request() => _instance;
|
|
|
|
/// 设置cookie
|
|
static void setCookie() {
|
|
accountManager = AccountManager();
|
|
dio.interceptors.add(accountManager);
|
|
Accounts.refresh();
|
|
LoginUtils.setWebCookie();
|
|
|
|
if (Accounts.main.isLogin) {
|
|
final coin = Pref.userInfoCache?.money;
|
|
if (coin == null) {
|
|
setCoin();
|
|
} else {
|
|
GlobalData().coins = coin;
|
|
}
|
|
}
|
|
}
|
|
|
|
static Future<void> setCoin() async {
|
|
final res = await UserHttp.getCoin();
|
|
if (res case Success(:final response)) {
|
|
GlobalData().coins = response;
|
|
}
|
|
}
|
|
|
|
static Future<void> buvidActive(Account account) async {
|
|
// 这样线程不安全, 但仍按预期进行
|
|
if (account.activated) return;
|
|
account.activated = true;
|
|
try {
|
|
// final html = await Request().get(Api.dynamicSpmPrefix,
|
|
// options: Options(extra: {'account': account}));
|
|
// final String spmPrefix = _spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
|
final String randPngEnd = base64.encode([
|
|
...Iterable<int>.generate(32, (_) => Utils.random.nextInt(256)),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
73,
|
|
69,
|
|
78,
|
|
68,
|
|
...Iterable<int>.generate(4, (_) => Utils.random.nextInt(256)),
|
|
]);
|
|
|
|
final jsonData = json.encode({
|
|
'3064': 1,
|
|
'39c8': '333.1387.fp.risk',
|
|
'3c43': {
|
|
'adca': 'Linux',
|
|
'bfe9': randPngEnd.substring(randPngEnd.length - 50),
|
|
},
|
|
});
|
|
|
|
await Request().post(
|
|
Api.activateBuvidApi,
|
|
data: {'payload': jsonData},
|
|
options: Options(
|
|
extra: {'account': account},
|
|
contentType: Headers.jsonContentType,
|
|
),
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
|
|
static Dio _cloneHttp11Dio() {
|
|
final h11 = dio.clone(
|
|
httpClientAdapter:
|
|
(dio.httpClientAdapter as Http2Adapter).fallbackAdapter,
|
|
);
|
|
final interceptors = h11.interceptors;
|
|
for (var i = 0; i < interceptors.length; i++) {
|
|
final elem = interceptors[i];
|
|
if (elem is RetryInterceptor) {
|
|
interceptors[i] = elem.copyWith(client: h11);
|
|
break;
|
|
}
|
|
}
|
|
return h11;
|
|
}
|
|
|
|
static Timer? _networkChangeDebounce;
|
|
static void _watchConnectivity() {
|
|
Connectivity().onConnectivityChanged.skip(1).listen((result) {
|
|
_networkChangeDebounce?.cancel();
|
|
_networkChangeDebounce = Timer(
|
|
const Duration(milliseconds: 500),
|
|
_resetAdaptersForNetworkChange,
|
|
);
|
|
});
|
|
}
|
|
|
|
static (IOHttpClientAdapter, ConnectionManager?) _createPool() {
|
|
final bool enableSystemProxy;
|
|
late final String systemProxyHost;
|
|
late final int? systemProxyPort;
|
|
if (Pref.enableSystemProxy) {
|
|
systemProxyHost = Pref.systemProxyHost;
|
|
systemProxyPort = int.tryParse(Pref.systemProxyPort);
|
|
enableSystemProxy = systemProxyPort != null && systemProxyHost.isNotEmpty;
|
|
} else {
|
|
enableSystemProxy = false;
|
|
}
|
|
|
|
final http11Adapter = IOHttpClientAdapter(
|
|
createHttpClient: enableSystemProxy
|
|
? () => HttpClient()
|
|
..idleTimeout = const Duration(seconds: 15)
|
|
..autoUncompress = false
|
|
..findProxy = ((_) => 'PROXY $systemProxyHost:$systemProxyPort')
|
|
..badCertificateCallback = (cert, host, port) => true
|
|
: () => HttpClient()
|
|
..idleTimeout = const Duration(seconds: 15)
|
|
..autoUncompress = false, // Http2Adapter没有自动解压, 统一行为
|
|
);
|
|
|
|
final connectionManager = _enableHttp2
|
|
? ConnectionManager(
|
|
idleTimeout: const Duration(seconds: 15),
|
|
onClientCreate: enableSystemProxy
|
|
? (_, config) => config
|
|
..proxy = Uri(
|
|
scheme: 'http',
|
|
host: systemProxyHost,
|
|
port: systemProxyPort,
|
|
)
|
|
..onBadCertificate = (_) => true
|
|
: Pref.badCertificateCallback
|
|
? (_, config) => config.onBadCertificate = (_) => true
|
|
: null,
|
|
)
|
|
: null;
|
|
return (http11Adapter, connectionManager);
|
|
}
|
|
|
|
@pragma('vm:notify-debugger-on-exception')
|
|
static void _resetAdaptersForNetworkChange() {
|
|
try {
|
|
final (h11, connectionManager) = _createPool();
|
|
if (connectionManager != null) {
|
|
(dio.httpClientAdapter as Http2Adapter)
|
|
..connectionManager.close(force: true)
|
|
..connectionManager = connectionManager
|
|
..fallbackAdapter.close(force: true)
|
|
..fallbackAdapter = h11;
|
|
_http11Dio?.httpClientAdapter = h11;
|
|
} else {
|
|
dio
|
|
..httpClientAdapter.close(force: true)
|
|
..httpClientAdapter = h11;
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
/*
|
|
* config it and create
|
|
*/
|
|
Request._internal() {
|
|
//BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
|
|
BaseOptions options = BaseOptions(
|
|
//请求基地址,可以包含子路径
|
|
baseUrl: HttpString.apiBaseUrl,
|
|
//连接服务器超时时间,单位是毫秒.
|
|
connectTimeout: const Duration(milliseconds: 10000),
|
|
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
|
receiveTimeout: const Duration(milliseconds: 10000),
|
|
//Http请求头.
|
|
headers: {
|
|
'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头
|
|
if (!_enableHttp2) 'connection': 'keep-alive',
|
|
'accept-encoding': 'br,gzip',
|
|
},
|
|
responseDecoder: _responseDecoder, // Http2Adapter没有自动解压
|
|
persistentConnection: true,
|
|
);
|
|
|
|
final (h11, connectionManager) = _createPool();
|
|
|
|
dio = Dio(options)
|
|
..httpClientAdapter = _enableHttp2
|
|
? Http2Adapter(connectionManager, fallbackAdapter: h11)
|
|
: h11;
|
|
|
|
// 先于其他Interceptor
|
|
if (Pref.retryCount != 0) {
|
|
dio.interceptors.add(
|
|
RetryInterceptor(dio, Pref.retryCount, Pref.retryDelay),
|
|
);
|
|
}
|
|
|
|
// 日志拦截器 输出请求、响应内容
|
|
if (kDebugMode) {
|
|
dio.interceptors.add(
|
|
LogInterceptor(
|
|
request: false,
|
|
requestHeader: false,
|
|
responseHeader: false,
|
|
),
|
|
);
|
|
}
|
|
|
|
dio
|
|
..transformer = BackgroundTransformer()
|
|
..options.validateStatus = (int? status) {
|
|
return status != null && status >= 200 && status < 300;
|
|
};
|
|
|
|
if (Platform.isIOS) _watchConnectivity();
|
|
}
|
|
|
|
/*
|
|
* get请求
|
|
*/
|
|
Future<Response> get<T>(
|
|
String url, {
|
|
Map<String, dynamic>? queryParameters,
|
|
Options? options,
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
try {
|
|
return await dio.get<T>(
|
|
url,
|
|
queryParameters: queryParameters,
|
|
options: options,
|
|
cancelToken: cancelToken,
|
|
);
|
|
} on DioException catch (e) {
|
|
return Response(
|
|
data: {
|
|
'message': await AccountManager.dioError(e),
|
|
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
|
statusCode: e.response?.statusCode ?? -1,
|
|
requestOptions: e.requestOptions,
|
|
);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* post请求
|
|
*/
|
|
Future<Response> post<T>(
|
|
String url, {
|
|
Object? data,
|
|
Map<String, dynamic>? queryParameters,
|
|
Options? options,
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
// if (kDebugMode) debugPrint('post-data: $data');
|
|
try {
|
|
return await dio.post<T>(
|
|
url,
|
|
data: data,
|
|
queryParameters: queryParameters,
|
|
options: options,
|
|
cancelToken: cancelToken,
|
|
);
|
|
} on DioException catch (e) {
|
|
AccountManager.toast(e);
|
|
return Response(
|
|
data: {
|
|
'message': await AccountManager.dioError(e),
|
|
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
|
statusCode: e.response?.statusCode ?? -1,
|
|
requestOptions: e.requestOptions,
|
|
);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 下载文件
|
|
*/
|
|
Future<Response> downloadFile(
|
|
String urlPath,
|
|
String savePath, {
|
|
CancelToken? cancelToken,
|
|
}) async {
|
|
try {
|
|
return await dio.download(
|
|
urlPath,
|
|
savePath,
|
|
cancelToken: cancelToken,
|
|
// onReceiveProgress: (int count, int total) {
|
|
// 进度
|
|
// if (kDebugMode) debugPrint("$count $total");
|
|
// },
|
|
);
|
|
// if (kDebugMode) debugPrint('downloadFile success: ${response.data}');
|
|
} on DioException catch (e) {
|
|
// if (kDebugMode) debugPrint('downloadFile error: $e');
|
|
return Response(
|
|
data: {
|
|
'message': await AccountManager.dioError(e),
|
|
},
|
|
statusCode: e.response?.statusCode ?? -1,
|
|
requestOptions: e.requestOptions,
|
|
);
|
|
}
|
|
}
|
|
|
|
static List<int> responseBytesDecoder(
|
|
List<int> responseBytes,
|
|
Map<String, List<String>> headers,
|
|
) => switch (headers['content-encoding']?.firstOrNull) {
|
|
'gzip' => _gzipDecoder.decodeBytes(responseBytes),
|
|
'br' => _brotliDecoder.convert(responseBytes),
|
|
_ => responseBytes,
|
|
};
|
|
|
|
static String _responseDecoder(
|
|
List<int> responseBytes,
|
|
RequestOptions options,
|
|
ResponseBody responseBody,
|
|
) => utf8.decode(
|
|
responseBytesDecoder(responseBytes, responseBody.headers),
|
|
allowMalformed: true,
|
|
);
|
|
}
|