From 3ddc10872ab5165711f8220440c2988acedfbf2f Mon Sep 17 00:00:00 2001 From: wanghongen Date: Wed, 21 Jun 2023 17:53:29 +0800 Subject: [PATCH] =?UTF-8?q?=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 --- lib/main.dart | 8 +- lib/network/bin/server.dart | 74 ++++-- lib/network/channel.dart | 24 +- lib/network/handler.dart | 25 +- lib/network/http/codec.dart | 2 +- lib/network/http/http.dart | 6 + lib/network/util/host_filter.dart | 2 + lib/network/util/logger.dart | 2 +- lib/network/util/request_rewrite.dart | 82 ++++++ lib/ui/left/domain.dart | 3 +- lib/ui/panel.dart | 12 +- lib/ui/toolbar/setting/filter.dart | 77 +++++- lib/ui/toolbar/setting/request_rewrite.dart | 273 +++++++++++++++++++- lib/ui/toolbar/setting/setting.dart | 84 +++--- lib/ui/toolbar/ssl/ssl.dart | 2 - 15 files changed, 572 insertions(+), 104 deletions(-) create mode 100644 lib/network/util/request_rewrite.dart diff --git a/lib/main.dart b/lib/main.dart index 6481899..087aa32 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,8 +16,7 @@ void main() async { //设置窗口大小 await windowManager.ensureInitialized(); WindowOptions windowOptions = WindowOptions( - minimumSize: const Size(930, 500), - size: const Size(1080, 620), + minimumSize: const Size(960, 680), center: true, titleBarStyle: Platform.isMacOS ? TitleBarStyle.hidden : TitleBarStyle.normal); windowManager.waitUntilReadyToShow(windowOptions, () async { @@ -28,6 +27,7 @@ void main() async { runApp(const FluentApp()); } +/// 主题 final ValueNotifier themeNotifier = ValueNotifier(ThemeMode.system); class FluentApp extends StatelessWidget { @@ -85,8 +85,8 @@ class _NetworkHomePagePageState extends State implements EventL child: Toolbar(proxyServer, domainWidget), ), body: Row(children: [ - SizedBox(width: 420, child: domainWidget), - const Spacer(), + SizedBox(width: 400, child: domainWidget), + const VerticalDivider(), Expanded(flex: 100, child: domainWidget.panel), ])); } diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index 68fab9a..41fd508 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -8,30 +8,40 @@ import '../channel.dart'; import '../handler.dart'; import '../http/codec.dart'; import '../util/logger.dart'; +import '../util/request_rewrite.dart'; import '../util/system_proxy.dart'; Future main() async { ProxyServer().start(); } +/// 代理服务器 class ProxyServer { bool init = false; int port = 8888; bool _enableSsl = false; - EventListener? listener; Server? server; + EventListener? listener; + RequestRewrites requestRewrites = RequestRewrites(); ProxyServer({this.listener}); bool get enableSsl => _enableSsl; - File configFile() { + File homeDir() { var userHome = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']; var separator = Platform.pathSeparator; - return File("${userHome!}$separator.proxyPin${separator}config.cnf"); + return File("${userHome!}$separator.proxypin"); } + /// 配置文件 + File configFile() { + var separator = Platform.pathSeparator; + return File("${homeDir().path}${separator}config.cnf"); + } + + /// 是否启用ssl set enableSsl(bool enableSsl) { _enableSsl = enableSsl; server?.enableSsl = enableSsl; @@ -48,15 +58,18 @@ class ProxyServer { Future start() async { Server server = Server(); if (!init) { + // 读取配置文件 init = true; await _loadConfig(); } + server.enableSsl = _enableSsl; server.initChannel((channel) { - channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(), HttpChannelHandler(listener: listener)); + channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(), + HttpChannelHandler(listener: listener, requestRewrites: requestRewrites)); }); return server.bind(port).then((serverSocket) { - log.i("listen on $port"); + logger.i("listen on $port"); SystemProxy.setSystemProxy(port, enableSsl); this.server = server; return server; @@ -65,7 +78,7 @@ class ProxyServer { /// 停止代理服务 Future stop() async { - log.i("stop on $port"); + logger.i("stop on $port"); if (Platform.isMacOS) { await SystemProxy.setProxyEnableMacOS(false, enableSsl); } else if (Platform.isWindows) { @@ -75,10 +88,23 @@ class ProxyServer { return server; } - void restart() { + /// 重启代理服务 + restart() { stop().then((value) => start()); } + /// 刷新配置文件 + flushConfig() async { + var file = configFile(); + await file.create(recursive: true); + HostFilter.whitelist.toJson(); + HostFilter.blacklist.toJson(); + var json = jsonEncode(toJson()); + logger.i('刷新配置文件 $runtimeType ${toJson()}'); + file.writeAsString(json); + } + + /// 加载配置文件 Future _loadConfig() async { var file = configFile(); var exits = await file.exists(); @@ -86,20 +112,38 @@ class ProxyServer { return; } Map config = jsonDecode(await file.readAsString()); - log.i('加载配置文件 [$file] $config'); + logger.i('加载配置文件 [$file] $config'); enableSsl = config['enableSsl'] == true; port = config['port'] ?? port; HostFilter.whitelist.load(config['whitelist']); HostFilter.blacklist.load(config['blacklist']); + + await _loadRequestRewriteConfig(); } - void flushConfig() async { - var file = configFile(); - await file.create(recursive: true); - HostFilter.whitelist.toJson(); - HostFilter.blacklist.toJson(); - var json = jsonEncode(toJson()); - log.i('刷新配置文件 $runtimeType ${toJson()}'); + /// 加载请求重写配置文件 + Future _loadRequestRewriteConfig() async { + var file = File('${homeDir().path}${Platform.pathSeparator}request_rewrite.json'); + var exits = await file.exists(); + if (!exits) { + return; + } + + Map config = jsonDecode(await file.readAsString()); + + logger.i('加载请求重写配置文件 [$file] $config'); + requestRewrites.load(config); + } + + /// 保存请求重写配置文件 + flushRequestRewriteConfig() async { + var file = File('${homeDir().path}${Platform.pathSeparator}request_rewrite.json'); + bool exists = await file.exists(); + if (!exists) { + await file.create(recursive: true); + } + var json = jsonEncode(requestRewrites.toJson()); + logger.i('刷新请求重写配置文件 ${file.path}'); file.writeAsString(json); } diff --git a/lib/network/channel.dart b/lib/network/channel.dart index d42c798..0adb31e 100644 --- a/lib/network/channel.dart +++ b/lib/network/channel.dart @@ -7,6 +7,7 @@ import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/util/attribute_keys.dart'; import 'package:network_proxy/network/util/crts.dart'; import 'package:network_proxy/network/util/host_filter.dart'; +import 'package:network_proxy/network/util/logger.dart'; import 'handler.dart'; @@ -119,12 +120,24 @@ class ChannelPipeline extends ChannelHandler { void channelRead(Channel channel, Uint8List msg) { try { var data = _decoder.decode(msg); - if (data != null) { - if (data is HttpResponse) { - data.remoteAddress = '${channel.remoteAddress.host}:${channel.remotePort}'; - } - _handler.channelRead(channel, data!); + if (data == null) { + return; } + if (data is HttpRequest) { + data.hostAndPort ??= getHostAndPort(data); + data.remoteDomain = data.hostAndPort.toString(); + data.requestUrl = data.uri.startsWith("/") ? '${data.remoteDomain}${data.uri}' : data.uri; + try { + data.path = Uri.parse(data.requestUrl).path; + } catch (e) { + logger.e("data.requestUrl ${data.requestUrl}", e, StackTrace.current); + } + } + + if (data is HttpResponse) { + data.remoteAddress = '${channel.remoteAddress.host}:${channel.remotePort}'; + } + _handler.channelRead(channel, data!); } catch (error, trace) { exceptionCaught(channel, error, trace: trace); } @@ -200,6 +213,7 @@ class HostAndPort { /// 解码 abstract interface class Decoder { + /// 解码 如果返回null说明数据不完整 T? decode(Uint8List data); } diff --git a/lib/network/handler.dart b/lib/network/handler.dart index fceeb74..41f5e74 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; import 'package:network_proxy/network/util/attribute_keys.dart'; -import 'package:network_proxy/network/util/logger.dart'; +import 'package:network_proxy/network/util/request_rewrite.dart'; import 'channel.dart'; import 'http/codec.dart'; @@ -26,11 +26,12 @@ abstract class EventListener { void onResponse(Channel channel, HttpResponse response); } -/// +/// http请求处理器 class HttpChannelHandler extends ChannelHandler { EventListener? listener; + RequestRewrites? requestRewrites; - HttpChannelHandler({this.listener}); + HttpChannelHandler({this.listener, this.requestRewrites}); @override void channelRead(Channel channel, HttpRequest msg) async { @@ -64,10 +65,9 @@ class HttpChannelHandler extends ChannelHandler { //实现抓包代理转发 if (httpRequest.method != HttpMethod.connect) { - if (httpRequest.uri.startsWith("/")) { - remoteChannel.putAttribute(AttributeKeys.uri, '${channel.getAttribute(AttributeKeys.host)}${httpRequest.uri}'); - } else { - remoteChannel.putAttribute(AttributeKeys.uri, httpRequest.uri); + var replaceBody = requestRewrites?.findRequestReplaceWith(httpRequest.path); + if (replaceBody?.isNotEmpty == true) { + httpRequest.body = utf8.encode(replaceBody!); } // log.i("[${channel.id}] ${remoteChannel.getAttribute(AttributeKeys.uri)}"); @@ -99,7 +99,7 @@ class HttpChannelHandler extends ChannelHandler { var hostAndPort = getHostAndPort(httpRequest); clientChannel.putAttribute(AttributeKeys.host, hostAndPort); - var proxyHandler = HttpResponseProxyHandler(clientChannel, listener: listener); + var proxyHandler = HttpResponseProxyHandler(clientChannel, listener: listener, requestRewrites: requestRewrites); var proxyChannel = await HttpClients.connect(hostAndPort, proxyHandler); clientChannel.putAttribute(clientId, proxyChannel); @@ -117,13 +117,20 @@ class HttpResponseProxyHandler extends ChannelHandler { final Channel clientChannel; EventListener? listener; + RequestRewrites? requestRewrites; - HttpResponseProxyHandler(this.clientChannel, {this.listener}); + HttpResponseProxyHandler(this.clientChannel, {this.listener, this.requestRewrites}); @override void channelRead(Channel channel, HttpResponse msg) { msg.request = clientChannel.getAttribute(AttributeKeys.request); // log.i("[${clientChannel.id}] Response ${msg.bodyAsString}"); + + var replaceBody = requestRewrites?.findResponseReplaceWith(msg.request?.path); + if (replaceBody?.isNotEmpty == true) { + msg.body = utf8.encode(replaceBody!); + } + listener?.onResponse(clientChannel, msg); //发送给客户端 clientChannel.write(msg); diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index 63b6ce5..7023cdf 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.dart @@ -53,7 +53,7 @@ abstract class HttpCodec implements Codec { message = createMessage(initialLine); _state = State.readHeader; } catch (e, cause) { - log.e("解析请求行失败 [${String.fromCharCodes(data)}]", e, cause); + logger.e("解析请求行失败 [${String.fromCharCodes(data)}]", e, cause); rethrow; } } diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 6dc4749..0259537 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:network_proxy/network/channel.dart'; + import 'http_headers.dart'; ///定义HTTP消息的接口,为HttpRequest和HttpResponse提供公共属性。 @@ -48,7 +50,11 @@ abstract class HttpMessage { class HttpRequest extends HttpMessage { final String uri; late HttpMethod method; + late String requestUrl; + String? path; + HostAndPort? hostAndPort; final DateTime requestTime = DateTime.now(); + String? remoteDomain; HttpRequest(this.method, this.uri, String protocolVersion) : super(protocolVersion); diff --git a/lib/network/util/host_filter.dart b/lib/network/util/host_filter.dart index 23c7cf3..b04f0e2 100644 --- a/lib/network/util/host_filter.dart +++ b/lib/network/util/host_filter.dart @@ -19,6 +19,7 @@ class HostFilter { if (whitelist.enabled) { return whitelist.list.every((element) => !element.hasMatch(host)); } + if (blacklist.enabled) { return blacklist.list.any((element) => element.hasMatch(host)); } @@ -36,6 +37,7 @@ abstract class HostList { return; } List? list = map['list']; + this.list.clear(); list?.forEach((element) { this.list.add(RegExp(element)); }); diff --git a/lib/network/util/logger.dart b/lib/network/util/logger.dart index 39c49c7..6b9b5df 100644 --- a/lib/network/util/logger.dart +++ b/lib/network/util/logger.dart @@ -1,6 +1,6 @@ import 'package:logger/logger.dart'; -final log = Logger( +final logger = Logger( printer: PrettyPrinter( methodCount: 0, errorMethodCount: 8, diff --git a/lib/network/util/request_rewrite.dart b/lib/network/util/request_rewrite.dart new file mode 100644 index 0000000..d8a4300 --- /dev/null +++ b/lib/network/util/request_rewrite.dart @@ -0,0 +1,82 @@ +class RequestRewrites { + bool enabled = false; + final List rules = []; + + load(Map? map) { + if (map == null) { + return; + } + enabled = map['enabled'] == true; + List? list = map['rules']; + rules.clear(); + list?.forEach((element) { + rules.add(RequestRewriteRule.formJson(element)); + }); + } + + String? findRequestReplaceWith(String? url) { + if (!enabled || url == null) { + return null; + } + for (var rule in rules) { + if (rule.enabled && rule.urlReg.hasMatch(url)) { + return rule.requestBody; + } + } + return null; + } + + String? findResponseReplaceWith(String? url) { + if (!enabled || url == null) { + return null; + } + for (var rule in rules) { + if (rule.enabled && rule.urlReg.hasMatch(url)) { + return rule.responseBody; + } + } + return null; + } + + addRule(RequestRewriteRule rule) { + rules.add(rule); + } + + removeIndex(List indexes) { + for (var i in indexes) { + rules.removeAt(i); + } + } + + toJson() { + return { + 'enabled': enabled, + 'rules': rules.map((e) => e.toJson()).toList(), + }; + } +} + +class RequestRewriteRule { + bool enabled = false; + final String url; + final RegExp urlReg; + String? requestBody; + String? responseBody; + + RequestRewriteRule(this.enabled, this.url, {this.requestBody, this.responseBody}) + : urlReg = RegExp(url.replaceAll("*", ".*")); + + factory RequestRewriteRule.formJson(Map map) { + return RequestRewriteRule(map['enabled'] == true, map['url'], + requestBody: map['requestBody'], responseBody: map['responseBody']); + } + + toJson() { + return { + 'enabled': enabled, + 'url': url, + 'requestBody': requestBody, + 'responseBody': responseBody, + }; + } +} diff --git a/lib/ui/left/domain.dart b/lib/ui/left/domain.dart index 58e8800..b63ca5a 100644 --- a/lib/ui/left/domain.dart +++ b/lib/ui/left/domain.dart @@ -26,9 +26,8 @@ class DomainWidget extends StatefulWidget { void clean() { var state = key as GlobalKey<_DomainWidgetState>; + panel.change(null, null); state.currentState?.clean(); - panel.request.set(null); - panel.response.set(null); } @override diff --git a/lib/ui/panel.dart b/lib/ui/panel.dart index d533de2..4771aca 100644 --- a/lib/ui/panel.dart +++ b/lib/ui/panel.dart @@ -18,11 +18,9 @@ class NetworkTabController extends StatefulWidget { NetworkTabController() : super(key: GlobalKey<_NetworkTabState>()); - void change(HttpRequest request, HttpResponse? response) { + void change(HttpRequest? request, HttpResponse? response) { this.request.set(request); - if (response != null) { - this.response.set(response); - } + this.response.set(response); var state = key as GlobalKey<_NetworkTabState>; state.currentState?.changeState(); } @@ -40,7 +38,6 @@ class _NetworkTabState extends State { @override Widget build(BuildContext context) { - return DefaultTabController( length: widget.tabs.length, child: Scaffold( @@ -63,7 +60,7 @@ class _NetworkTabState extends State { } var response = widget.response.get(); var content = [ - rowWidget("Request URL", request.uri), + rowWidget("Request URL", request.requestUrl), const SizedBox(height: 20), rowWidget("Request Method", request.method.name), const SizedBox(height: 20), @@ -161,7 +158,8 @@ class _NetworkTabState extends State { } Iterable? _cookieWidget(String? cookie) { - return cookie?.split(";") + return cookie + ?.split(";") .map((e) => Strings.splitFirst(e, "=")) .where((element) => element != null) .map((e) => rowWidget(e!.key, e.value)); diff --git a/lib/ui/toolbar/setting/filter.dart b/lib/ui/toolbar/setting/filter.dart index 374a630..064de56 100644 --- a/lib/ui/toolbar/setting/filter.dart +++ b/lib/ui/toolbar/setting/filter.dart @@ -3,6 +3,69 @@ import 'package:network_proxy/network/bin/server.dart'; import '../../../network/util/host_filter.dart'; +class FilterDialog extends StatefulWidget { + final ProxyServer proxyServer; + + const FilterDialog({super.key, required this.proxyServer}); + + @override + State createState() => _FilterDialogState(); +} + +class _FilterDialogState extends State { + final ValueNotifier hostEnableNotifier = ValueNotifier(false); + + @override + void dispose() { + hostEnableNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + scrollable: true, + title: Row(children: [ + const Text("域名过滤", style: TextStyle(fontSize: 18)), + Expanded( + child: Align( + alignment: Alignment.topRight, + child: ElevatedButton.icon( + icon: const Icon(Icons.close, size: 15), + label: const Text("关闭"), + onPressed: () { + Navigator.of(context).pop(); + }))) + ]), + content: SizedBox( + width: 680, + height: 450, + child: Flex( + direction: Axis.horizontal, + children: [ + Expanded( + flex: 1, + child: DomainFilter( + title: "白名单", + subtitle: "只代理白名单中的域名, 白名单启用黑名单将会失效", + hostList: HostFilter.whitelist, + proxyServer: widget.proxyServer, + hostEnableNotifier: hostEnableNotifier)), + const SizedBox(width: 10), + Expanded( + flex: 1, + child: DomainFilter( + title: "黑名单", + subtitle: "黑名单中的域名不会代理", + hostList: HostFilter.blacklist, + proxyServer: widget.proxyServer, + hostEnableNotifier: hostEnableNotifier)), + ], + ), + )); + } +} + class DomainFilter extends StatefulWidget { final String title; final String subtitle; @@ -15,7 +78,8 @@ class DomainFilter extends StatefulWidget { required this.title, required this.subtitle, required this.hostList, - required this.hostEnableNotifier, required this.proxyServer}); + required this.hostEnableNotifier, + required this.proxyServer}); @override State createState() { @@ -53,13 +117,16 @@ class _DomainFilterState extends State { }); }), Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - FilledButton.tonal( + FilledButton.icon( + icon: const Icon(Icons.add), onPressed: () { add(); }, - child: const Text("增加")), - TextButton( - child: const Text("删除"), + label: const Text("增加")), + const SizedBox(width: 10), + TextButton.icon( + icon: const Icon(Icons.remove), + label: const Text("删除"), onPressed: () { if (domainList.selected().isEmpty) { return; diff --git a/lib/ui/toolbar/setting/request_rewrite.dart b/lib/ui/toolbar/setting/request_rewrite.dart index 5cc9ac6..b876e67 100644 --- a/lib/ui/toolbar/setting/request_rewrite.dart +++ b/lib/ui/toolbar/setting/request_rewrite.dart @@ -1,14 +1,281 @@ - import 'package:flutter/material.dart'; +import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/util/request_rewrite.dart'; class RequestRewrite extends StatefulWidget { + final ProxyServer proxyServer; + + const RequestRewrite({super.key, required this.proxyServer}); + @override - _RequestRewriteState createState() => _RequestRewriteState(); + State createState() => _RequestRewriteState(); } class _RequestRewriteState extends State { + late RequestRuleList requestRuleList; + late ValueNotifier enableNotifier; + bool changed = false; + + @override + void initState() { + super.initState(); + requestRuleList = RequestRuleList(widget.proxyServer.requestRewrites); + enableNotifier = ValueNotifier(widget.proxyServer.requestRewrites.enabled == true); + } + + @override + void dispose() { + if (changed || enableNotifier.value != widget.proxyServer.requestRewrites.enabled) { + widget.proxyServer.requestRewrites.enabled = enableNotifier.value; + widget.proxyServer.flushRequestRewriteConfig(); + } + + enableNotifier.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Container(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 280, + child: ValueListenableBuilder( + valueListenable: enableNotifier, + builder: (_, bool v, __) { + return SwitchListTile( + contentPadding: const EdgeInsets.only(left: 2), + title: const Text('是否启用请求重写'), + dense: true, + value: enableNotifier.value, + onChanged: (value) { + enableNotifier.value = value; + }); + })), + const SizedBox(height: 10), + Row(children: [ + FilledButton.icon( + icon: const Icon(Icons.add), + onPressed: () { + add(); + }, + label: const Text("增加")), + const SizedBox(width: 10), + OutlinedButton.icon( + onPressed: () { + var selectedIndex = requestRuleList.currentSelectedIndex(); + add(selectedIndex); + }, + icon: const Icon(Icons.edit), + label: const Text("编辑")), + TextButton.icon( + icon: const Icon(Icons.remove), + label: const Text("删除"), + onPressed: () { + var removeSelected = requestRuleList.removeSelected(); + if (removeSelected.isEmpty) { + return; + } + + changed = true; + setState(() { + widget.proxyServer.requestRewrites.removeIndex(removeSelected); + requestRuleList.changeState(); + }); + }) + ]), + const SizedBox(height: 10), + requestRuleList, + ], + ); + } + + void add([int currentIndex = -1]) { + showDialog( + context: context, + builder: (BuildContext context) { + return RuleAddDialog( + requestRewrites: widget.proxyServer.requestRewrites, + currentIndex: currentIndex, + onChange: () { + changed = true; + requestRuleList.changeState(); + }); + }); + } +} + +///请求重写规则添加对话框 +class RuleAddDialog extends StatelessWidget { + final RequestRewrites requestRewrites; + final int currentIndex; + final Function onChange; + + const RuleAddDialog({super.key, required this.currentIndex, required this.onChange, required this.requestRewrites}); + + @override + Widget build(BuildContext context) { + GlobalKey formKey = GlobalKey(); + RequestRewriteRule? rule; + if (currentIndex >= 0) { + rule = requestRewrites.rules[currentIndex]; + } + + ValueNotifier enableNotifier = ValueNotifier(rule == null || rule.enabled); + String? url = rule?.url; + 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: 'URL', hintText: '/api/v1/*'), + validator: (val) { + if (val == null || val.isEmpty) { + return 'URL不能为空'; + } + return null; + }, + initialValue: url, + onSaved: (val) => url = val), + TextFormField( + initialValue: requestBody, + decoration: const InputDecoration(labelText: '请求体替换为:'), + onSaved: (val) => requestBody = val), + TextFormField( + initialValue: responseBody, + minLines: 3, + maxLines: 15, + decoration: const InputDecoration(labelText: '响应体替换为:', hintText: '{"code":"200","data":{}}'), + onSaved: (val) => responseBody = val) + ])), + actions: [ + FilledButton( + child: const Text("保存"), + onPressed: () { + if ((formKey.currentState as FormState).validate()) { + (formKey.currentState as FormState).save(); + + if (currentIndex >= 0) { + requestRewrites.rules[currentIndex] = RequestRewriteRule(enableNotifier.value, url!, + requestBody: requestBody, responseBody: responseBody); + } else { + requestRewrites.addRule(RequestRewriteRule(enableNotifier.value, url!, + requestBody: requestBody, responseBody: responseBody)); + } + + enableNotifier.dispose(); + onChange.call(); + Navigator.of(context).pop(); + } + }) + ]); + } +} + +class RequestRuleList extends StatefulWidget { + final RequestRewrites requestRewrites; + + RequestRuleList(this.requestRewrites) : super(key: GlobalKey<_RequestRuleListState>()); + + @override + State createState() => _RequestRuleListState(); + + List removeSelected() { + var state = (key as GlobalKey<_RequestRuleListState>).currentState; + List list = []; + state?.selected.forEach((key, value) { + if (value == true) { + list.add(key); + } + }); + state?.selected.clear(); + return list; + } + + int currentSelectedIndex() { + var state = (key as GlobalKey<_RequestRuleListState>).currentState; + return state?.currentSelectedIndex ?? -1; + } + + changeState() { + var state = (key as GlobalKey<_RequestRuleListState>).currentState; + state?.changeState(); + } +} + +class _RequestRuleListState extends State { + final Map selected = {}; + int currentSelectedIndex = -1; + + changeState() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.only(top: 10), + height: 300, + constraints: const BoxConstraints(minWidth: 500), + child: SingleChildScrollView( + child: DataTable( + columnSpacing: 36, + dataRowMaxHeight: 100, + border: TableBorder.symmetric(outside: BorderSide(width: 1, color: Theme.of(context).highlightColor)), + columns: const [ + DataColumn(label: Text('启用')), + DataColumn(label: Text('URL')), + DataColumn(label: Text('重写请求体')), + DataColumn(label: Text('重写响应体')), + ], + 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].url))), + DataCell(Container( + constraints: const BoxConstraints(maxWidth: 120), + 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; + }); + })), + ))); } } diff --git a/lib/ui/toolbar/setting/setting.dart b/lib/ui/toolbar/setting/setting.dart index be4815e..ae8cb1d 100644 --- a/lib/ui/toolbar/setting/setting.dart +++ b/lib/ui/toolbar/setting/setting.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/ui/toolbar/setting/request_rewrite.dart'; import 'package:network_proxy/ui/toolbar/setting/theme.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../../network/util/host_filter.dart'; import 'filter.dart'; class Setting extends StatefulWidget { @@ -26,9 +26,7 @@ class _SettingState extends State { offset: const Offset(10, 30), itemBuilder: (context) { return [ - PopupMenuItem( - child: PortWidget(proxyServer: widget.proxyServer) - ), + PopupMenuItem(child: PortWidget(proxyServer: widget.proxyServer)), const PopupMenuItem( child: ThemeSetting(), ), @@ -39,13 +37,15 @@ class _SettingState extends State { trailing: const Icon(Icons.arrow_right), onTap: () => _filter())), PopupMenuItem( - child: const ListTile( - title: Text("Github"), - dense: true, - trailing: Icon(Icons.arrow_right)), + child: ListTile( + title: const Text("请求重写"), + dense: true, + trailing: const Icon(Icons.arrow_right), + onTap: () => _reqeustRewrite())), + PopupMenuItem( + child: const ListTile(title: Text("Github"), dense: true, trailing: Icon(Icons.arrow_right)), onTap: () { - launchUrl(Uri.parse( - "https://github.com/wanghongenpin/network-proxy-flutter")); + launchUrl(Uri.parse("https://github.com/wanghongenpin/network-proxy-flutter")); }, ) ]; @@ -53,49 +53,34 @@ class _SettingState extends State { ); } + void _reqeustRewrite() { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) { + return AlertDialog( + scrollable: true, + title: Row(children: [ + const Text("请求重写"), + Expanded( + child: Align( + alignment: Alignment.topRight, + child: ElevatedButton.icon( + icon: const Icon(Icons.close, size: 15), + label: const Text("关闭"), + onPressed: () => Navigator.of(context).pop()))) + ]), + content: RequestRewrite(proxyServer: widget.proxyServer), + ); + }); + } + void _filter() { - final ValueNotifier hostEnableNotifier = ValueNotifier(false); showDialog( barrierDismissible: false, context: context, builder: (context) { - return AlertDialog( - scrollable: true, - actions: [ - FilledButton( - child: const Text("关闭"), - onPressed: () { - Navigator.of(context).pop(); - hostEnableNotifier.dispose(); - }), - ], - title: const Text("域名过滤", style: TextStyle(fontSize: 18)), - content: SizedBox( - width: 680, - height: 450, - child: Flex( - direction: Axis.horizontal, - children: [ - Expanded( - flex: 1, - child: DomainFilter( - title: "白名单", - subtitle: "只代理白名单中的域名, 白名单启用黑名单将会失效", - hostList: HostFilter.whitelist, - proxyServer: widget.proxyServer, - hostEnableNotifier: hostEnableNotifier)), - const SizedBox(width: 10), - Expanded( - flex: 1, - child: DomainFilter( - title: "黑名单", - subtitle: "黑名单中的域名不会代理", - hostList: HostFilter.blacklist, - proxyServer: widget.proxyServer, - hostEnableNotifier: hostEnableNotifier)), - ], - ), - )); + return FilterDialog(proxyServer: widget.proxyServer); }, ); } @@ -122,8 +107,7 @@ class _PortState extends State { textController.text = widget.proxyServer.port.toString(); portFocus.addListener(() async { //失去焦点 - if (!portFocus.hasFocus && - textController.text != widget.proxyServer.port.toString()) { + if (!portFocus.hasFocus && textController.text != widget.proxyServer.port.toString()) { widget.proxyServer.port = int.parse(textController.text); widget.proxyServer.restart(); widget.proxyServer.flushConfig(); diff --git a/lib/ui/toolbar/ssl/ssl.dart b/lib/ui/toolbar/ssl/ssl.dart index 2b3b0fb..e562845 100644 --- a/lib/ui/toolbar/ssl/ssl.dart +++ b/lib/ui/toolbar/ssl/ssl.dart @@ -4,8 +4,6 @@ import 'package:flutter/services.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/utils/ip.dart'; - - class SslWidget extends StatefulWidget { final ProxyServer proxyServer;