手机版启动默认不再自动开启抓包

This commit is contained in:
wanghongen
2023-07-30 01:07:23 +08:00
parent b13cd10de1
commit 8a466dc11a
24 changed files with 416 additions and 295 deletions

View File

@@ -37,6 +37,14 @@
<action android:name="andorid.net.VpnService" />
</intent-filter>
</service>
<activity
android:name=".VpnAlertDialog"
android:exported="false">
<intent-filter>
<action android:name="ProxyVpnService" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

View File

@@ -7,7 +7,7 @@ import android.os.Build
import android.os.ParcelFileDescriptor
class ProxyVpnService : VpnService() {
private lateinit var vpnInterface: ParcelFileDescriptor
private var vpnInterface: ParcelFileDescriptor? = null
companion object {
const val ProxyHost = "ProxyHost"
@@ -35,14 +35,19 @@ class ProxyVpnService : VpnService() {
}
private fun disconnect() {
vpnInterface.close()
vpnInterface?.close()
}
private fun connect(proxyHost: String, proxyPort: Int) {
vpnInterface = createVpnInterface(proxyHost, proxyPort)
if (vpnInterface == null) {
val alertDialog = Intent(applicationContext, VpnAlertDialog::class.java)
alertDialog.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(alertDialog)
}
}
private fun createVpnInterface(proxyHost: String, proxyPort: Int): ParcelFileDescriptor {
private fun createVpnInterface(proxyHost: String, proxyPort: Int): ParcelFileDescriptor? {
return Builder()
.addAddress("10.0.0.2", 32)
.addRoute("0.0.0.0", 0)
@@ -53,7 +58,7 @@ class ProxyVpnService : VpnService() {
.setHttpProxy(ProxyInfo.buildDirectProxy(proxyHost, proxyPort))
}
}
.establish() ?: throw IllegalStateException("无法初始化vpnInterface")
.establish()
}

View File

@@ -0,0 +1,23 @@
package com.network.proxy
import android.app.Activity
import android.app.AlertDialog
import android.os.Bundle
import kotlin.system.exitProcess
class VpnAlertDialog : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dialog: AlertDialog = AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("必须添加VPN才能使用")
.setPositiveButton("确认") { _, _ ->
exitProcess(0)
}
.setCancelable(false)
.create()
dialog.show()
}
}

View File

@@ -10,13 +10,6 @@ import NetworkExtension
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// let url = URL(string: "http://www.baidu.com")!
// let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
// guard let data = data else { return }
// print(String(data: data, encoding: .utf8)!)
// }
// task.resume()
let controller: FlutterViewController = window.rootViewController as! FlutterViewController ;
let batteryChannel = FlutterMethodChannel.init(name: "com.proxy/proxyVpn", binaryMessenger: controller as! FlutterBinaryMessenger);
batteryChannel.setMethodCallHandler({
@@ -54,14 +47,11 @@ import NetworkExtension
}
}
var backgroundUpdateTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
func endBackgroundUpdateTask() {
AudioManager.shared.openBackgroundAudioAutoplay = false
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
}
override func applicationWillResignActive(_ application: UIApplication) {
if (!VpnManager.shared.isRunning()) {
return
}
AudioManager.shared.openBackgroundAudioAutoplay = true
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
@@ -69,7 +59,17 @@ import NetworkExtension
}
override func applicationDidBecomeActive(_ application: UIApplication) {
self.endBackgroundUpdateTask()
}
var backgroundUpdateTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
func endBackgroundUpdateTask() {
if (!VpnManager.shared.isRunning()) {
return
}
AudioManager.shared.openBackgroundAudioAutoplay = false
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
}
}

View File

@@ -169,5 +169,9 @@ extension VpnManager{
}
}
func isRunning() -> Bool {
return vpnStatus == VPNStatus.on
}
}

View File

@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:chinese_font_library/chinese_font_library.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/ui/component/split_view.dart';
import 'package:network_proxy/ui/content/body.dart';
@@ -20,11 +21,6 @@ import 'network/handler.dart';
import 'network/http/http.dart';
void main(List<String> args) async {
if (Platforms.isMobile()) {
runApp(const FluentApp(MobileHomePage()));
return;
}
//多窗口
if (args.firstOrNull == 'multi_window') {
final windowId = int.parse(args[1]);
@@ -34,6 +30,13 @@ void main(List<String> args) async {
}
WidgetsFlutterBinding.ensureInitialized();
var configuration = Configuration.instance;
if (Platforms.isMobile()) {
runApp(FluentApp(MobileHomePage(configuration: (await configuration))));
return;
}
await windowManager.ensureInitialized();
//设置窗口大小
WindowOptions windowOptions = WindowOptions(
@@ -46,7 +49,7 @@ void main(List<String> args) async {
await windowManager.focus();
});
runApp(const FluentApp(DesktopHomePage()));
runApp(FluentApp(DesktopHomePage(configuration: (await configuration))));
}
///多窗口
@@ -104,7 +107,9 @@ class FluentApp extends StatelessWidget {
}
class DesktopHomePage extends StatefulWidget {
const DesktopHomePage({super.key});
final Configuration configuration;
const DesktopHomePage({super.key, required this.configuration});
@override
State<DesktopHomePage> createState() => _DesktopHomePagePageState();
@@ -129,13 +134,10 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
@override
void initState() {
super.initState();
proxyServer = ProxyServer(listener: this);
proxyServer = ProxyServer(widget.configuration, listener: this);
panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 18), proxyServer: proxyServer);
proxyServer.initializedListener(() {
if (!proxyServer.guide) {
return;
}
if (widget.configuration.guide) {
//首次引导
showDialog(
context: context,
@@ -145,8 +147,8 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
actions: [
TextButton(
onPressed: () {
proxyServer.guide = false;
proxyServer.flushConfig();
widget.configuration.guide = false;
widget.configuration.flushConfig();
Navigator.pop(context);
},
child: const Text('关闭'))
@@ -155,7 +157,9 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
content: const Text('默认不会开启HTTPS抓包请安装证书后再开启HTTPS抓包。\n'
'点击的HTTPS抓包(加锁图标),选择安装根证书,按照提示操作即可。'));
});
});
return;
}
}
@override

