From 2ad0c8453c034917083c6ab6c4f0ffadae836b5e Mon Sep 17 00:00:00 2001 From: Liplum Date: Tue, 30 Apr 2024 15:32:44 +0800 Subject: [PATCH] debug network --- lib/init.dart | 4 --- lib/network/dio.dart | 29 +++++++++++++++++ lib/network/proxy.dart | 3 ++ lib/r.dart | 2 ++ lib/session/sso.dart | 58 ++++++++++++++++++---------------- lib/settings/page/storage.dart | 5 +-- 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/lib/init.dart b/lib/init.dart index ba545ef92..a6e590733 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -75,10 +75,6 @@ class Init { ssoSession = SsoSession( dio: dio, cookieJar: cookieJar, - onError: (error, stackTrace) { - debugPrint(error.toString()); - debugPrintStack(stackTrace: stackTrace); - }, inputCaptcha: (Uint8List imageBytes) async { final context = $key.currentContext!; // return await context.show$Sheet$( diff --git a/lib/network/dio.dart b/lib/network/dio.dart index f9ff1d59e..a2d68c03b 100644 --- a/lib/network/dio.dart +++ b/lib/network/dio.dart @@ -6,6 +6,7 @@ import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:fk_user_agent/fk_user_agent.dart'; import 'package:flutter/foundation.dart'; import 'package:sit/r.dart'; +import 'package:sit/session/sso.dart'; final _rand = Random(); @@ -18,6 +19,14 @@ class DioInit { if (!kIsWeb) { dio.interceptors.add(CookieManager(cookieJar)); } + if (kDebugMode) { + dio.interceptors.add(LogInterceptor(logPrint: (obj) { + networkLogger.i(obj); + })); + } + if (kDebugMode && R.debugNetwork) { + dio.interceptors.add(PoorNetworkDioInterceptor()); + } if (config != null) { dio.options = config; } @@ -44,3 +53,23 @@ class DioInit { } } } + +class PoorNetworkDioInterceptor extends Interceptor { + @override + Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async { + final duration = Duration(milliseconds: _rand.nextInt(5000)); + debugPrint("Start to request ${options.uri}"); + await Future.delayed(duration); + debugPrint("Delayed Request ${options.uri} $duration"); + handler.next(options); + } + + @override + Future onResponse(Response response, ResponseInterceptorHandler handler) async { + final duration = Duration(milliseconds: _rand.nextInt(5000)); + debugPrint("Start to response ${response.realUri}"); + await Future.delayed(duration); + debugPrint("Delayed Response ${response.realUri} $duration"); + handler.next(response); + } +} diff --git a/lib/network/proxy.dart b/lib/network/proxy.dart index e5677ab37..fd1770e9b 100644 --- a/lib/network/proxy.dart +++ b/lib/network/proxy.dart @@ -12,6 +12,9 @@ class SitHttpOverrides extends HttpOverrides { final client = super.createHttpClient(context); client.badCertificateCallback = (cert, host, port) => true; client.findProxy = (url) { + if (kDebugMode) { + print('Accessing "$url", captured by $SitHttpOverrides'); + } final host = url.host; final isSchoolLanRequired = _isSchoolLanRequired(host); final profiles = _buildProxy(isSchoolLanRequired); diff --git a/lib/r.dart b/lib/r.dart index 6516d3681..5892628b5 100644 --- a/lib/r.dart +++ b/lib/r.dart @@ -23,6 +23,8 @@ class R { /// For debugging iOS on other platforms. static const debugCupertino = kDebugMode ? false : false; + static const debugNetwork = true; + /// The default window size is small enough for any modern desktop device. static const Size defaultWindowSize = Size(500, 800); diff --git a/lib/session/sso.dart b/lib/session/sso.dart index 21cf260ce..925cb031f 100644 --- a/lib/session/sso.dart +++ b/lib/session/sso.dart @@ -5,6 +5,7 @@ import 'package:collection/collection.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart' hide Key; +import 'package:logger/logger.dart'; import 'package:sit/credentials/entity/credential.dart'; import 'package:sit/credentials/error.dart'; import 'package:sit/credentials/init.dart'; @@ -46,6 +47,16 @@ const _neededHeaders = { "Referer": "https://authserver.sit.edu.cn/authserver/login", }; +final networkLogger = Logger( + printer: PrettyPrinter( + methodCount: 8, + // Number of method calls to be displayed + errorMethodCount: 8, + // Print an emoji for each log message + printTime: true, // Should each log print contain a timestamp + ), +); + /// Single Sign-On class SsoSession { static const String _authServerUrl = 'https://authserver.sit.edu.cn/authserver'; @@ -57,39 +68,36 @@ class SsoSession { final Dio dio; final CookieJar cookieJar; - /// Session错误拦截器 - final void Function(Object error, StackTrace stackTrace)? onError; - /// Input captcha manually final Future Function(Uint8List imageBytes) inputCaptcha; /// Lock it to prevent simultaneous login. - final _loginLock = Lock(); + static final _loginLock = Lock(); SsoSession({ required this.dio, required this.cookieJar, required this.inputCaptcha, - this.onError, }); /// - User try to log in actively on a login page. Future loginLocked(Credentials credentials) async { return await _loginLock.synchronized(() async { + networkLogger.i("loginLocked ${DateTime.now().toIso8601String()}"); try { - final autoCaptcha = await _login( + final byAutoCaptcha = await _login( credentials: credentials, inputCaptcha: (captchaImage) => AuthSession.recognizeOaCaptcha(captchaImage), ); - return autoCaptcha; + return byAutoCaptcha; } catch (error, stackTrace) { debugPrintError(error, stackTrace); } - final manuallyCaptcha = await _login( + final byManualCaptcha = await _login( credentials: credentials, inputCaptcha: inputCaptcha, ); - return manuallyCaptcha; + return byManualCaptcha; }); } @@ -103,7 +111,6 @@ class SsoSession { }) async { options ??= Options(); - /// 正常地请求 Future requestNormally() async { final response = await dio.request( url, @@ -118,11 +125,10 @@ class SsoSession { onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, ); - // 处理重定向 return await processRedirect(dio, response, headers: _neededHeaders); } - // 第一次先正常请求 + // request normally at first final firstResponse = await requestNormally(); // check if the response is the login page. if so, login it first. @@ -166,7 +172,7 @@ class SsoSession { final Response response; try { // 首先获取AuthServer首页 - final html = await _getAuthServerHtml(); + final html = await _fetchAuthServerHtml(); var captcha = ''; if (await isCaptchaRequired(credentials.account)) { final captchaImage = await getCaptcha(); @@ -179,9 +185,9 @@ class SsoSession { } } // 获取casTicket - final casTicket = _getCasTicketFromAuthHtml(html); + final casTicket = _extractCasTicketFromAuthHtml(html); // 获取salt - final salt = _getSaltFromAuthHtml(html); + final salt = _extractSaltFromAuthHtml(html); // 加密密码 final hashedPwd = _hashPassword(salt, credentials.password); // 登录系统,获得cookie @@ -234,8 +240,8 @@ class SsoSession { return CredentialsErrorType.accountPassword; } - /// 提取认证页面中的加密盐 - String _getSaltFromAuthHtml(String htmlText) { + /// Extract the Salt from the auth page + String _extractSaltFromAuthHtml(String htmlText) { final a = RegExp(r'var pwdDefaultEncryptSalt = "(.*?)";'); final matchResult = a.firstMatch(htmlText)!.group(0)!; final salt = matchResult.substring(29, matchResult.length - 2); @@ -243,8 +249,8 @@ class SsoSession { return salt; } - /// 提取认证页面中的Cas Ticket - String _getCasTicketFromAuthHtml(String htmlText) { + /// Extract the CAS ticket from the auth page + String _extractCasTicketFromAuthHtml(String htmlText) { final a = RegExp(r' _getAuthServerHtml() async { + /// Fetch the auth page, where the account, password and captcha box are. + Future _fetchAuthServerHtml() async { final response = await dio.get( _loginUrl, options: Options(headers: Map.from(_neededHeaders)..remove('Referer')), @@ -261,7 +267,7 @@ class SsoSession { return response.data; } - /// 判断是否需要验证码 + /// check if captcha is required for this logging in Future isCaptchaRequired(String username) async { final response = await dio.get( _needCaptchaUrl, @@ -276,7 +282,6 @@ class SsoSession { return needCaptcha; } - /// 获取验证码 Future getCaptcha() async { final response = await dio.get( _captchaUrl, @@ -289,9 +294,9 @@ class SsoSession { return captchaData; } - /// 登录统一认证平台 + /// Login the single sign-on Future _postLoginRequest(String username, String hashedPassword, String captcha, String casTicket) async { - // 登录系统 + // Login final res = await dio.post(_loginUrl, data: { 'username': username, @@ -311,7 +316,6 @@ class SsoSession { }, headers: _neededHeaders, )); - // 处理重定向 return await processRedirect(dio, res, headers: _neededHeaders); } @@ -331,7 +335,7 @@ class SsoSession { options: options, ); } catch (error, stackTrace) { - onError?.call(error, stackTrace); + debugPrintError(error, stackTrace); rethrow; } } diff --git a/lib/settings/page/storage.dart b/lib/settings/page/storage.dart index 58265ad02..91e92aebf 100644 --- a/lib/settings/page/storage.dart +++ b/lib/settings/page/storage.dart @@ -105,8 +105,9 @@ class _BoxSectionState extends State { final boxNameStyle = ctx.textTheme.headlineSmall; final action = PullDownMenuButton( itemBuilder: (ctx) => [ - PullDownItem.edit( - title: i18n.edit, + PullDownItem.delete( + icon: context.icons.delete, + title: i18n.delete, onTap: () async { final confirm = await _showDeleteBoxRequest(ctx); if (confirm == true) {