Files
PiliPlus/lib/http/init.dart
dom 87bb00e9c5 tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-05-09 21:37:07 +08:00

293 lines
8.4 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
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_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:archive/archive.dart';
import 'package:brotli/brotli.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
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 (IOHttpClientAdapter, ConnectionManager?) _createPool() {
final http11Adapter = IOHttpClientAdapter(
createHttpClient: () => HttpClient()
..idleTimeout = const Duration(seconds: 15)
..autoUncompress = false, // Http2Adapter没有自动解压, 统一行为
);
final connectionManager = _enableHttp2
? ConnectionManager(
idleTimeout: const Duration(seconds: 15),
onClientCreate: Pref.badCertificateCallback
? (_, config) => config.onBadCertificate = (_) => true
: null,
)
: null;
return (http11Adapter, connectionManager);
}
/*
* 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;
};
}
/*
* 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,
);
}