diff --git a/lib/common/constants.dart b/lib/common/constants.dart index fb081729..3d1f8622 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -23,8 +23,8 @@ class Constants { static const String userAgent = 'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/2001100 channel/yingyongbao innerVer/2001100 osVer/14 network/2'; static const String statistics = - '%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D'; - //Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}'); + '{"appId":5,"platform":3,"version":"1.46.2","abtest":""}'; + // 请求时会自动encodeComponent static const urlPattern = r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]'; diff --git a/lib/http/api.dart b/lib/http/api.dart index 155e7f29..9d86d9f0 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -619,7 +619,7 @@ class Api { /// 申请二维码(TV端) static const getTVCode = - 'https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code'; + '${HttpString.passBaseUrl}/x/passport-tv-login/qrcode/auth_code'; ///扫码登录(TV端) static const qrcodePoll = @@ -662,7 +662,7 @@ class Api { static const getUnreadDynamic = '/x/web-interface/dynamic/entrance'; /// 用户动态主页 - static const dynamicSpmPrefix = 'https://space.bilibili.com/1/dynamic'; + static const dynamicSpmPrefix = '${HttpString.spaceBaseUrl}/1/dynamic'; /// 激活buvid3 static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi'; diff --git a/lib/http/init.dart b/lib/http/init.dart index 788db89a..4250d041 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -4,10 +4,13 @@ import 'dart:developer'; import 'dart:io'; import 'dart:math' show Random; import 'package:PiliPlus/build_config.dart'; +import 'package:archive/archive.dart'; +import 'package:brotli/brotli.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:dio_http2_adapter/dio_http2_adapter.dart'; import 'package:flutter/material.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import '../utils/storage.dart'; @@ -18,6 +21,9 @@ import 'interceptor.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web; class Request { + static const gzipDecoder = GZipDecoder(); + static const brotilDecoder = BrotliDecoder(); + static final Request _instance = Request._internal(); static late CookieManager cookieManager; static late final Dio dio; @@ -52,18 +58,18 @@ class Request { ); } final userInfo = GStorage.userInfo.get('userInfoCache'); - if (userInfo != null && userInfo.mid != null) { - final List cookie2 = await cookieManager.cookieJar + if (userInfo?.mid != null) { + final List tUrlCookies = await cookieManager.cookieJar .loadForRequest(Uri.parse(HttpString.tUrl)); - if (cookie2.isEmpty) { + if (tUrlCookies.isEmpty) { try { - await Request().get(HttpString.tUrl); + await dio.head(HttpString.tUrl); } catch (e) { log("setCookie, ${e.toString()}"); } } + setOptionsHeaders(userInfo); } - setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null); try { await buvidActivate(); @@ -81,23 +87,15 @@ class Request { static Future getCsrf() async { List cookies = await cookieManager.cookieJar .loadForRequest(Uri.parse(HttpString.apiBaseUrl)); - String token = ''; - if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) { - token = cookies.firstWhere((e) => e.name == 'bili_jct').value; - } - return token; + return cookies + .firstWhere((e) => e.name == 'bili_jct', orElse: () => Cookie('', '')) + .value; } - static setOptionsHeaders(userInfo, bool status) { - if (status) { - dio.options.headers['x-bili-mid'] = userInfo.mid.toString(); - dio.options.headers['x-bili-aurora-eid'] = - IdUtils.genAuroraEid(userInfo.mid); - } - dio.options.headers['env'] = 'prod'; - dio.options.headers['app-key'] = 'android64'; - dio.options.headers['x-bili-aurora-zone'] = 'sh001'; - dio.options.headers['referer'] = 'https://www.bilibili.com/'; + static setOptionsHeaders(userInfo) { + dio.options.headers['x-bili-mid'] = userInfo.mid.toString(); + dio.options.headers['x-bili-aurora-eid'] = + IdUtils.genAuroraEid(userInfo.mid); } static Future buvidActivate() async { @@ -121,7 +119,7 @@ class Request { await Request().post(Api.activateBuvidApi, data: {'payload': jsonData}, - options: Options(contentType: 'application/json')); + options: Options(contentType: Headers.jsonContentType)); } /* @@ -130,15 +128,24 @@ class Request { Request._internal() { //BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 BaseOptions options = BaseOptions( - //请求基地址,可以包含子路径 - baseUrl: HttpString.apiBaseUrl, - //连接服务器超时时间,单位是毫秒. - connectTimeout: const Duration(milliseconds: 12000), - //响应流上前后两次接受到数据的间隔,单位为毫秒。 - receiveTimeout: const Duration(milliseconds: 12000), - //Http请求头. - headers: {}, - ); + //请求基地址,可以包含子路径 + baseUrl: HttpString.apiBaseUrl, + //连接服务器超时时间,单位是毫秒. + connectTimeout: const Duration(milliseconds: 12000), + //响应流上前后两次接受到数据的间隔,单位为毫秒。 + receiveTimeout: const Duration(milliseconds: 12000), + //Http请求头. + headers: { + 'connection': 'keep-alive', + 'accept-encoding': 'br,gzip', + 'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头 + 'referer': HttpString.baseUrl, + 'env': 'prod', + 'app-key': 'android64', + 'x-bili-aurora-zone': 'sh001', + }, + responseDecoder: responseDecoder, // Http2Adapter没有自动解压 + persistentConnection: true); enableSystemProxy = GStorage.setting .get(SettingBoxKey.enableSystemProxy, defaultValue: false) as bool; @@ -147,33 +154,36 @@ class Request { systemProxyPort = GStorage.setting.get(SettingBoxKey.systemProxyPort, defaultValue: ''); - dio = Dio(options); + final http11Adapter = IOHttpClientAdapter(createHttpClient: () { + final client = HttpClient() + ..idleTimeout = const Duration(seconds: 30) + ..autoUncompress = false; // Http2Adapter没有自动解压, 统一行为 + // 设置代理 + if (enableSystemProxy) { + client.findProxy = (_) => 'PROXY $systemProxyHost:$systemProxyPort'; + client.badCertificateCallback = + (X509Certificate cert, String host, int port) => true; + } + return client; + }); - /// fix 第三方登录 302重定向 跟iOS代理问题冲突 - // ..httpClientAdapter = Http2Adapter( - // ConnectionManager( - // idleTimeout: const Duration(milliseconds: 10000), - // onClientCreate: (context, ClientSetting config) => - // config.onBadCertificate = (_) => true, - // ), - // ); - - /// 设置代理 - if (enableSystemProxy) { - dio.httpClientAdapter = IOHttpClientAdapter( - createHttpClient: () { - final HttpClient client = HttpClient(); - // Config the client. - client.findProxy = (Uri uri) { - // return 'PROXY host:port'; - return 'PROXY $systemProxyHost:$systemProxyPort'; - }; - client.badCertificateCallback = - (X509Certificate cert, String host, int port) => true; - return client; - }, - ); - } + 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)); + } + }), + fallbackAdapter: http11Adapter) + : http11Adapter; // 日志拦截器 输出请求、响应内容 if (BuildConfig.isDebug) { @@ -186,8 +196,7 @@ class Request { dio.transformer = BackgroundTransformer(); dio.options.validateStatus = (int? status) { - return status! >= 200 && status < 300 || - HttpString.validateStatusCodes.contains(status); + return status! >= 200 && status < 300; }; } @@ -285,19 +294,24 @@ class Request { } static String headerUa({type = 'mob'}) { - String headerUa = ''; - if (type == 'mob') { - if (Platform.isIOS) { - headerUa = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1'; - } else { - headerUa = - 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36'; - } - } else { - headerUa = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15'; + return type == 'mob' + ? Platform.isIOS + ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1' + : 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36' + : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15'; + } + + static String responseDecoder(List responseBytes, RequestOptions options, + ResponseBody responseBody) { + switch (responseBody.headers['content-encoding']?.firstOrNull) { + case 'gzip': + return utf8.decode(gzipDecoder.decodeBytes(responseBytes), + allowMalformed: true); + case 'br': + return utf8.decode(brotilDecoder.convert(responseBytes), + allowMalformed: true); + default: + return utf8.decode(responseBytes, allowMalformed: true); } - return headerUa; } } diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index b3ab9b3b..d5e54369 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -32,7 +32,7 @@ class ApiInterceptor extends Interceptor { options.headers.remove('x-bili-mid'); options.headers.remove('x-bili-aurora-eid'); options.headers.remove('x-bili-aurora-zone'); - options.headers['cookie'] = ''; + options.headers.remove('cookie'); options.queryParameters.remove('access_key'); options.queryParameters.remove('csrf'); options.queryParameters.remove('csrf_token'); @@ -45,7 +45,7 @@ class ApiInterceptor extends Interceptor { // app端不需要cookie if (options.uri.host == 'app.bilibili.com') { - options.headers['cookie'] = ''; + options.headers.remove('cookie'); } if (options.extra['clearCookie'] == true) { @@ -139,5 +139,5 @@ class ApiInterceptor extends Interceptor { } extension _ConnectivityResultExt on ConnectivityResult { - String get title => ['蓝牙', 'Wi-Fi', '局域', '流量', '无', '代理', '其他'][index]; + String get title => const ['蓝牙', 'Wi-Fi', '局域', '流量', '无', '代理', '其他'][index]; } diff --git a/lib/http/video.dart b/lib/http/video.dart index 3b0ba31a..6d89a343 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -120,7 +120,6 @@ class VideoHttp { Api.recommendListApp, queryParameters: data, options: Options(headers: { - 'Host': 'app.bilibili.com', 'buvid': LoginHttp.buvid, 'fp_local': '1111111111111111111111111111111111111111111111111111111111111111', diff --git a/lib/main.dart b/lib/main.dart index bc55bc37..027d7236 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -54,9 +54,7 @@ void main() async { ], ); } - if (BuildConfig.isDebug || GStorage.badCertificateCallback) { - HttpOverrides.global = _CustomHttpOverrides(); - } + HttpOverrides.global = _CustomHttpOverrides(); await setupServiceLocator(); Request(); await Request.setCookie(); @@ -294,10 +292,18 @@ class MyApp extends StatelessWidget { } class _CustomHttpOverrides extends HttpOverrides { + static final badCertificateCallback = + BuildConfig.isDebug || GStorage.badCertificateCallback; + @override HttpClient createHttpClient(SecurityContext? context) { - return super.createHttpClient(context) - ..badCertificateCallback = + final client = super.createHttpClient(context) + ..maxConnectionsPerHost = 32 + ..idleTimeout = const Duration(seconds: 30); + if (badCertificateCallback) { + client.badCertificateCallback = (X509Certificate cert, String host, int port) => true; + } + return client; } } diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 07576271..a7924c34 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -2181,6 +2181,14 @@ List get extraSettings => [ setKey: SettingBoxKey.defaultShowComment, defaultVal: false, ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '启用HTTP/2', + subtitle: '测试中', + leading: Icon(Icons.swap_horizontal_circle_outlined), + setKey: SettingBoxKey.enableHttp2, + defaultVal: false, + ), SettingsModel( settingsType: SettingsType.normal, title: '评论展示', diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 73079682..bbed5986 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -644,6 +644,7 @@ class SettingBoxKey { expandDynLivePanel = 'expandDynLivePanel', springDescription = 'springDescription', collapsibleVideoPage = 'collapsibleVideoPage', + enableHttp2 = 'enableHttp2', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock',