From abb5429d61b820136f67ed98f58014cfee1ca96b Mon Sep 17 00:00:00 2001 From: wanghongenpin <178070584@qq.com> Date: Tue, 19 Sep 2023 19:54:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=AF=BC?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/network/handler.dart | 6 +- lib/network/http/http.dart | 16 +++- lib/ui/component/utils.dart | 14 +-- lib/ui/content/body.dart | 16 ++-- lib/ui/desktop/left/domain.dart | 2 +- lib/ui/desktop/left/history.dart | 16 +++- lib/utils/har.dart | 92 +++++++++++++------ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Runner/RunnerProfile.entitlements | 2 + pubspec.lock | 80 ++++++++++++++-- pubspec.yaml | 1 + test/tests.dart | 7 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 16 files changed, 205 insertions(+), 58 deletions(-) diff --git a/lib/network/handler.dart b/lib/network/handler.dart index f4af301..d752ea1 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -236,7 +236,9 @@ class HttpChannelHandler extends ChannelHandler { ..body = message.codeUnits ..hostAndPort = hostAndPort; - request.response = HttpResponse(status)..body = message.codeUnits; + request.response = HttpResponse(status) + ..headers.contentType = 'text/plain' + ..body = message.codeUnits; listener?.onRequest(channel, request); listener?.onResponse(channel, request.response!); } @@ -263,7 +265,7 @@ class HttpResponseProxyHandler extends ChannelHandler { msg.body = utf8.encode(replaceBody!); } - if (!HostFilter.filter(msg.request?.hostAndPort?.host)) { + if (!HostFilter.filter(msg.request?.hostAndPort?.host) && msg.request?.method != HttpMethod.connect) { listener?.onResponse(clientChannel, msg); } diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 306afb2..752a89b 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -85,7 +85,7 @@ class HttpRequest extends HttpMessage { late HttpMethod method; HostAndPort? hostAndPort; - final DateTime requestTime = DateTime.now(); + DateTime requestTime = DateTime.now(); //请求时间 HttpResponse? response; HttpRequest(this.method, this.uri, {String protocolVersion = "HTTP/1.1"}) : super(protocolVersion); @@ -137,6 +137,7 @@ class HttpRequest extends HttpMessage { 'method': method.name, 'headers': headers.toJson(), 'body': body == null ? null : String.fromCharCodes(body!), + 'requestTime': requestTime.millisecondsSinceEpoch, }; } @@ -144,6 +145,9 @@ class HttpRequest extends HttpMessage { var request = HttpRequest(HttpMethod.valueOf(json['method']), json['uri']); request.headers.addAll(HttpHeaders.fromJson(json['headers'])); request.body = json['body']?.toString().codeUnits; + if (json['requestTime'] != null) { + request.requestTime = DateTime.fromMillisecondsSinceEpoch(json['requestTime']); + } return request; } @@ -173,7 +177,7 @@ enum ContentType { ///HTTP响应。 class HttpResponse extends HttpMessage { final HttpStatus status; - final DateTime responseTime = DateTime.now(); + DateTime responseTime = DateTime.now(); HttpRequest? request; HttpResponse(this.status, {String protocolVersion = "HTTP/1.1"}) : super(protocolVersion); @@ -185,11 +189,16 @@ class HttpResponse extends HttpMessage { return '${responseTime.difference(request!.requestTime).inMilliseconds}ms'; } + //json序列化 factory HttpResponse.fromJson(Map json) { - return HttpResponse(HttpStatus(json['status']['code'], json['status']['reasonPhrase']), + var httpResponse = HttpResponse(HttpStatus(json['status']['code'], json['status']['reasonPhrase']), protocolVersion: json['protocolVersion']) ..headers.addAll(HttpHeaders.fromJson(json['headers'])) ..body = json['body']?.toString().codeUnits; + if (json['responseTime'] != null) { + httpResponse.responseTime = DateTime.fromMillisecondsSinceEpoch(json['responseTime']); + } + return httpResponse; } @override @@ -203,6 +212,7 @@ class HttpResponse extends HttpMessage { }, 'headers': headers.toJson(), 'body': body == null ? null : String.fromCharCodes(body!), + 'responseTime': responseTime.millisecondsSinceEpoch, }; } diff --git a/lib/ui/component/utils.dart b/lib/ui/component/utils.dart index ddf73fb..d05c1e5 100644 --- a/lib/ui/component/utils.dart +++ b/lib/ui/component/utils.dart @@ -86,13 +86,13 @@ Widget contextMenu(BuildContext context, EditableTextState editableTextState) { type: ContextMenuButtonType.selectAll, ) ]; - if (Platforms.isDesktop()) { - list.add(ContextMenuButtonItem( - onPressed: () async { - editableTextState.shareSelection(SelectionChangedCause.toolbar); - }, - type: ContextMenuButtonType.share, - )); + if (Platforms.isMobile()) { + // list.add(ContextMenuButtonItem( + // onPressed: () async { + // editableTextState.shareSelection(SelectionChangedCause.toolbar); + // }, + // type: ContextMenuButtonType.share, + // )); } return AdaptiveTextSelectionToolbar.buttonItems( diff --git a/lib/ui/content/body.dart b/lib/ui/content/body.dart index 750d525..1d835c3 100644 --- a/lib/ui/content/body.dart +++ b/lib/ui/content/body.dart @@ -119,9 +119,9 @@ class HttpBodyState extends State { var list = [ Text('$type Body', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), - const SizedBox(width: 15), + const SizedBox(width: 10), IconButton( - icon: const Icon(Icons.copy, size: 20), + icon: const Icon(Icons.copy, size: 18), tooltip: '复制', onPressed: () { var body = bodyKey.currentState?.body; @@ -133,9 +133,9 @@ class HttpBodyState extends State { ]; if (!widget.hideRequestRewrite) { - list.add(const SizedBox(width: 5)); + list.add(const SizedBox(width: 3)); list.add(IconButton( - icon: const Icon(Icons.edit_document, size: 20), + icon: const Icon(Icons.edit_document, size: 18), tooltip: '请求重写', onPressed: () { HttpRequest? request; @@ -170,16 +170,16 @@ class HttpBodyState extends State { })); } - list.add(const SizedBox(width: 5)); + list.add(const SizedBox(width: 3)); list.add(IconButton( - icon: const Icon(Icons.abc, size: 25), + icon: const Icon(Icons.abc, size: 20), tooltip: '编码', onPressed: () { encodeWindow(EncoderType.base64, context, bodyKey.currentState?.body); })); if (!inNewWindow) { - list.add(const SizedBox(width: 5)); - list.add(IconButton(icon: const Icon(Icons.open_in_new, size: 20), tooltip: '新窗口打开', onPressed: () => openNew())); + list.add(const SizedBox(width: 3)); + list.add(IconButton(icon: const Icon(Icons.open_in_new, size: 18), tooltip: '新窗口打开', onPressed: () => openNew())); } return Wrap( diff --git a/lib/ui/desktop/left/domain.dart b/lib/ui/desktop/left/domain.dart index 24df20c..e87846f 100644 --- a/lib/ui/desktop/left/domain.dart +++ b/lib/ui/desktop/left/domain.dart @@ -86,7 +86,7 @@ class DomainWidgetState extends State with AutomaticKeepAliveClien Widget body = widget.shrinkWrap ? SingleChildScrollView(child: Column(children: list.toList())) - : ListView.builder(itemCount: list.length, itemBuilder: (_, index) => list.elementAt(index)); + : ListView.builder(itemCount: list.length, cacheExtent: 1000, itemBuilder: (_, index) => list.elementAt(index)); return Scaffold( body: body, diff --git a/lib/ui/desktop/left/history.dart b/lib/ui/desktop/left/history.dart index e60e297..6c7b07d 100644 --- a/lib/ui/desktop/left/history.dart +++ b/lib/ui/desktop/left/history.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:date_format/date_format.dart'; +import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/server.dart'; @@ -142,6 +143,19 @@ class _HistoryState extends State<_HistoryWidget> { return GestureDetector( onSecondaryTapDown: (details) => { showContextMenu(context, details.globalPosition, items: [ + CustomPopupMenuItem( + height: 35, + child: const Text('导出', style: TextStyle(fontSize: 13)), + onTap: () async { + String fileName = 'ProxyPin$name.har'.replaceAll(" ", "_").replaceAll(":", "_"); + final FileSaveLocation? result = await getSaveLocation(suggestedName: fileName); + if (result == null) { + return; + } + List requests = await storage.getRequests(name); + var file = await File(result.path).create(); + Har.writeFile(requests, file, title: name); + }), CustomPopupMenuItem( height: 35, child: const Text('删除', style: TextStyle(fontSize: 13)), @@ -155,7 +169,7 @@ class _HistoryState extends State<_HistoryWidget> { storage.removeHistory(name); FlutterToastr.show('删除成功', context); }); - }) + }), ]) }, child: ListTile( diff --git a/lib/utils/har.dart b/lib/utils/har.dart index 07a9053..522d1ef 100644 --- a/lib/utils/har.dart +++ b/lib/utils/har.dart @@ -8,14 +8,15 @@ class Har { static int maxBodyLength = 1024 * 1024 * 4; static List _entries(List list) { - return list.map((e) => toHar(e)).toList(); + return list.where((element) => element.response != null).map((e) => toHar(e)).toList(); } static Map toHar(HttpRequest request) { bool isImage = request.response?.contentType == ContentType.image; Map har = { - "startedDateTime": request.requestTime.toIso8601String(), // 请求发出的时间(ISO 8601) + "startedDateTime": request.requestTime.toUtc().toIso8601String(), // 请求发出的时间(ISO 8601) "time": request.response?.responseTime.difference(request.requestTime).inMilliseconds, + "pageref": "ProxyPin", // 页面标识 "request": { "method": request.method.name, // 请求方法 "url": request.requestUrl, // 请求地址 @@ -23,14 +24,10 @@ class Har { "cookies": [], // 请求携带的cookie "headers": _headers(request), // 请求头 "queryString": [], // 请求参数 - "postData": { - "mimeType": request.headers.contentType, // 请求体类型 - "text": request.bodyAsString, // 请求体内容 - }, + "postData": _getPostData(request), // 请求体 "headersSize": -1, // 请求头大小 "bodySize": request.body?.length ?? -1, // 请求体大小 }, - "cache": {}, 'timings': { 'send': 0, @@ -40,32 +37,38 @@ class Har { 'serverIPAddress': request.response?.remoteAddress }; - if (request.response != null) { - har['response'] = { - "status": request.response?.status.code, // 响应状态码 - "statusText": request.response?.status.reasonPhrase, // 响应状态码描述 - "httpVersion": request.response?.protocolVersion, // HTTP协议版本 - "cookies": [], // 响应携带的cookie - "headers": _headers(request.response), // 响应头 - "content": { - "size": isImage ? 0 : request.response?.body?.length, // 响应体大小 - "mimeType": request.response?.headers.contentType, // 响应体类型 - "text": isImage ? '' : request.response?.bodyAsString, // 响应体内容 - }, - "redirectURL": '', // 重定向地址 - "headersSize": -1, // 响应头大小 - "bodySize": request.response?.body?.length ?? -1, // 响应体大小 - }; - } + har['response'] = { + "status": request.response?.status.code ?? 0, // 响应状态码 + "statusText": request.response?.status.reasonPhrase ?? '', // 响应状态码描述 + "httpVersion": request.response?.protocolVersion ?? 'HTTP/1.1', // HTTP协议版本 + "cookies": [], // 响应携带的cookie + "headers": _headers(request.response), // 响应头 + "content": { + "size": isImage ? 0 : request.response?.body?.length ?? -1, // 响应体大小 + "mimeType": _getContentType(request.response?.headers.contentType), // 响应体类型 + "text": isImage ? '' : request.response?.bodyAsString, // 响应体内容 + }, + "redirectURL": '', // 重定向地址 + "headersSize": -1, // 响应头大小 + "bodySize": request.response?.body?.length ?? -1, // 响应体大小 + }; return har; } - static Future writeFile(List list, File file) async { + static Future writeFile(List list, File file, {String title = ''}) async { var entries = _entries(list); Map har = {}; har["log"] = { "version": "1.2", - "creator": {"name": "ProxyPin", "version": "1.0.2"}, + "creator": {"name": "ProxyPin", "version": "1.0.3"}, + "pages": [ + { + "title": "[ProxyPin]$title", + "id": "ProxyPin", + "startedDateTime": list.firstOrNull?.requestTime.toUtc().toIso8601String(), + "pageTimings": {"onContentLoad": -1, "onLoad": -1} + } + ], "entries": entries, }; var json = jsonEncode(har); @@ -120,6 +123,43 @@ class Har { httpRequest.response = httpResponse; httpResponse?.request = httpRequest; httpRequest.hostAndPort = HostAndPort.of(httpRequest.requestUrl); + //请求时间 + if (har['startedDateTime'] != null) { + httpRequest.requestTime = DateTime.parse(har['startedDateTime']).toLocal(); + } + if (har['time'] != null) { + httpRequest.response?.responseTime = httpRequest.requestTime.add(Duration(milliseconds: har['time'])); + } return httpRequest; } + + static Map _getPostData(HttpRequest request) { + if (request.contentType == ContentType.formData || request.contentType == ContentType.formUrl) { + return { + "mimeType": request.headers.contentType, // 请求体类型 + "text": request.bodyAsString, // 请求体内容 + "params": request.bodyAsString, // 请求体内容 + }; + } + return { + "mimeType": request.headers.contentType, // 请求体类型 + "text": request.bodyAsString, // 请求体内容 + }; + } + + //获取contentType + static String? _getContentType(String? type) { + if (type == null) { + return ''; + } + var indexOf = type.indexOf("charset="); + if (indexOf == -1) { + return type; + } + var contentType = type.substring(0, indexOf).trimRight(); + if (contentType.endsWith(";")) { + return contentType.substring(0, contentType.length - 1); + } + return contentType; + } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c4e6c1a..c7b52bd 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -16,6 +17,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) desktop_multi_window_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopMultiWindowPlugin"); desktop_multi_window_plugin_register_with_registrar(desktop_multi_window_registrar); + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) proxy_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ProxyManagerPlugin"); proxy_manager_plugin_register_with_registrar(proxy_manager_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e067901..fd6ce5f 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_multi_window + file_selector_linux proxy_manager screen_retriever url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 66dc7b4..35cf907 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import desktop_multi_window +import file_selector_macos import path_provider_foundation import proxy_manager import screen_retriever @@ -15,6 +16,7 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ProxyManagerPlugin.register(with: registry.registrar(forPlugin: "ProxyManagerPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) diff --git a/macos/Runner/RunnerProfile.entitlements b/macos/Runner/RunnerProfile.entitlements index 491484b..e7e2409 100644 --- a/macos/Runner/RunnerProfile.entitlements +++ b/macos/Runner/RunnerProfile.entitlements @@ -14,5 +14,7 @@ com.apple.security.scripting-targets + com.apple.security.files.user-selected.read-write + diff --git a/pubspec.lock b/pubspec.lock index 938804b..8ae8aee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.flutter-io.cn" source: hosted - version: "1.18.0" + version: "1.17.2" convert: dependency: transitive description: @@ -138,6 +138,70 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "7.0.0" + file_selector: + dependency: "direct main" + description: + name: file_selector + sha256: "84eaf3e034d647859167d1f01cfe7b6352488f34c1b4932635012b202014c25b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + file_selector_android: + dependency: transitive + description: + name: file_selector_android + sha256: d41e165d6f798ca941d536e5dc93494d50e78c571c28ad60cfe0b0fefeb9f1e7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.0+3" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: b3fbdda64aa2e335df6e111f6b0f1bb968402ed81d2dd1fa4274267999aa32c2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.1+6" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.1" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: dc6622c4d66cb1bee623ddcc029036603c6cc45c85e4a775bb06008d61c809c1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.2+1" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+1" flutter: dependency: "direct main" description: flutter @@ -434,18 +498,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.flutter-io.cn" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -466,10 +530,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.flutter-io.cn" source: hosted - version: "0.6.1" + version: "0.6.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cd18ccb..f0fd54a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: share_plus: ^7.1.0 brotli: ^0.6.0 installed_apps: ^1.3.1 + file_selector: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter diff --git a/test/tests.dart b/test/tests.dart index 9da7384..b6fd4b7 100644 --- a/test/tests.dart +++ b/test/tests.dart @@ -1,9 +1,12 @@ import 'dart:io'; void main() { + var iso8601string = DateTime.now().toUtc().toIso8601String(); + var parse = DateTime.parse(iso8601string).toLocal(); print(DateTime.now().toIso8601String()); - print(DateTime.now().toString()); - print(DateTime.now().toUtc().toString()); + print(parse.hour); + print(DateTime.parse(iso8601string)); + print(DateTime.now().toUtc().toIso8601String()); print(Platform.version); print(Platform.localHostname); print(Platform.operatingSystem); diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 80a5a79..49a9901 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { DesktopMultiWindowPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); ProxyManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ProxyManagerPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index e935db6..df44e67 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_multi_window + file_selector_windows proxy_manager screen_retriever share_plus