View File

@@ -0,0 +1,142 @@
import 'dart:convert';
import 'dart:io';
import 'package:network_proxy/network/util/host_filter.dart';
import 'package:network_proxy/network/util/logger.dart';
import 'package:network_proxy/network/util/request_rewrite.dart';
import 'package:network_proxy/utils/platform.dart';
import 'package:path_provider/path_provider.dart';
class Configuration {
int port = 9099;
//是否启用https抓包
bool enableSsl = false;
//是否启用桌面抓包
bool enableDesktop = true;
//是否引导
bool guide = false;
//是否显示更新内容公告
bool upgradeNotice = true;
//请求重写
RequestRewrites requestRewrites = RequestRewrites();
Configuration._();
/// 单例
static Configuration? _instance;
static Future<Configuration> get instance async {
if (_instance == null) {
Configuration configuration = Configuration._();
await configuration.initConfig();
_instance = configuration;
}
return _instance!;
}
/// 初始化配置
Future<void> initConfig() async {
// 读取配置文件
await _loadConfig();
}
Future<File> homeDir() async {
String? userHome;
if (Platforms.isDesktop()) {
userHome = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
} else {
userHome = (await getApplicationSupportDirectory()).path;
}
var separator = Platform.pathSeparator;
return File("${userHome!}$separator.proxypin");
}
/// 配置文件
Future<File> configFile() async {
var separator = Platform.pathSeparator;
var home = await homeDir();
return File("${home.path}${separator}config.cnf");
}
/// 刷新配置文件
flushConfig() async {
var file = await configFile();
var exists = await file.exists();
if (!exists) {
file = await file.create(recursive: true);
}
HostFilter.whitelist.toJson();
HostFilter.blacklist.toJson();
var json = jsonEncode(toJson());
logger.i('刷新配置文件 $runtimeType ${toJson()}');
file.writeAsString(json);
}
/// 加载配置文件
Future<void> _loadConfig() async {
var file = await configFile();
var exits = await file.exists();
if (!exits) {
guide = true;
return;
}
Map<String, dynamic> config = jsonDecode(await file.readAsString());
logger.i('加载配置文件 [$file]');
port = config['port'] ?? port;
enableSsl = config['enableSsl'] == true;
enableDesktop = config['enableDesktop'] ?? true;
guide = config['guide'] ?? false;
upgradeNotice = config['upgradeNotice'] ?? true;
HostFilter.whitelist.load(config['whitelist']);
HostFilter.blacklist.load(config['blacklist']);
await _loadRequestRewriteConfig();
}
/// 加载请求重写配置文件
Future<void> _loadRequestRewriteConfig() async {
var home = await homeDir();
var file = File('${home.path}${Platform.pathSeparator}request_rewrite.json');
var exits = await file.exists();
if (!exits) {
return;
}
Map<String, dynamic> config = jsonDecode(await file.readAsString());
logger.i('加载请求重写配置文件 [$file]');
requestRewrites.load(config);
}
/// 保存请求重写配置文件
flushRequestRewriteConfig() async {
var home = await homeDir();
var file = File('${home.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);
}
Map<String, dynamic> toJson() {
return {
'guide': guide,
'upgradeNotice': upgradeNotice,
'port': port,
'enableSsl': enableSsl,
'enableDesktop': enableDesktop,
'whitelist': HostFilter.whitelist.toJson(),
'blacklist': HostFilter.blacklist.toJson(),
};
}
}

View File

@@ -1,82 +1,39 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:network_proxy/network/util/host_filter.dart';
import 'package:network_proxy/utils/platform.dart';
import 'package:path_provider/path_provider.dart';
import 'package:network_proxy/network/bin/configuration.dart';
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<void> main() async {
ProxyServer().start();
var configuration = await Configuration.instance;
ProxyServer(configuration).start();
}
/// 代理服务器
class ProxyServer {
//是否初始化
bool init = false;
int port = 9099;
//是否启用https抓包
bool _enableSsl = false;
//是否启用桌面抓包
bool enableDesktop = true;
//是否引导
bool guide = false;
//是否启动
bool get isRunning => server?.isRunning ?? false;
Server? server;
//请求事件监听
EventListener? listener;
//请求重写
RequestRewrites requestRewrites = RequestRewrites();
final Configuration configuration;
final List<Function> _initializedListeners = [];
ProxyServer({this.listener});
//初始化
Future<void> initializedListener(Function action) async {
_initializedListeners.add(action);
}
Future<File> homeDir() async {
String? userHome;
if (Platforms.isDesktop()) {
userHome =
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
} else {
userHome = (await getApplicationSupportDirectory()).path;
}
var separator = Platform.pathSeparator;
return File("${userHome!}$separator.proxypin");
}
/// 配置文件
Future<File> configFile() async {
var separator = Platform.pathSeparator;
var home = await homeDir();
return File("${home.path}${separator}config.cnf");
}
ProxyServer(this.configuration, {this.listener});
///是否启用https抓包
bool get enableSsl => _enableSsl;
bool get enableSsl => configuration.enableSsl;
int get port => configuration.port;
set enableSsl(bool enableSsl) {
_enableSsl = enableSsl;
configuration.enableSsl = enableSsl;
server?.enableSsl = enableSsl;
if (server == null || server?.isRunning == false) {
return;
@@ -90,27 +47,16 @@ class ProxyServer {
/// 启动代理服务
Future<Server> start() async {
Server server = Server();
if (!init) {
// 读取配置文件
init = true;
await _loadConfig();
for (var element in _initializedListeners) {
element.call();
}
}
server.enableSsl = _enableSsl;
server.enableSsl = configuration.enableSsl;
server.initChannel((channel) {
channel.pipeline.handle(
HttpRequestCodec(),
HttpResponseCodec(),
HttpChannelHandler(
listener: listener, requestRewrites: requestRewrites));
channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(),
HttpChannelHandler(listener: listener, requestRewrites: configuration.requestRewrites));
});
return server.bind(port).then((serverSocket) {
logger.i("listen on $port");
this.server = server;
if (enableDesktop) {
if (configuration.enableDesktop) {
SystemProxy.setSystemProxy(port, enableSsl);
}
return server;
@@ -120,7 +66,7 @@ class ProxyServer {
/// 停止代理服务
Future<Server?> stop() async {
logger.i("stop on $port");
if (enableDesktop) {
if (configuration.enableDesktop) {
if (Platform.isMacOS) {
await SystemProxy.setProxyEnableMacOS(false, enableSsl);
} else if (Platform.isWindows) {
@@ -135,80 +81,4 @@ class ProxyServer {
restart() {
stop().then((value) => start());
}
/// 刷新配置文件
flushConfig() async {
var file = await configFile();
var exists = await file.exists();
if (!exists) {
file = await file.create(recursive: true);
}
HostFilter.whitelist.toJson();
HostFilter.blacklist.toJson();
var json = jsonEncode(toJson());
logger.i('刷新配置文件 $runtimeType ${toJson()}');
file.writeAsString(json);
}
/// 加载配置文件
Future<void> _loadConfig() async {
var file = await configFile();
var exits = await file.exists();
if (!exits) {
guide = true;
return;
}
Map<String, dynamic> config = jsonDecode(await file.readAsString());
logger.i('加载配置文件 [$file]');
port = config['port'] ?? port;
enableSsl = config['enableSsl'] == true;
enableDesktop = config['enableDesktop'] ?? true;
guide = config['guide'] ?? false;
HostFilter.whitelist.load(config['whitelist']);
HostFilter.blacklist.load(config['blacklist']);
await _loadRequestRewriteConfig();
}
/// 加载请求重写配置文件
Future<void> _loadRequestRewriteConfig() async {
var home = await homeDir();
var file =
File('${home.path}${Platform.pathSeparator}request_rewrite.json');
var exits = await file.exists();
if (!exits) {
return;
}
Map<String, dynamic> config = jsonDecode(await file.readAsString());
logger.i('加载请求重写配置文件 [$file]');
requestRewrites.load(config);
}
/// 保存请求重写配置文件
flushRequestRewriteConfig() async {
var home = await homeDir();
var file =
File('${home.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);
}
Map<String, dynamic> toJson() {
return {
'guide': guide,
'port': port,
'enableSsl': enableSsl,
'enableDesktop': enableDesktop,
'whitelist': HostFilter.whitelist.toJson(),
'blacklist': HostFilter.blacklist.toJson(),
};
}
}

View File

@@ -125,7 +125,7 @@ class HttpBodyState extends State<HttpBodyWidget> {
var size = MediaQuery.of(context).size;
var ratio = 1.0;
if (Platform.isWindows) {
WindowManager.instance.getDevicePixelRatio();
ratio = WindowManager.instance.getDevicePixelRatio();
}
final window = await DesktopMultiWindow.createWindow(jsonEncode(
{'name': 'HttpBodyWidget', 'httpMessage': widget.httpMessage, 'inNewWindow': true},

View File

@@ -2,6 +2,7 @@ import 'dart:collection';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/channel.dart';
import 'package:network_proxy/network/http/http.dart';
@@ -154,7 +155,15 @@ class HeaderBody extends StatefulWidget {
///根据文本过滤
Iterable<PathRow> filter(String text) {
return _body.where((element) => element.request.requestUrl.toLowerCase().contains(text));
return _body.where((element) {
if (element.request.method.name.toLowerCase() == text) {
return true;
}
if (element.request.requestUrl.toLowerCase().contains(text)) {
return true;
}
return element.response.get()?.contentType.name.toLowerCase().contains(text) == true;
});
}
///复制
@@ -175,12 +184,13 @@ class HeaderBody extends StatefulWidget {
class _HeaderBodyState extends State<HeaderBody> {
final GlobalKey<ColorTransitionState> transitionState = GlobalKey<ColorTransitionState>();
late Configuration configuration;
late bool selected;
@override
void initState() {
super.initState();
configuration = widget.proxyServer.configuration;
selected = widget.selected;
}
@@ -241,21 +251,21 @@ class _HeaderBodyState extends State<HeaderBody> {
child: const Text("添加黑名单", style: TextStyle(fontSize: 14)),
onTap: () {
HostFilter.blacklist.add(widget.header.host);
widget.proxyServer.flushConfig();
configuration.flushConfig();
}),
PopupMenuItem(
height: 38,
child: const Text("添加白名单", style: TextStyle(fontSize: 14)),
onTap: () {
HostFilter.whitelist.add(widget.header.host);
widget.proxyServer.flushConfig();
configuration.flushConfig();
}),
PopupMenuItem(
height: 38,
child: const Text("删除白名单", style: TextStyle(fontSize: 14)),
onTap: () {
HostFilter.whitelist.remove(widget.header.host);
widget.proxyServer.flushConfig();
configuration.flushConfig();
}),
PopupMenuItem(height: 38, child: const Text("删除", style: TextStyle(fontSize: 14)), onTap: () => _delete()),
],

View File

@@ -131,7 +131,7 @@ class _PathRowState extends State<PathRow> {
var size = MediaQuery.of(context).size;
var ratio = 1.0;
if (Platform.isWindows) {
WindowManager.instance.getDevicePixelRatio();
ratio = WindowManager.instance.getDevicePixelRatio();
}
final window = await DesktopMultiWindow.createWindow(jsonEncode(

View File

@@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/util/host_filter.dart';
class FilterDialog extends StatefulWidget {
final ProxyServer proxyServer;
final Configuration configuration;
const FilterDialog({super.key, required this.proxyServer});
const FilterDialog({super.key, required this.configuration});
@override
State<FilterDialog> createState() => _FilterDialogState();
@@ -49,7 +48,7 @@ class _FilterDialogState extends State<FilterDialog> {
title: "白名单",
subtitle: "只代理白名单中的域名, 白名单启用黑名单将会失效",
hostList: HostFilter.whitelist,
proxyServer: widget.proxyServer,
configuration: widget.configuration,
hostEnableNotifier: hostEnableNotifier)),
const SizedBox(width: 10),
Expanded(
@@ -58,7 +57,7 @@ class _FilterDialogState extends State<FilterDialog> {
title: "黑名单",
subtitle: "黑名单中的域名不会代理",
hostList: HostFilter.blacklist,
proxyServer: widget.proxyServer,
configuration: widget.configuration,
hostEnableNotifier: hostEnableNotifier)),
],
),
@@ -70,7 +69,7 @@ class DomainFilter extends StatefulWidget {
final String title;
final String subtitle;
final HostList hostList;
final ProxyServer proxyServer;
final Configuration configuration;
final ValueNotifier<bool> hostEnableNotifier;
const DomainFilter(
@@ -79,7 +78,7 @@ class DomainFilter extends StatefulWidget {
required this.subtitle,
required this.hostList,
required this.hostEnableNotifier,
required this.proxyServer});
required this.configuration});
@override
State<StatefulWidget> createState() {
@@ -145,7 +144,7 @@ class _DomainFilterState extends State<DomainFilter> {
@override
void dispose() {
if (changed) {
widget.proxyServer.flushConfig();
widget.configuration.flushConfig();
}
super.dispose();
}

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/util/request_rewrite.dart';
class RequestRewrite extends StatefulWidget {
final ProxyServer proxyServer;
final Configuration configuration;
const RequestRewrite({super.key, required this.proxyServer});
const RequestRewrite({super.key, required this.configuration});
@override
State<RequestRewrite> createState() => _RequestRewriteState();
@@ -19,15 +19,15 @@ class _RequestRewriteState extends State<RequestRewrite> {
@override
void initState() {
super.initState();
requestRuleList = RequestRuleList(widget.proxyServer.requestRewrites);
enableNotifier = ValueNotifier(widget.proxyServer.requestRewrites.enabled == true);
requestRuleList = RequestRuleList(widget.configuration.requestRewrites);
enableNotifier = ValueNotifier(widget.configuration.requestRewrites.enabled == true);
}
@override
void dispose() {
if (changed || enableNotifier.value != widget.proxyServer.requestRewrites.enabled) {
widget.proxyServer.requestRewrites.enabled = enableNotifier.value;
widget.proxyServer.flushRequestRewriteConfig();
if (changed || enableNotifier.value != widget.configuration.requestRewrites.enabled) {
widget.configuration.requestRewrites.enabled = enableNotifier.value;
widget.configuration.flushRequestRewriteConfig();
}
enableNotifier.dispose();
@@ -80,7 +80,7 @@ class _RequestRewriteState extends State<RequestRewrite> {
changed = true;
setState(() {
widget.proxyServer.requestRewrites.removeIndex(removeSelected);
widget.configuration.requestRewrites.removeIndex(removeSelected);
requestRuleList.changeState();
});
})
@@ -97,7 +97,7 @@ class _RequestRewriteState extends State<RequestRewrite> {
barrierDismissible: false,
builder: (BuildContext context) {
return RuleAddDialog(
requestRewrites: widget.proxyServer.requestRewrites,
requestRewrites: widget.configuration.requestRewrites,
currentIndex: currentIndex,
onChange: () {
changed = true;
@@ -250,7 +250,7 @@ class _RequestRuleListState extends State<RequestRuleList> {
border: TableBorder.symmetric(outside: BorderSide(width: 1, color: Theme.of(context).highlightColor)),
columns: const <DataColumn>[
DataColumn(label: Text('启用')),
DataColumn(label: Text('URL')),
DataColumn(label: Text('Path')),
DataColumn(label: Text('请求体')),
DataColumn(label: Text('响应体')),
],

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/util/system_proxy.dart';
import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart';
@@ -20,10 +21,12 @@ class Setting extends StatefulWidget {
class _SettingState extends State<Setting> {
late ValueNotifier<bool> enableDesktopListenable;
late Configuration configuration;
@override
void initState() {
enableDesktopListenable = ValueNotifier<bool>(widget.proxyServer.enableDesktop);
configuration = widget.proxyServer.configuration;
enableDesktopListenable = ValueNotifier<bool>(configuration.enableDesktop);
super.initState();
}
@@ -54,12 +57,12 @@ class _SettingState extends State<Setting> {
title: const Text("抓取电脑请求"),
visualDensity: const VisualDensity(horizontal: -4),
dense: true,
value: widget.proxyServer.enableDesktop,
value: configuration.enableDesktop,
onChanged: (val) {
SystemProxy.setSystemProxyEnable(widget.proxyServer.port, val, widget.proxyServer.enableSsl);
widget.proxyServer.enableDesktop = val;
configuration.enableDesktop = val;
enableDesktopListenable.value = !enableDesktopListenable.value;
widget.proxyServer.flushConfig();
configuration.flushConfig();
}))),
const PopupMenuItem(padding: EdgeInsets.all(0), child: ThemeSetting(dense: true)),
menuItem("域名过滤", onTap: () => hostFilter()),
@@ -106,7 +109,7 @@ class _SettingState extends State<Setting> {
label: const Text("关闭"),
onPressed: () => Navigator.of(context).pop())))
]),
content: RequestRewrite(proxyServer: widget.proxyServer),
content: RequestRewrite(configuration: configuration),
);
});
}
@@ -117,7 +120,7 @@ class _SettingState extends State<Setting> {
barrierDismissible: false,
context: context,
builder: (context) {
return FilterDialog(proxyServer: widget.proxyServer);
return FilterDialog(configuration: configuration);
},
);
}
@@ -146,11 +149,11 @@ class _PortState extends State<PortWidget> {
portFocus.addListener(() async {
//失去焦点
if (!portFocus.hasFocus && textController.text != widget.proxyServer.port.toString()) {
widget.proxyServer.port = int.parse(textController.text);
widget.proxyServer.configuration.port = int.parse(textController.text);
if (widget.proxyServer.isRunning) {
widget.proxyServer.restart();
}
widget.proxyServer.flushConfig();
widget.proxyServer.configuration.flushConfig();
}
});
}

View File

@@ -19,22 +19,10 @@ class SslWidget extends StatefulWidget {
}
class _SslState extends State<SslWidget> {
bool _enableSsl = true;
@override
void initState() {
super.initState();
widget.proxyServer.initializedListener(() {
_enableSsl = widget.proxyServer.enableSsl;
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
icon: Icon(Icons.https, color: _enableSsl ? null : Colors.red),
icon: Icon(Icons.https, color: widget.proxyServer.enableSsl ? null : Colors.red),
surfaceTintColor: Colors.white70,
tooltip: "HTTPS代理",
offset: const Offset(10, 30),
@@ -42,11 +30,7 @@ class _SslState extends State<SslWidget> {
return [
PopupMenuItem(
padding: const EdgeInsets.all(0),
child: _Switch(
proxyServer: widget.proxyServer,
onEnableChange: (val) => setState(() {
_enableSsl = val;
}))),
child: _Switch(proxyServer: widget.proxyServer, onEnableChange: (val) => setState(() {}))),
PopupMenuItem(
padding: const EdgeInsets.all(0),
child: ListTile(
@@ -282,7 +266,7 @@ class _SwitchState extends State<_Switch> {
void dispose() {
super.dispose();
if (changed) {
widget.proxyServer.flushConfig();
widget.proxyServer.configuration.flushConfig();
}
}
}

View File

@@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_toastr/flutter_toastr.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/channel.dart';
import 'package:window_manager/window_manager.dart';
class SocketLaunch extends StatefulWidget {
final ProxyServer proxyServer;
final int size;
final bool startup;
final Function? onStart;
final Function? onStop;
const SocketLaunch({super.key, required this.proxyServer, this.size = 25, this.onStart, this.onStop});
const SocketLaunch(
{super.key, required this.proxyServer, this.size = 25, this.onStart, this.onStop, this.startup = true});
@override
State<StatefulWidget> createState() {
@@ -27,14 +28,9 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener, Widget
windowManager.addListener(this);
WidgetsBinding.instance.addObserver(this);
//启动代理服务器
widget.proxyServer.start().then((value) {
setState(() {
started = true;
});
widget.onStart?.call();
}).catchError((e) {
FlutterToastr.show("启动失败,请检查端口号${widget.proxyServer.port}是否被占用", context, duration: 3);
});
if (widget.startup) {
start();
}
}
@override
@@ -70,19 +66,27 @@ class _SocketLaunchState extends State<SocketLaunch> with WindowListener, Widget
icon: Icon(started ? Icons.stop : Icons.play_arrow_sharp,
color: started ? Colors.red : Colors.green, size: widget.size.toDouble()),
onPressed: () async {
Future<Server?> result = started ? widget.proxyServer.stop() : widget.proxyServer.start();
if (started) {
widget.onStop?.call();
widget.proxyServer.stop().then((value) {
widget.onStop?.call();
setState(() {
started = !started;
});
});
} else {
widget.onStart?.call();
start();
}
result
.then((value) => setState(() {
started = !started;
}))
.catchError((e) {
FlutterToastr.show("启动失败,请检查端口号${widget.proxyServer.port}是否被占用", context);
});
});
}
start() {
widget.proxyServer.start().then((value) {
setState(() {
started = true;
});
widget.onStart?.call();
}).catchError((e) {
FlutterToastr.show("启动失败,请检查端口号${widget.proxyServer.port}是否被占用", context, duration: 3);
});
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/http_client.dart';
import 'package:network_proxy/network/util/host_filter.dart';
@@ -75,7 +76,7 @@ class ConnectRemoteState extends State<ConnectRemote> {
showDialog(
context: context,
builder: (context) {
return ConfigSyncWidget(proxyServer: widget.proxyServer, config: config);
return ConfigSyncWidget(configuration: widget.proxyServer.configuration, config: config);
});
}
}).onError((error, stackTrace) {
@@ -86,10 +87,10 @@ class ConnectRemoteState extends State<ConnectRemote> {
}
class ConfigSyncWidget extends StatefulWidget {
final ProxyServer proxyServer;
final Configuration configuration;
final Map<String, dynamic> config;
const ConfigSyncWidget({super.key, required this.proxyServer, required this.config});
const ConfigSyncWidget({super.key, required this.configuration, required this.config});
@override
State<StatefulWidget> createState() {
@@ -152,10 +153,10 @@ class ConfigSyncState extends State<ConfigSyncWidget> {
HostFilter.blacklist.load(widget.config['blacklist']);
}
if (syncRewrite) {
widget.proxyServer.requestRewrites.load(widget.config['requestRewrites']);
widget.proxyServer.flushRequestRewriteConfig();
widget.configuration.requestRewrites.load(widget.config['requestRewrites']);
widget.configuration.flushRequestRewriteConfig();
}
widget.proxyServer.flushConfig();
widget.configuration.flushConfig();
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('同步成功')));
}),

View File

@@ -43,17 +43,17 @@ class DrawerWidget extends StatelessWidget {
ListTile(
title: const Text("域名白名单"),
trailing: const Icon(Icons.arrow_right),
onTap: () =>
navigator(context, MobileFilterWidget(proxyServer: proxyServer, hostList: HostFilter.whitelist))),
onTap: () => navigator(
context, MobileFilterWidget(configuration: proxyServer.configuration, hostList: HostFilter.whitelist))),
ListTile(
title: const Text("域名黑名单"),
trailing: const Icon(Icons.arrow_right),
onTap: () =>
navigator(context, MobileFilterWidget(proxyServer: proxyServer, hostList: HostFilter.blacklist))),
onTap: () => navigator(
context, MobileFilterWidget(configuration: proxyServer.configuration, hostList: HostFilter.blacklist))),
ListTile(
title: const Text("请求重写"),
trailing: const Icon(Icons.arrow_right),
onTap: () => navigator(context, MobileRequestRewrite(proxyServer: proxyServer))),
onTap: () => navigator(context, MobileRequestRewrite(configuration: proxyServer.configuration))),
ListTile(
title: const Text("Github"),
trailing: const Icon(Icons.arrow_right),
@@ -169,7 +169,7 @@ class MoreEnum extends StatelessWidget {
hostname: response.headers.get("hostname"));
if (context.mounted && Navigator.canPop(context)) {
FlutterToastr.show("连接成功", context);
FlutterToastr.show("连接成功${proxyServer.isRunning ? '' : ',手机需要开启抓包才可以抓取请求哦'}", context);
Navigator.pop(context);
}
}

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:network_proxy/native/vpn.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/channel.dart';
import 'package:network_proxy/network/handler.dart';
@@ -13,7 +14,9 @@ import 'package:network_proxy/ui/mobile/menu.dart';
import 'package:network_proxy/ui/mobile/request/list.dart';
class MobileHomePage extends StatefulWidget {
const MobileHomePage({super.key});
final Configuration configuration;
const MobileHomePage({super.key, required this.configuration});
@override
State<StatefulWidget> createState() {
@@ -39,7 +42,7 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener {
@override
void initState() {
proxyServer = ProxyServer(listener: this);
proxyServer = ProxyServer(widget.configuration, listener: this);
desktop.addListener(() {
if (desktop.value.connect) {
proxyServer.server?.remoteHost = "http://${desktop.value.host}:${desktop.value.port}";
@@ -48,7 +51,26 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener {
proxyServer.server?.remoteHost = null;
}
});
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.configuration.guide) {
//首次引导
String content = '默认不会开启HTTPS抓包请安装证书后再开启HTTPS抓包。\n'
'点击的设置 -> HTTPS抓包根据提示安装证书操作即可。';
showAlertDialog('提示', content, () {
widget.configuration.guide = false;
widget.configuration.upgradeNotice = false;
widget.configuration.flushConfig();
});
return;
}
if (widget.configuration.upgradeNotice) {
showUpgradeNotice();
}
});
}
@override
@@ -74,6 +96,7 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener {
onPressed: () {},
child: SocketLaunch(
proxyServer: proxyServer,
startup: false,
size: 38,
onStart: () => Vpn.startVpn("127.0.0.1", proxyServer.port),
onStop: () => Vpn.stopVpn())),
@@ -100,6 +123,32 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener {
);
}
showUpgradeNotice() {
String content = '1. 手机版启动默认不再自动开启抓包,请手动点击启动按钮。\n'
'2. 增加外部代理可配置其他VPN软件地址开启抓包不会影响访问外网。\n'
'3. 搜索功能增强,可直接搜索响应类型和请求方法。';
showAlertDialog('更新内容', content, () {
widget.configuration.upgradeNotice = false;
widget.configuration.flushConfig();
});
}
showAlertDialog(String title, String content, Function onClose) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) {
return AlertDialog(actions: [
TextButton(
onPressed: () {
onClose.call();
Navigator.pop(context);
},
child: const Text('关闭'))
], title: Text(title, style: const TextStyle(fontSize: 18)), content: Text(content));
});
}
/// 搜索框
Widget search() {
return Padding(

View File

@@ -3,6 +3,7 @@ import 'dart:collection';
import 'package:date_format/date_format.dart';
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/bin/server.dart';
import 'package:network_proxy/network/channel.dart';
import 'package:network_proxy/network/http/http.dart';
@@ -160,17 +161,26 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
}
bool filter(HttpRequest request) {
if (searchText?.isNotEmpty == true) {
return request.requestUrl.toLowerCase().contains(searchText!);
if (searchText == null || searchText!.isEmpty) {
return true;
}
return true;
if (request.method.name.toLowerCase() == searchText) {
return true;
}
if (request.requestUrl.toLowerCase().contains(searchText!)) {
return true;
}
return request.response?.contentType.name.toLowerCase().contains(searchText!) == true;
}
changeState() {
//防止频繁刷新
if (!changing) {
changing = true;
Future.delayed(const Duration(milliseconds: 300), () {
Future.delayed(const Duration(milliseconds: 100), () {
setState(() {
changing = false;
});
@@ -217,6 +227,7 @@ class DomainList extends StatefulWidget {
class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMixin {
GlobalKey<RequestSequenceState> requestSequenceKey = GlobalKey<RequestSequenceState>();
late Configuration configuration;
//域名和对应请求列表的映射
Map<HostAndPort, List<HttpRequest>> containerMap = {};
@@ -235,6 +246,8 @@ class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMix
@override
initState() {
super.initState();
configuration = widget.proxyServer.configuration;
for (var request in widget.list) {
var hostAndPort = request.hostAndPort!;
container.add(hostAndPort);
@@ -271,7 +284,7 @@ class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMix
//防止频繁刷新
if (!changing) {
changing = true;
Future.delayed(const Duration(milliseconds: 200), () {
Future.delayed(const Duration(milliseconds: 50), () {
setState(() {
changing = false;
});
@@ -332,7 +345,7 @@ class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMix
var time =
formatDate(containerMap[list.elementAt(index)]!.last.requestTime, [m, '/', d, ' ', HH, ':', nn, ':', ss]);
return ListTile(
visualDensity: const VisualDensity( vertical: -4),
visualDensity: const VisualDensity(vertical: -4),
title: Text(list.elementAt(index).domain, maxLines: 1, overflow: TextOverflow.ellipsis),
trailing: const Icon(Icons.chevron_right),
subtitle: Text("最后请求时间: $time, 次数: ${containerMap[list.elementAt(index)]!.length}",
@@ -366,7 +379,7 @@ class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMix
child: const SizedBox(width: double.infinity, child: Text("添加黑名单", textAlign: TextAlign.center)),
onPressed: () {
HostFilter.blacklist.add(hostAndPort.host);
widget.proxyServer.flushConfig();
configuration.flushConfig();
FlutterToastr.show("已添加至黑名单", context);
Navigator.of(context).pop();
}),
@@ -375,7 +388,7 @@ class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMix
child: const SizedBox(width: double.infinity, child: Text("添加白名单", textAlign: TextAlign.center)),
onPressed: () {
HostFilter.whitelist.add(hostAndPort.host);
widget.proxyServer.flushConfig();
configuration.flushConfig();
FlutterToastr.show("已添加至白名单", context);
Navigator.of(context).pop();
}),
@@ -384,7 +397,7 @@ class DomainListState extends State<DomainList> with AutomaticKeepAliveClientMix
child: const SizedBox(width: double.infinity, child: Text("删除白名单", textAlign: TextAlign.center)),
onPressed: () {
HostFilter.whitelist.remove(hostAndPort.host);
widget.proxyServer.flushConfig();
configuration.flushConfig();
FlutterToastr.show("已删除白名单", context);
Navigator.of(context).pop();
}),

View File

@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import '../../../../network/util/host_filter.dart';
class MobileFilterWidget extends StatefulWidget {
final ProxyServer proxyServer;
final Configuration configuration;
final HostList hostList;
const MobileFilterWidget({super.key, required this.proxyServer, required this.hostList});
const MobileFilterWidget({super.key, required this.configuration, required this.hostList});
@override
State<MobileFilterWidget> createState() => _MobileFilterState();
@@ -34,7 +34,7 @@ class _MobileFilterState extends State<MobileFilterWidget> {
title: title,
subtitle: subtitle,
hostList: widget.hostList,
proxyServer: widget.proxyServer,
configuration: widget.configuration,
hostEnableNotifier: hostEnableNotifier),
));
}
@@ -44,7 +44,7 @@ class DomainFilter extends StatefulWidget {
final String title;
final String subtitle;
final HostList hostList;
final ProxyServer proxyServer;
final Configuration configuration;
final ValueNotifier<bool> hostEnableNotifier;
const DomainFilter(
@@ -53,7 +53,7 @@ class DomainFilter extends StatefulWidget {
required this.subtitle,
required this.hostList,
required this.hostEnableNotifier,
required this.proxyServer});
required this.configuration});
@override
State<StatefulWidget> createState() {
@@ -114,7 +114,7 @@ class _DomainFilterState extends State<DomainFilter> {
@override
void dispose() {
if (changed) {
widget.proxyServer.flushConfig();
widget.configuration.flushConfig();
}
super.dispose();
}

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/bin/configuration.dart';
import 'package:network_proxy/network/util/request_rewrite.dart';
class MobileRequestRewrite extends StatefulWidget {
final ProxyServer proxyServer;
final Configuration configuration;
const MobileRequestRewrite({super.key, required this.proxyServer});
const MobileRequestRewrite({super.key, required this.configuration});
@override
State<MobileRequestRewrite> createState() => _MobileRequestRewriteState();
@@ -19,15 +19,15 @@ class _MobileRequestRewriteState extends State<MobileRequestRewrite> {
@override
void initState() {
super.initState();
requestRuleList = RequestRuleList(widget.proxyServer.requestRewrites);
enableNotifier = ValueNotifier(widget.proxyServer.requestRewrites.enabled);
requestRuleList = RequestRuleList(widget.configuration.requestRewrites);
enableNotifier = ValueNotifier(widget.configuration.requestRewrites.enabled);
}
@override
void dispose() {
if (changed || enableNotifier.value != widget.proxyServer.requestRewrites.enabled) {
widget.proxyServer.requestRewrites.enabled = enableNotifier.value;
widget.proxyServer.flushRequestRewriteConfig();
if (changed || enableNotifier.value != widget.configuration.requestRewrites.enabled) {
widget.configuration.requestRewrites.enabled = enableNotifier.value;
widget.configuration.flushRequestRewriteConfig();
}
enableNotifier.dispose();
@@ -81,7 +81,7 @@ class _MobileRequestRewriteState extends State<MobileRequestRewrite> {
changed = true;
setState(() {
widget.proxyServer.requestRewrites.removeIndex(removeSelected);
widget.configuration.requestRewrites.removeIndex(removeSelected);
requestRuleList.changeState();
});
})
@@ -97,7 +97,7 @@ class _MobileRequestRewriteState extends State<MobileRequestRewrite> {
context: context,
builder: (BuildContext context) {
return RuleAddDialog(
requestRewrites: widget.proxyServer.requestRewrites,
requestRewrites: widget.configuration.requestRewrites,
currentIndex: currentIndex,
onChange: () {
changed = true;
@@ -248,7 +248,7 @@ class _RequestRuleListState extends State<RequestRuleList> {
border: TableBorder.symmetric(outside: BorderSide(width: 1, color: Theme.of(context).highlightColor)),
columns: const <DataColumn>[
DataColumn(label: Text('启用')),
DataColumn(label: Text('URL')),
DataColumn(label: Text('Path')),
DataColumn(label: Text('请求体')),
DataColumn(label: Text('响应体')),
],

View File

@@ -21,7 +21,7 @@ class _MobileSslState extends State<MobileSslWidget> {
@override
void dispose() {
if (changed) {
widget.proxyServer.flushConfig();
widget.proxyServer.configuration.flushConfig();
}
super.dispose();
}

View File

@@ -9,11 +9,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:network_proxy/main.dart';
import 'package:network_proxy/network/bin/configuration.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const FluentApp(DesktopHomePage()));
Configuration configuration = await Configuration.instance;
await tester.pumpWidget(FluentApp(DesktopHomePage(configuration: configuration)));
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);