From cfd23cd05924d1d07911fd9a3f5cf958cc01a597 Mon Sep 17 00:00:00 2001 From: wanghongen Date: Sat, 26 Aug 2023 22:04:17 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=A2=9E=E5=8A=A0=E5=BF=AB?= =?UTF-8?q?=E9=80=9F=E8=AF=B7=E6=B1=82=E9=87=8D=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/main.dart | 2 +- lib/network/bin/configuration.dart | 2 +- lib/network/util/request_rewrite.dart | 11 +- lib/storage/favorites.dart | 1 + lib/ui/content/body.dart | 101 ++++++-- .../toolbar/setting/request_rewrite.dart | 59 ++--- lib/ui/mobile/mobile.dart | 16 +- lib/ui/mobile/setting/request_rewrite.dart | 237 +++++++++--------- lib/utils/curl.dart | 2 +- 11 files changed, 239 insertions(+), 196 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 182555d..789b773 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -292,7 +292,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e42adcb..87131a0 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ rules = []; + RequestRewrites._(); + + //单例 + static final RequestRewrites _instance = RequestRewrites._(); + + static RequestRewrites get instance => _instance; + load(Map? map) { if (map == null) { return; @@ -47,7 +53,8 @@ class RequestRewrites { return null; } - addRule(RequestRewriteRule rule) { + void addRule(RequestRewriteRule rule) { + rules.removeWhere((it) => it.path == rule.path && it.domain == rule.domain); rules.add(rule); } diff --git a/lib/storage/favorites.dart b/lib/storage/favorites.dart index 0e1b7ea..a384dba 100644 --- a/lib/storage/favorites.dart +++ b/lib/storage/favorites.dart @@ -69,6 +69,7 @@ class _Item { _Item(this.request, [this.response]) { response ??= request.response; request.response = response; + response?.request = request; } factory _Item.fromJson(Map json) { diff --git a/lib/ui/content/body.dart b/lib/ui/content/body.dart index d26afe4..77760fe 100644 --- a/lib/ui/content/body.dart +++ b/lib/ui/content/body.dart @@ -5,10 +5,14 @@ import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/http/http.dart'; +import 'package:network_proxy/network/util/request_rewrite.dart'; import 'package:network_proxy/ui/component/json/json_viewer.dart'; import 'package:network_proxy/ui/component/json/theme.dart'; import 'package:network_proxy/ui/component/utils.dart'; +import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart'; +import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart'; import 'package:network_proxy/utils/num.dart'; import 'package:network_proxy/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; @@ -99,30 +103,71 @@ class HttpBodyState extends State { return tabController; } + /// 标题 Widget titleWidget({inNewWindow = false}) { var type = widget.httpMessage is HttpRequest ? "Request" : "Response"; + var list = [ + Text('$type Body', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), + const SizedBox(width: 15), + IconButton( + icon: const Icon(Icons.copy), + tooltip: '复制', + onPressed: () { + var body = bodyKey.currentState?.body; + if (body == null) { + return; + } + Clipboard.setData(ClipboardData(text: body)).then((value) => FlutterToastr.show("已复制到剪切板", context)); + }), + ]; + + if (!inNewWindow || Platforms.isMobile()) { + list.add(const SizedBox(width: 5)); + list.add(IconButton( + icon: const Icon(Icons.edit_document), + tooltip: '请求重写', + onPressed: () { + HttpRequest? request; + if (widget.httpMessage is HttpRequest) { + request = widget.httpMessage as HttpRequest; + } else { + request = (widget.httpMessage as HttpResponse).request; + } + + var body = bodyKey.currentState?.body; + var rule = RequestRewriteRule(true, request?.path() ?? '', request?.remoteDomain(), + requestBody: widget.httpMessage is HttpRequest ? body : null, + responseBody: widget.httpMessage is HttpResponse ? body : null); + + if (Platforms.isMobile()) { + Navigator.push(context, MaterialPageRoute(builder: (_) => RewriteRule(rule: rule))).then((value) { + if (value is RequestRewriteRule) { + Configuration.instance.then((it) => it.flushRequestRewriteConfig()); + } + }); + } else { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) => RuleAddDialog(rule: rule)).then((value) { + if (value != null) { + Configuration.instance.then((it) => it.flushRequestRewriteConfig()); + FlutterToastr.show("添加请求重写规则成功", context); + } + }); + } + })); + } + + if (!inNewWindow) { + list.add(const SizedBox(width: 5)); + list.add(IconButton(icon: const Icon(Icons.open_in_new), tooltip: '新窗口打开', onPressed: () => openNew())); + } + return Row( mainAxisAlignment: widget.inNewWindow ? MainAxisAlignment.center : MainAxisAlignment.start, - children: [ - Text('$type Body', style: const TextStyle(fontWeight: FontWeight.w500)), - const SizedBox(width: 15), - IconButton( - icon: const Icon(Icons.copy), - tooltip: '复制', - onPressed: () { - var body = bodyKey.currentState?.body; - if (body == null) { - return; - } - - Clipboard.setData(ClipboardData(text: body)).then((value) => FlutterToastr.show("已复制到剪切板", context)); - }), - const SizedBox(width: 5), - inNewWindow - ? const SizedBox() - : IconButton(icon: const Icon(Icons.open_in_new), tooltip: '新窗口打开', onPressed: () => openNew()) - ], + children: list, ); } @@ -193,14 +238,16 @@ class _BodyState extends State<_Body> { if (viewType == ViewType.hex) { return message!.body!.map(intToHex).join(" "); } - if (viewType == ViewType.formUrl) { - return Uri.decodeFull(message!.bodyAsString); - } - if (viewType == ViewType.jsonText || viewType == ViewType.json) { - //json格式化 - var jsonObject = json.decode(message!.bodyAsString); - return const JsonEncoder.withIndent(" ").convert(jsonObject); - } + try { + if (viewType == ViewType.formUrl) { + return Uri.decodeFull(message!.bodyAsString); + } + if (viewType == ViewType.jsonText || viewType == ViewType.json) { + //json格式化 + var jsonObject = json.decode(message!.bodyAsString); + return const JsonEncoder.withIndent(" ").convert(jsonObject); + } + } catch (_) {} return message!.bodyAsString; } diff --git a/lib/ui/desktop/toolbar/setting/request_rewrite.dart b/lib/ui/desktop/toolbar/setting/request_rewrite.dart index ddddcac..24f8912 100644 --- a/lib/ui/desktop/toolbar/setting/request_rewrite.dart +++ b/lib/ui/desktop/toolbar/setting/request_rewrite.dart @@ -97,33 +97,29 @@ class _RequestRewriteState extends State { barrierDismissible: false, builder: (BuildContext context) { return RuleAddDialog( - requestRewrites: widget.configuration.requestRewrites, currentIndex: currentIndex, - onChange: () { - changed = true; - requestRuleList.changeState(); - }); - }); + rule: currentIndex >= 0 ? widget.configuration.requestRewrites.rules[currentIndex] : null); + }).then((value) { + if (value != null) { + changed = true; + requestRuleList.changeState(); + } + }); } } ///请求重写规则添加对话框 class RuleAddDialog extends StatelessWidget { - final RequestRewrites requestRewrites; final int currentIndex; - final Function onChange; + final RequestRewriteRule? rule; - const RuleAddDialog({super.key, required this.currentIndex, required this.onChange, required this.requestRewrites}); + const RuleAddDialog({super.key, this.currentIndex = -1, this.rule}); @override Widget build(BuildContext context) { GlobalKey formKey = GlobalKey(); - RequestRewriteRule? rule; - if (currentIndex >= 0) { - rule = requestRewrites.rules[currentIndex]; - } - ValueNotifier enableNotifier = ValueNotifier(rule == null || rule.enabled); + ValueNotifier enableNotifier = ValueNotifier(rule == null || rule?.enabled == true); String? domain = rule?.domain; String? path = rule?.path; String? requestBody = rule?.requestBody; @@ -179,17 +175,18 @@ class RuleAddDialog extends StatelessWidget { if ((formKey.currentState as FormState).validate()) { (formKey.currentState as FormState).save(); + var rule = RequestRewriteRule( + enableNotifier.value, path!, domain?.trim().isEmpty == true ? null : domain?.trim(), + requestBody: requestBody, responseBody: responseBody); + if (currentIndex >= 0) { - requestRewrites.rules[currentIndex] = RequestRewriteRule(enableNotifier.value, path!, domain, - requestBody: requestBody, responseBody: responseBody); + RequestRewrites.instance.rules[currentIndex] = rule; } else { - requestRewrites.addRule(RequestRewriteRule(enableNotifier.value, path!, domain, - requestBody: requestBody, responseBody: responseBody)); + RequestRewrites.instance.addRule(rule); } enableNotifier.dispose(); - onChange.call(); - Navigator.of(context).pop(); + Navigator.of(context).pop(rule); } }), ElevatedButton( @@ -210,19 +207,10 @@ class RequestRuleList extends StatefulWidget { State createState() => _RequestRuleListState(); List removeSelected() { + var index = currentSelectedIndex(); var state = (key as GlobalKey<_RequestRuleListState>).currentState; - List list = []; - var selectedIndex = state?.currentSelectedIndex; - state?.selected.forEach((key, value) { - if (value == true) { - list.add(key); - if (selectedIndex == key) { - state.currentSelectedIndex = -1; - } - } - }); - state?.selected.clear(); - return list; + state?.currentSelectedIndex = -1; + return index >= 0 ? [index] : []; } int currentSelectedIndex() { @@ -237,7 +225,6 @@ class RequestRuleList extends StatefulWidget { } class _RequestRuleListState extends State { - final Map selected = {}; int currentSelectedIndex = -1; changeState() { @@ -248,8 +235,7 @@ class _RequestRuleListState extends State { Widget build(BuildContext context) { return Container( padding: const EdgeInsets.only(top: 10), - height: 300, - constraints: const BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500, minHeight: 300), child: SingleChildScrollView( child: DataTable( columnSpacing: 36, @@ -283,10 +269,9 @@ class _RequestRuleListState extends State { style: const TextStyle(fontSize: 12)), )) ], - selected: selected[index] == true, + selected: currentSelectedIndex == index, onSelectChanged: (value) { setState(() { - selected[index] = value!; currentSelectedIndex = index; }); })), diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index c202df8..4ab8ed6 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -86,17 +86,17 @@ class MobileHomeState extends State implements EventListener { ]), drawer: DrawerWidget(proxyServer: proxyServer), floatingActionButton: FloatingActionButton( - onPressed: () {}, - child: SocketLaunch( - proxyServer: proxyServer, - startup: false, - size: 38, - onStart: () => Vpn.startVpn("127.0.0.1", proxyServer.port, proxyServer.configuration.appWhitelist), - onStop: () => Vpn.stopVpn())), + onPressed: null, + child: Center( + child: SocketLaunch( + proxyServer: proxyServer, + size: 36, + startup: false, + onStart: () => Vpn.startVpn("127.0.0.1", proxyServer.port, proxyServer.configuration.appWhitelist), + onStop: () => Vpn.stopVpn()))), body: ValueListenableBuilder( valueListenable: desktop, builder: (context, value, _) { - return Column(children: [ value.connect ? remoteConnect(value) : const SizedBox(), Expanded(child: RequestListWidget(key: requestStateKey, proxyServer: proxyServer)) diff --git a/lib/ui/mobile/setting/request_rewrite.dart b/lib/ui/mobile/setting/request_rewrite.dart index 34f067d..e168352 100644 --- a/lib/ui/mobile/setting/request_rewrite.dart +++ b/lib/ui/mobile/setting/request_rewrite.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; @@ -87,117 +88,118 @@ class _MobileRequestRewriteState extends State { }) ]), const SizedBox(height: 10), - requestRuleList, + Expanded(child: requestRuleList), ], ))); } void add([int currentIndex = -1]) { - showDialog( - context: context, - builder: (BuildContext context) { - return RuleAddDialog( - requestRewrites: widget.configuration.requestRewrites, - currentIndex: currentIndex, - onChange: () { - changed = true; - requestRuleList.changeState(); - }); + var rewriteRule = + RewriteRule(rule: currentIndex == -1 ? null : widget.configuration.requestRewrites.rules[currentIndex]); + + Navigator.push(context, MaterialPageRoute(builder: (_) => rewriteRule)).then((rule) { + if (rule != null) { + changed = true; + setState(() { + if (currentIndex == -1) { + widget.configuration.requestRewrites.addRule(rule); + } else { + widget.configuration.requestRewrites.rules[currentIndex] = rule; + } + requestRuleList.changeState(); }); + } + }); } } ///请求重写规则添加对话框 -class RuleAddDialog extends StatelessWidget { - final RequestRewrites requestRewrites; +class RewriteRule extends StatelessWidget { final int currentIndex; - final Function onChange; + final RequestRewriteRule? rule; - const RuleAddDialog({super.key, required this.currentIndex, required this.onChange, required this.requestRewrites}); + const RewriteRule({super.key, required this.rule, this.currentIndex = -1}); @override Widget build(BuildContext context) { GlobalKey formKey = GlobalKey(); - RequestRewriteRule? rule; - if (currentIndex >= 0) { - rule = requestRewrites.rules[currentIndex]; - } - ValueNotifier enableNotifier = ValueNotifier(rule == null || rule.enabled); + ValueNotifier enableNotifier = ValueNotifier(rule == null || rule!.enabled); String? domain = rule?.domain; String? path = rule?.path; String? requestBody = rule?.requestBody; String? responseBody = rule?.responseBody; - return AlertDialog( - title: const Text("添加请求重写规则", style: TextStyle(fontSize: 16)), - scrollable: true, - content: Form( - key: formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ValueListenableBuilder( - valueListenable: enableNotifier, - builder: (_, bool enable, __) { - return SwitchListTile( - contentPadding: const EdgeInsets.only(left: 0), - title: const Text('是否启用', textAlign: TextAlign.start), - value: enable, - onChanged: (value) => enableNotifier.value = value); - }), - TextFormField( - decoration: const InputDecoration(labelText: '域名(可选)', hintText: 'baidu.com 不需要填写HTTP'), - initialValue: domain, - onSaved: (val) => domain = val), - TextFormField( - decoration: const InputDecoration(labelText: 'Path', hintText: '/api/v1/*'), - validator: (val) { - if (val == null || val.isEmpty) { - return 'Path不能为空'; - } - return null; - }, - initialValue: path, - onSaved: (val) => path = val), - TextFormField( - initialValue: requestBody, - decoration: const InputDecoration(labelText: '请求体替换为:'), - onSaved: (val) => requestBody = val), - TextFormField( - initialValue: responseBody, - minLines: 3, - maxLines: 10, - decoration: const InputDecoration(labelText: '响应体替换为:', hintText: '{"code":"200","data":{}}'), - onSaved: (val) => responseBody = val) - ])), + return Scaffold( + appBar: AppBar( + title: const Text("请求重写规则", style: TextStyle(fontSize: 16)), actions: [ - FilledButton( + TextButton( child: const Text("保存"), onPressed: () { if ((formKey.currentState as FormState).validate()) { (formKey.currentState as FormState).save(); + enableNotifier.dispose(); + var requestRewriteRule = RequestRewriteRule( + enableNotifier.value, path!, domain?.trim().isEmpty == true ? null : domain?.trim(), + requestBody: requestBody, responseBody: responseBody); if (currentIndex >= 0) { - requestRewrites.rules[currentIndex] = RequestRewriteRule(enableNotifier.value, path!, domain, - requestBody: requestBody, responseBody: responseBody); + RequestRewrites.instance.rules[currentIndex] = requestRewriteRule; } else { - requestRewrites.addRule(RequestRewriteRule(enableNotifier.value, path!, domain, - requestBody: requestBody, responseBody: responseBody)); + RequestRewrites.instance.addRule(requestRewriteRule); } - enableNotifier.dispose(); - onChange.call(); - Navigator.of(context).pop(); + FlutterToastr.show("添加请求重写规则成功", context); + Navigator.of(context).pop(requestRewriteRule); } - }), - ElevatedButton( - child: const Text("关闭"), - onPressed: () { - Navigator.of(context).pop(); }) - ]); + ], + ), + body: Padding( + padding: const EdgeInsets.all(10), + child: Form( + key: formKey, + child: ListView(children: [ + ValueListenableBuilder( + valueListenable: enableNotifier, + builder: (_, bool enable, __) { + return SwitchListTile( + contentPadding: const EdgeInsets.only(left: 0), + title: const Text('是否启用', textAlign: TextAlign.start), + value: enable, + onChanged: (value) => enableNotifier.value = value); + }), + TextFormField( + decoration: const InputDecoration(labelText: '域名(可选)', hintText: 'baidu.com 不需要填写HTTP'), + initialValue: domain, + onSaved: (val) => domain = val), + TextFormField( + decoration: const InputDecoration(labelText: 'Path', hintText: '/api/v1/*'), + validator: (val) { + if (val == null || val.isEmpty) { + return 'Path不能为空'; + } + return null; + }, + minLines: 1, + maxLines: 3, + initialValue: path, + onSaved: (val) => path = val), + TextFormField( + initialValue: requestBody, + decoration: const InputDecoration(labelText: '请求体替换为:'), + minLines: 1, + maxLines: 3, + onSaved: (val) => requestBody = val), + TextFormField( + initialValue: responseBody, + minLines: 3, + maxLines: 10, + decoration: const InputDecoration(labelText: '响应体替换为:', hintText: '{"code":"200","data":{}}'), + onSaved: (val) => responseBody = val) + ]))), + ); } } @@ -210,24 +212,15 @@ class RequestRuleList extends StatefulWidget { State createState() => _RequestRuleListState(); List removeSelected() { + var index = currentSelectedIndex(); var state = (key as GlobalKey<_RequestRuleListState>).currentState; - List list = []; - var selectedIndex = state?.currentSelectedIndex; - state?.selected.forEach((key, value) { - if (value == true) { - list.add(key); - if (selectedIndex == key) { - state.currentSelectedIndex = -1; - } - } - }); - state?.selected.clear(); - return list; + state?.selected = -1; + return index == -1 ? [] : [index]; } int currentSelectedIndex() { var state = (key as GlobalKey<_RequestRuleListState>).currentState; - return state?.currentSelectedIndex ?? -1; + return state?.selected ?? -1; } changeState() { @@ -237,8 +230,7 @@ class RequestRuleList extends StatefulWidget { } class _RequestRuleListState extends State { - final Map selected = {}; - int currentSelectedIndex = -1; + int selected = -1; changeState() { setState(() {}); @@ -262,32 +254,43 @@ class _RequestRuleListState extends State { rows: List.generate( widget.requestRewrites.rules.length, (index) => DataRow( - cells: [ - DataCell(Text(widget.requestRewrites.rules[index].enabled ? "是" : "否")), - DataCell(ConstrainedBox( - constraints: const BoxConstraints(minWidth: 60), - child: Text( - '${widget.requestRewrites.rules[index].domain ?? ''}${widget.requestRewrites.rules[index].path}'))), - DataCell(Container( - constraints: const BoxConstraints(maxWidth: 180), + cells: [ + cell(Text(widget.requestRewrites.rules[index].enabled ? "是" : "否"), index), + cell( + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 60, maxWidth: 150), + child: Text( + '${widget.requestRewrites.rules[index].domain ?? ''}${widget.requestRewrites.rules[index].path}')), + index), + cell( + Container( + constraints: const BoxConstraints(maxWidth: 150), + child: SelectableText.rich( + maxLines: 3, + TextSpan(text: widget.requestRewrites.rules[index].requestBody), + style: const TextStyle(fontSize: 12))), + index), + cell( + Container( + constraints: const BoxConstraints(maxWidth: 200), + padding: const EdgeInsetsDirectional.all(10), child: SelectableText.rich( - TextSpan(text: widget.requestRewrites.rules[index].requestBody), - style: const TextStyle(fontSize: 12)))), - DataCell(Container( - constraints: const BoxConstraints(maxWidth: 300), - padding: const EdgeInsetsDirectional.all(10), - child: SelectableText.rich( - TextSpan(text: widget.requestRewrites.rules[index].responseBody), - style: const TextStyle(fontSize: 12)), - )) - ], - selected: selected[index] == true, - onSelectChanged: (value) { - setState(() { - selected[index] = value!; - currentSelectedIndex = index; - }); - })), + maxLines: 5, + TextSpan(text: widget.requestRewrites.rules[index].responseBody), + style: const TextStyle(fontSize: 12)), + ), + index) + ], + selected: selected == index, + )), ))); } + + DataCell cell(Widget child, int index) { + return DataCell(child, onTap: () { + setState(() { + selected = index; + }); + }); + } } diff --git a/lib/utils/curl.dart b/lib/utils/curl.dart index 89f658c..0a235bd 100644 --- a/lib/utils/curl.dart +++ b/lib/utils/curl.dart @@ -13,6 +13,6 @@ String curlRequest(HttpRequest request) { if (request.bodyAsString.isNotEmpty) { body = " --data '${request.bodyAsString}' \\\n"; } - return "curl '${request.requestUrl}' \\\n" + return "curl -X ${request.method.name} '${request.requestUrl}' \\\n" "${headers.join('\\\n')} $body \\\n --compressed"; }