From 099175fa27b6f541dc248a0f23ebab4100092e60 Mon Sep 17 00:00:00 2001 From: wanghongen Date: Wed, 7 Jun 2023 19:13:00 +0800 Subject: [PATCH] ui --- README.md | 2 + lib/main.dart | 229 ++++++++++++++--------- lib/network/bin/server.dart | 24 ++- lib/network/channel.dart | 78 ++++++-- lib/network/handler.dart | 30 ++- lib/network/http/codec.dart | 2 +- lib/network/http/http.dart | 20 +- lib/network/util/CertificateManager.dart | 26 +-- lib/network/util/logger.dart | 11 ++ lib/ui/components.dart | 82 ++++++++ lib/ui/widgets.dart | 78 ++++++++ lib/{network => utils}/compress.dart | 0 macos/Runner/DebugProfile.entitlements | 2 + macos/Runner/Release.entitlements | 2 + pubspec.yaml | 33 +--- 15 files changed, 441 insertions(+), 178 deletions(-) create mode 100644 lib/network/util/logger.dart create mode 100644 lib/ui/components.dart create mode 100644 lib/ui/widgets.dart rename lib/{network => utils}/compress.dart (100%) diff --git a/README.md b/README.md index dd427b8..f6af756 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ network proxy +https://blog.niekun.net/archives/1629.html + ## Getting Started This project is a starting point for a Flutter application. diff --git a/lib/main.dart b/lib/main.dart index dda5554..b9562cf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,14 @@ +import 'dart:collection'; + import 'package:flutter/material.dart'; +import 'package:network/network/bin/server.dart'; +import 'package:network/ui/widgets.dart'; + +import 'network/channel.dart'; +import 'network/handler.dart'; +import 'network/http/http.dart'; +import 'network/util/AttributeKeys.dart'; +import 'ui/components.dart'; void main() { runApp(const MyApp()); @@ -7,119 +17,158 @@ void main() { class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'Doraemon', theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a blue toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: const NetworkHomePage(title: 'Network Proxy'), ); } } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". +class NetworkHomePage extends StatefulWidget { + const NetworkHomePage({super.key, required this.title}); final String title; @override - State createState() => _MyHomePageState(); + State createState() => _NetworkHomePagePageState(); } -class _MyHomePageState extends State { - int _counter = 0; +class _NetworkHomePagePageState extends State implements EventListener { + LinkedHashMap> containerMap = LinkedHashMap>(); - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); + Set expandedHosts = {}; + + @override + void onRequest(Channel channel, HttpRequest request) { + HostAndPort hostAndPort = channel.getAttribute(AttributeKeys.HOST_KEY); + var list = containerMap[hostAndPort]; + if (list == null) { + list = [request]; + setState(() { + containerMap; + }); + containerMap[hostAndPort] = list; + } else { + list.add(request); + } + } + + @override + void onResponse(Channel channel, HttpResponse response) {} + + @override + void initState() { + super.initState(); + print("initState"); + start(listener: this); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(widget.title), ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); + body: Row(children: [ + SizedBox(width: 420, child: ListView(children: _buildHosts())), + const Spacer(), + Expanded(flex: 100, child: NetworkTabController()), + ])); + } + + Widget _row(HostAndPort host) { + bool selected = expandedHosts.contains(host.url); + return ListTile( + leading: Icon(selected ? Icons.arrow_drop_down : Icons.arrow_right, size: 16), + dense: true, + selected: selected, + horizontalTitleGap: 0, + visualDensity: const VisualDensity(vertical: -3.6), + title: Text(host.url, textAlign: TextAlign.left), + onTap: () { + if (!expandedHosts.remove(host.url)) { + expandedHosts.add(host.url); + } + setState(() { + expandedHosts; + }); + }); + } + + List _buildHosts() { + print(containerMap.keys); + List list = []; + for (var host in containerMap.keys) { + list.add(_row(host)); + + if (expandedHosts.contains(host.url)) { + containerMap[host]?.forEach((element) { + print(element); + list.add(ListURI(leading: Icons.html, text: '${element.method.name} ${Uri.parse(element.uri).path}')); + }); + } + } + return list; + // return [ + // ListTile( + // leading: const Icon(Icons.arrow_drop_down), + // dense: true, + // title: const Text('https://dmall.com', textAlign: TextAlign.left), + // selected: true, + // onTap: () {}), + // const ListURI(leading: Icons.image_outlined, text: 'POST /private/browser/stats/haha'), + // const ListURI(leading: Icons.javascript_sharp, text: 'GET /private/browser/stats/haha'), + // const ListURI(leading: Icons.css, text: 'GET /private/browser/stats/haha'), + // ListTile( + // leading: const Icon( + // size: 15, + // Icons.document_scanner, + // color: Colors.red, + // ), + // title: const Text('POST /private/browser/stats', textAlign: TextAlign.left), + // textColor: Colors.red, + // trailing: const Icon(Icons.chevron_right), + // visualDensity: const VisualDensity(vertical: -4), + // dense: true, + // contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 50.0), + // onTap: () {}), + // const Divider(), + // ListTile( + // leading: const Icon(Icons.expand_more), + // dense: true, + // title: const Text('https://baidu.com', textAlign: TextAlign.left), + // onTap: () {}), + // _buildRow(), + // const Divider(), + // ]; + } + + Widget _buildRow() { + final ScrollController scrollController = ScrollController(); + return Scrollbar( + controller: scrollController, + thumbVisibility: true, + trackVisibility: true, + child: SingleChildScrollView( + controller: scrollController, + scrollDirection: Axis.horizontal, + child: Container( + margin: const EdgeInsets.only(left: 16), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RowURI(leading: Icons.image, text: "POST /private/browser/stats/private/browser/stats"), + RowURI(leading: Icons.javascript_sharp, text: "GET /private/browser/stats.js"), + RowURI(leading: Icons.css, text: "GET /openapi/documents/oristartguid.css"), + ], + )), + )); } } diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index 9876d27..b75e8cb 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -1,14 +1,34 @@ import 'dart:async'; +import 'dart:io'; import '../channel.dart'; import '../handler.dart'; import '../http/codec.dart'; Future main() async { + start(); +} + +Future start({EventListener? listener}) async { const port = 8888; Server server = Server(port) ..initChannel((channel) { - channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(), HttpChannelHandler()); + channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(), HttpChannelHandler(listener: listener)); }); - await server.bind(); + await server.bind().then((value) => {setSystemProxy(port)}); +} + +void setSystemProxy(int port) { + if (Platform.isMacOS) { + // Process.run('networksetup', ['-getsecurewebproxy', 'Wi-Fi']).then((ProcessResult results) { + // print(results.stdout); + // }); + // Process.run('networksetup', ['-setsecurewebproxy', 'Wi-Fi', '127.0.0.1', port.toString()]) + // .then((ProcessResult results) { + // print(results.stdout); + // }); + // Process.run('networksetup', ['-setwebproxy', 'Wi-Fi', '127.0.0.1', port.toString()]).then((ProcessResult results) { + // print(results.stdout); + // }); + } } diff --git a/lib/network/channel.dart b/lib/network/channel.dart index 13c1f80..336db11 100644 --- a/lib/network/channel.dart +++ b/lib/network/channel.dart @@ -2,22 +2,25 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; +import 'package:basic_utils/basic_utils.dart'; import 'package:logger/logger.dart'; import 'package:network/network/util/AttributeKeys.dart'; import 'package:network/network/util/CertificateManager.dart'; +import 'package:network/network/util/HostFilter.dart'; +import 'handler.dart'; ///处理I/O事件或截获I/O操作 abstract class ChannelHandler { var log = Logger( printer: PrettyPrinter( - methodCount: 0, - errorMethodCount: 8, - lineLength: 120, - colors: true, - printEmojis: false, - excludeBox: {Level.info: true, Level.debug: true}, - )); + methodCount: 0, + errorMethodCount: 8, + lineLength: 120, + colors: true, + printEmojis: false, + excludeBox: {Level.info: true, Level.debug: true}, + )); void channelActive(Channel channel) {} @@ -28,8 +31,14 @@ abstract class ChannelHandler { } void exceptionCaught(Channel channel, Object cause, {StackTrace? trace}) { - var attribute = channel.getAttribute(AttributeKeys.HOST_KEY); - log.e("error $attribute $channel", cause, trace); + HostAndPort? attribute = channel.getAttribute(AttributeKeys.HOST_KEY); + X509CertificateData? x509certificateFromPem; + if (attribute != null && CertificateManager.get(attribute.host) != null) { + String cer = CertificateManager.get(attribute.host)!; + x509certificateFromPem = X509Utils.x509CertificateFromPem(cer); + } + + log.e("error $attribute $channel ${x509certificateFromPem?.tbsCertificate?.subject}", cause, trace); } } @@ -46,7 +55,9 @@ class Channel { final int remotePort; Channel(this._socket) - : _id = DateTime.now().millisecondsSinceEpoch + Random().nextInt(9999), + : _id = DateTime + .now() + .millisecondsSinceEpoch + Random().nextInt(999999), remoteAddress = _socket.remoteAddress, remotePort = _socket.remotePort; @@ -86,7 +97,7 @@ class Channel { @override String toString() { - return 'Channel($_id ${remoteAddress.host}:$remotePort)'; + return 'Channel($id ${remoteAddress.host}:$remotePort)'; } } @@ -162,9 +173,25 @@ class HostAndPort { return HostAndPort(scheme, hostAndPort[0], 80); } + String get url { + return '$scheme$host${(port == 80 || port == 443) ? "" : ":$port"}'; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HostAndPort && + runtimeType == other.runtimeType && + scheme == other.scheme && + host == other.host && + port == other.port; + + @override + int get hashCode => scheme.hashCode ^ host.hashCode ^ port.hashCode; + @override String toString() { - return '$scheme$host:$port'; + return url; } } @@ -180,7 +207,7 @@ abstract interface class Encoder { /// 编解码器 abstract class Codec implements Decoder, Encoder { - static const int defaultMaxInitialLineLength = 1024; + static const int defaultMaxInitialLineLength = 8192; } class RawCodec extends Codec { @@ -222,6 +249,7 @@ class Network { _onEvent(Uint8List data, Channel channel) async { HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.HOST_KEY); + //ssl握手 if (hostAndPort != null && hostAndPort.isSsl()) { try { var certificate = await CertificateManager.getCertificateContext(hostAndPort.host); @@ -233,9 +261,22 @@ class Network { } return; } + //黑名单 + if (hostAndPort != null && HostFilter.filter(hostAndPort.host)) { + if (HostFilter.filter(hostAndPort.host)) { + relay(channel, channel.getAttribute(channel.id)); + } + } channel.pipeline.channelRead(channel, data); } + + /// 转发请求 + void relay(Channel clientChannel, Channel remoteChannel) { + var rawCodec = RawCodec(); + clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel)); + remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel)); + } } class Server extends Network { @@ -244,11 +285,12 @@ class Server extends Network { Server(this.port); Future bind() async { - ServerSocket.bind(InternetAddress.loopbackIPv4, port).then((serverSocket) => { - serverSocket.listen((socket) { - listen(socket); - }) - }); + ServerSocket.bind(InternetAddress.loopbackIPv4, port).then((serverSocket) => + { + serverSocket.listen((socket) { + listen(socket); + }) + }); } } diff --git a/lib/network/handler.dart b/lib/network/handler.dart index f4ee5cb..df0b9b4 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -1,10 +1,12 @@ import 'dart:collection'; import 'dart:convert'; +import 'dart:io'; import 'package:network/network/http/http.dart'; import 'package:network/network/http/http_headers.dart'; import 'package:network/network/util/AttributeKeys.dart'; import 'package:network/network/util/HostFilter.dart'; +import 'package:network/network/util/logger.dart'; import 'channel.dart'; import 'http/codec.dart'; @@ -19,8 +21,18 @@ HostAndPort getHostAndPort(HttpRequest request) { return HostAndPort.of(requestUri); } +abstract class EventListener { + void onRequest(Channel channel, HttpRequest request); + + void onResponse(Channel channel, HttpResponse response); +} + /// class HttpChannelHandler extends ChannelHandler { + EventListener? listener; + + HttpChannelHandler({this.listener}); + @override void channelActive(Channel channel) { // log.i("accept ${channel.remoteAddress.address}"); @@ -45,7 +57,6 @@ class HttpChannelHandler extends ChannelHandler { /// 转发请求 Future forward(Channel channel, HttpRequest httpRequest) async { var remoteChannel = await _getRemoteChannel(channel, httpRequest); - //实现抓包代理转发 if (httpRequest.method != HttpMethod.connect) { if (channel.getAttribute(AttributeKeys.HOST_KEY) == null) { @@ -55,7 +66,7 @@ class HttpChannelHandler extends ChannelHandler { AttributeKeys.URI_KEY, '${channel.getAttribute(AttributeKeys.HOST_KEY)}${httpRequest.uri}'); } log.i("[${channel.id}] ${remoteChannel.getAttribute(AttributeKeys.URI_KEY)}"); - + listener?.onRequest(channel, httpRequest); //实现抓包代理转发 await remoteChannel.write(httpRequest); } @@ -81,22 +92,9 @@ class HttpChannelHandler extends ChannelHandler { await clientChannel.write(HttpResponse(httpRequest.protocolVersion, HttpStatus.ok)); } - //黑名单 - if (HostFilter.filter(hostAndPort.host)) { - relay(clientChannel, proxyChannel); - return proxyChannel; - } - clientChannel.putAttribute(clientId, proxyChannel); return proxyChannel; } - - /// 转发请求 - void relay(Channel clientChannel, Channel remoteChannel) { - var rawCodec = RawCodec(); - clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel)); - remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel)); - } } /// http响应代理 @@ -112,7 +110,7 @@ class HttpResponseProxyHandler extends ChannelHandler { void channelRead(Channel channel, HttpResponse msg) { String contentType = msg.headers.contentType; if (excludeContent.every((element) => !contentType.contains(element))) { - log.i("[${clientChannel.id}] Response ${utf8.decode(msg.body ?? [])}"); + // log.i("[${clientChannel.id}] Response ${String.fromCharCodes(msg.body ?? [])}"); } //发送给客户端 clientChannel.write(msg); diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index a44af54..81a2482 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:network/utils/num.dart'; import '../channel.dart'; -import '../compress.dart'; +import '../../utils/compress.dart'; import 'http.dart'; import 'http_headers.dart'; diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 0d504ca..6ab8247 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -24,8 +24,7 @@ class HttpRequest extends HttpMessage { final String uri; late HttpMethod method; - HttpRequest(this.method, this.uri, String protocolVersion) - : super(protocolVersion); + HttpRequest(this.method, this.uri, String protocolVersion) : super(protocolVersion); @override String toString() { @@ -62,8 +61,12 @@ enum HttpMethod { const HttpMethod(this.name); static HttpMethod valueOf(String name) { - return HttpMethod.values - .firstWhere((element) => element.name == name.toUpperCase()); + try { + return HttpMethod.values.firstWhere((element) => element.name == name.toUpperCase()); + } catch (error) { + print("$name :$error"); + rethrow; + } } } @@ -85,19 +88,16 @@ class HttpStatus { static final HttpStatus notFound = newStatus(404, "Not Found"); /// 500 Internal Server Error - static final HttpStatus internalServerError = - newStatus(500, "Internal Server Error"); + static final HttpStatus internalServerError = newStatus(500, "Internal Server Error"); /// 502 Bad Gateway static final HttpStatus badGateway = newStatus(502, "Bad Gateway"); /// 503 Service Unavailable - static final HttpStatus serviceUnavailable = - newStatus(503, "Service Unavailable"); + static final HttpStatus serviceUnavailable = newStatus(503, "Service Unavailable"); /// 504 Gateway Timeout - static final HttpStatus gatewayTimeout = - newStatus(504, "Gateway Timeout"); + static final HttpStatus gatewayTimeout = newStatus(504, "Gateway Timeout"); static HttpStatus newStatus(int statusCode, String reasonPhrase) { return HttpStatus(statusCode, reasonPhrase); diff --git a/lib/network/util/CertificateManager.dart b/lib/network/util/CertificateManager.dart index 3b75c79..d2138b0 100644 --- a/lib/network/util/CertificateManager.dart +++ b/lib/network/util/CertificateManager.dart @@ -6,7 +6,6 @@ import 'package:basic_utils/basic_utils.dart'; Future main() async { var securityContext = await CertificateManager.getCertificateContext('www.baidu.com'); print(securityContext); - // print(CertificateManager.caCert.tbsCertificateSeqAsString); print(CertificateManager._caCert.tbsCertificate?.subject); print(CertificateManager._caCert.tbsCertificateSeqAsString); print(CertificateManager._caCert); @@ -14,7 +13,7 @@ Future main() async { class CertificateManager { /// 证书缓存 - static final Map _certificateMap = {}; + static final Map _certificateMap = {}; /// 服务端密钥 static final AsymmetricKeyPair _serverKeyPair = CryptoUtils.generateRSAKeyPair(); @@ -28,22 +27,27 @@ class CertificateManager { /// 是否初始化 static bool _initialized = false; + static String? get(String host) { + return _certificateMap[host]; + } + /// 获取域名自签名证书 static Future getCertificateContext(String host) async { - if (_certificateMap.containsKey(host)) { - return _certificateMap[host]!; - } - if (!_initialized) { - await _initCAConfig(); + var cer = _certificateMap[host]; + + if (cer == null) { + if (!_initialized) { + await _initCAConfig(); + } + cer = generate(_serverKeyPair.publicKey as RSAPublicKey, _caPriKey, host); + _certificateMap[host] = cer; } - var cer = generate(_serverKeyPair.publicKey as RSAPublicKey, _caPriKey, host); var rsaPrivateKey = _serverKeyPair.privateKey as RSAPrivateKey; - var securityContext = SecurityContext.defaultContext + + return SecurityContext.defaultContext ..useCertificateChainBytes(cer.codeUnits) ..usePrivateKeyBytes(CryptoUtils.encodeRSAPrivateKeyToPemPkcs1(rsaPrivateKey).codeUnits); - _certificateMap[host] = securityContext; - return securityContext; } /// 生成证书 diff --git a/lib/network/util/logger.dart b/lib/network/util/logger.dart new file mode 100644 index 0000000..39c49c7 --- /dev/null +++ b/lib/network/util/logger.dart @@ -0,0 +1,11 @@ +import 'package:logger/logger.dart'; + +final log = Logger( + printer: PrettyPrinter( + methodCount: 0, + errorMethodCount: 8, + lineLength: 120, + colors: true, + printEmojis: false, + excludeBox: {Level.info: true, Level.debug: true}, + )); diff --git a/lib/ui/components.dart b/lib/ui/components.dart new file mode 100644 index 0000000..b249fff --- /dev/null +++ b/lib/ui/components.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; + +class ListURI extends StatefulWidget { + final IconData? leading; + final String text; + final Color? color; + final IconData trailing; + + const ListURI( + {Key? key, this.leading, required this.text, this.color = Colors.green, this.trailing = Icons.chevron_right}) + : super(key: key); + + @override + State createState() => _ListURIState(); +} + +class _ListURIState extends State { + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(widget.leading, size: 15, color: widget.color), + title: Text(widget.text, overflow: TextOverflow.ellipsis, maxLines: 1), + trailing: Icon(widget.trailing), + dense: true, + contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 50.0), + onTap: () {}); + } +} + +class RowURI extends StatefulWidget { + final IconData? leading; + final String text; + final IconData trailing; + + const RowURI({Key? key, this.leading, required this.text, this.trailing = Icons.arrow_right}) : super(key: key); + + @override + State createState() { + return _RowURIState(); + } +} + +class _RowURIState extends State { + @override + Widget build(BuildContext context) { + return Row(children: [ + TextButton.icon( + icon: Icon(widget.leading, size: 16), + onPressed: () { + print("hello"); + }, + label: Text(widget.text, style: const TextStyle(color: Colors.black87)), + ), + const Positioned(right: 10, child: Icon(Icons.chevron_right)) + ]); + } +} + +class IconText extends StatefulWidget { + const IconText({Key? key, this.leading, required this.text, this.color, this.trailing}) : super(key: key); + + final Widget? leading; + final String text; + final Color? color; + final Widget? trailing; + + @override + State createState() => _IconTextState(); +} + +class _IconTextState extends State { + @override + Widget build(BuildContext context) { + return Row( + children: [ + widget.leading ?? const SizedBox(), + Text(widget.text), + widget.trailing ?? const SizedBox(), + ], + ); + } +} diff --git a/lib/ui/widgets.dart b/lib/ui/widgets.dart new file mode 100644 index 0000000..b5c7d68 --- /dev/null +++ b/lib/ui/widgets.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +class NetworkTabController extends DefaultTabController { + static const myTabs = [ + Tab(child: Text('General', style: TextStyle(fontSize: 18))), + Tab(child: Text('Request', style: TextStyle(fontSize: 18))), + Tab(child: Text('Response', style: TextStyle(fontSize: 18))), + Tab(child: Text('Cookies', style: TextStyle(fontSize: 18))), + ]; + + NetworkTabController({super.key}) + : super( + length: 4, + child: Scaffold( + appBar: AppBar( + title: const TabBar(tabs: myTabs), + ), + body: TabBarView( + children: [ + ListView(children: const [ + Text.rich(TextSpan(children: [ + TextSpan(text: "Home: "), + TextSpan(text: "https://flutterchina.club", style: TextStyle(color: Colors.blue)), + ])) + ]), + Container( + padding: const EdgeInsets.all(20), + child: const Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text('General', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), + Row(children: [ + Expanded(flex: 1, child: Text("Request URL:")), + Expanded( + flex: 4, + child: Text( + "https://googleads.g.doubleclick.net/pagead/html/r20230601/r20190131/zrt_lookup.html")) + ]), + SizedBox(height: 10), //保留间距 + Row(children: [ + Expanded(flex: 1, child: Text("Request Method:")), + Expanded(flex: 4, child: Text("GET")) + ]), + SizedBox(height: 10), //保留间距 + Row(children: [ + Expanded(flex: 1, child: Text("Status Code:")), + Expanded(flex: 4, child: Text("200")) + ]), + SizedBox(height: 10), //保留间距 + Row(children: [ + Expanded(flex: 1, child: Text("Remote Address:")), + Expanded(flex: 4, child: Text("127.0.0.1:8080")) + ]) + ])), + const ExpansionTile( + title: Text("General", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)), + initiallyExpanded: true, + childrenPadding: EdgeInsets.all(20), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + Expanded(flex: 1, child: Text("Request URL:")), + Expanded( + flex: 4, + child: Text( + "https://googleads.g.doubleclick.net/pagead/html/r20230601/r20190131/zrt_lookup.html")) + ]), + SizedBox( + height: 20, + ), + Row(children: [ + Expanded(flex: 1, child: Text("Status Code:")), + Expanded(flex: 4, child: Text("200")) + ]) + ]), + ListView(children: const [Text("Cookies")]) + ], + ), + )); +} diff --git a/lib/network/compress.dart b/lib/utils/compress.dart similarity index 100% rename from lib/network/compress.dart rename to lib/utils/compress.dart diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a3..c946719 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a..48271ac 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,5 +4,7 @@ com.apple.security.app-sandbox + com.apple.security.network.client + diff --git a/pubspec.yaml b/pubspec.yaml index 7001de7..3d46317 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,21 +1,7 @@ name: network description: network proxy -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: @@ -38,24 +24,13 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware + assets: + - assets/certs/ca.crt + - assets/certs/ca_private.der # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages @@ -79,6 +54,4 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages - assets: - - assets/certs/