diff --git a/lib/network/handler.dart b/lib/network/handler.dart index 651550d..de4fbd5 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -28,6 +28,7 @@ import 'package:network_proxy/network/util/request_rewrite.dart'; import 'package:network_proxy/utils/ip.dart'; import 'channel.dart'; +import 'http/codec.dart'; import 'http_client.dart'; ///请求和响应事件监听 @@ -61,16 +62,30 @@ class HttpChannelHandler extends ChannelHandler { //转发请求 forward(channel, msg).catchError((error, trace) { - channel.close(); - if (error is SocketException && - (error.message.contains("Failed host lookup") || error.message.contains("Connection timed out"))) { - log.e("连接失败 ${error.message}"); - return; - } - log.e("转发请求失败", error: error, stackTrace: trace); + exceptionCaught(channel, error, trace: trace); }); } + @override + void exceptionCaught(Channel channel, error, {StackTrace? trace}) { + super.exceptionCaught(channel, error, trace: trace); + HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host); + String message = error.toString(); + HttpStatus status = HttpStatus(-1, message); + if (error is HandshakeException) { + status = HttpStatus(-2, 'SSL握手失败'); + } else if (error is ParserException) { + status = HttpStatus(-3, error.message); + } else if (error is SocketException) { + status = HttpStatus(-4, error.message); + } + HttpRequest request = HttpRequest(HttpMethod.connect, hostAndPort?.domain ?? channel.remoteAddress.host) + ..body = message.codeUnits; + request.response = HttpResponse(status)..body = message.codeUnits; + listener?.onRequest(channel, request); + listener?.onResponse(channel, request.response!); + } + @override void channelInactive(Channel channel) { Channel? remoteChannel = channel.getAttribute(channel.id); @@ -81,7 +96,7 @@ class HttpChannelHandler extends ChannelHandler { localRequest(HttpRequest msg, Channel channel) async { //获取配置 if (msg.path() == '/config') { - var response = HttpResponse(msg.protocolVersion, HttpStatus.ok); + var response = HttpResponse(HttpStatus.ok, protocolVersion: msg.protocolVersion); var body = { "requestRewrites": requestRewrites?.toJson(), 'whitelist': HostFilter.whitelist.toJson(), @@ -92,7 +107,7 @@ class HttpChannelHandler extends ChannelHandler { return; } - var response = HttpResponse(msg.protocolVersion, HttpStatus.ok); + var response = HttpResponse(HttpStatus.ok, protocolVersion: msg.protocolVersion); response.body = utf8.encode('pong'); response.headers.set("os", Platform.operatingSystem); response.headers.set("hostname", Platform.isAndroid ? Platform.operatingSystem : Platform.localHostname); @@ -122,7 +137,7 @@ class HttpChannelHandler extends ChannelHandler { void _crtDownload(Channel channel, HttpRequest request) async { const String fileMimeType = 'application/x-x509-ca-cert'; - var response = HttpResponse(request.protocolVersion, HttpStatus.ok); + var response = HttpResponse(HttpStatus.ok); response.headers.set(HttpHeaders.CONTENT_TYPE, fileMimeType); response.headers.set("Content-Disposition", 'inline;filename=ProxyPinCA.crt'); response.headers.set("Connection", 'close'); @@ -169,8 +184,8 @@ class HttpChannelHandler extends ChannelHandler { clientChannel.putAttribute(clientId, proxyChannel); //https代理新建连接请求 if (httpRequest.method == HttpMethod.connect) { - await clientChannel - .write(HttpResponse(httpRequest.protocolVersion, HttpStatus.ok.reason('Connection established'))); + await clientChannel.write( + HttpResponse(HttpStatus.ok.reason('Connection established'), protocolVersion: httpRequest.protocolVersion)); } return proxyChannel; diff --git a/lib/network/http/body_reader.dart b/lib/network/http/body_reader.dart index 9f3683e..f795dee 100644 --- a/lib/network/http/body_reader.dart +++ b/lib/network/http/body_reader.dart @@ -47,7 +47,7 @@ class BodyReader { Result readBody(Uint8List data) { if (_bodyBuffer.length > Codec.maxBodyLength) { _bodyBuffer.clear(); - throw Exception('Body length exceeds ${Codec.maxBodyLength}'); + throw ParserException('Body length exceeds ${Codec.maxBodyLength}'); } _offset = 0; diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index 1aea1f3..9a62feb 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.dart @@ -38,6 +38,18 @@ class HttpConstants { static const int colon = 58; } +class ParserException implements Exception { + final String message; + final String? source; + + ParserException(this.message, [this.source]); + + @override + String toString() { + return 'ParserException{message: $message source: $source}'; + } +} + enum State { readInitial, readHeader, @@ -80,14 +92,9 @@ abstract class HttpCodec implements Codec { //请求行 if (_state == State.readInitial) { init(); - try { - var initialLine = _readInitialLine(data); - message = createMessage(initialLine); - _state = State.readHeader; - } catch (e, cause) { - logger.e("解析请求行失败 [${String.fromCharCodes(data)}]", error: e, stackTrace: cause); - rethrow; - } + var initialLine = _readInitialLine(data); + message = createMessage(initialLine); + _state = State.readHeader; } //请求头 @@ -209,7 +216,7 @@ class HttpResponseCodec extends HttpCodec { @override HttpResponse createMessage(List reqLine) { var httpStatus = HttpStatus(int.parse(reqLine[1]), reqLine[2]); - return HttpResponse(reqLine[0], httpStatus); + return HttpResponse(httpStatus, protocolVersion: reqLine[0]); } @override @@ -243,7 +250,7 @@ class HttpParse { } } if (initialLine.length != 3) { - throw Exception("parseLine error ${String.fromCharCodes(data)}"); + throw ParserException("parseLine error", String.fromCharCodes(data)); } return initialLine; diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 0f929c9..19d66dc 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -156,7 +156,7 @@ class HttpResponse extends HttpMessage { final DateTime responseTime = DateTime.now(); HttpRequest? request; - HttpResponse(String protocolVersion, this.status) : super(protocolVersion); + HttpResponse(this.status, {String protocolVersion = "HTTP/1.1"}) : super(protocolVersion); String costTime() { if (request == null) { @@ -166,7 +166,8 @@ class HttpResponse extends HttpMessage { } factory HttpResponse.fromJson(Map json) { - return HttpResponse(json['protocolVersion'], HttpStatus(json['status']['code'], json['status']['reasonPhrase'])) + return HttpResponse(HttpStatus(json['status']['code'], json['status']['reasonPhrase']), + protocolVersion: json['protocolVersion']) ..headers.addAll(HttpHeaders.fromJson(json['headers'])) ..body = json['body']?.toString().codeUnits; } @@ -292,6 +293,6 @@ class HttpStatus { @override String toString() { - return 'HttpResponseStatus{code: $code, reasonPhrase: $reasonPhrase}'; + return '$code $reasonPhrase'; } } diff --git a/lib/network/network.dart b/lib/network/network.dart index 688110e..447636f 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -88,8 +88,6 @@ class Network { channel.secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data); } catch (error, trace) { if (error is HandshakeException) { - channel.socket.destroy(); - subscription?.pause(); await subscription?.cancel(); } channel.pipeline.exceptionCaught(channel, error, trace: trace); diff --git a/lib/network/util/crts.dart b/lib/network/util/crts.dart index 8aec2af..71d6268 100644 --- a/lib/network/util/crts.dart +++ b/lib/network/util/crts.dart @@ -1,3 +1,19 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import 'dart:core'; import 'dart:io'; import 'dart:math'; diff --git a/lib/network/util/host_filter.dart b/lib/network/util/host_filter.dart index a0b1089..f2d17ac 100644 --- a/lib/network/util/host_filter.dart +++ b/lib/network/util/host_filter.dart @@ -2,6 +2,8 @@ void main() { print(HostFilter.filter("stackoverflow.com")); } +/// @author wanghongen +/// 2023/7/26 class HostFilter { /// 白名单 static final Whites whitelist = Whites(); diff --git a/lib/network/util/logger.dart b/lib/network/util/logger.dart index 40e68d0..6277972 100644 --- a/lib/network/util/logger.dart +++ b/lib/network/util/logger.dart @@ -3,7 +3,7 @@ import 'package:logger/logger.dart'; final logger = Logger( printer: PrettyPrinter( methodCount: 0, - errorMethodCount: 15, + errorMethodCount: 10, lineLength: 120, colors: true, printEmojis: false, diff --git a/lib/network/util/request_rewrite.dart b/lib/network/util/request_rewrite.dart index 13b3469..df3cb12 100644 --- a/lib/network/util/request_rewrite.dart +++ b/lib/network/util/request_rewrite.dart @@ -1,3 +1,6 @@ + +/// @author wanghongen +/// 2023/7/26 class RequestRewrites { bool enabled = true; final List rules = []; diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index 1bf417a..bcc212a 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'package:network_proxy/utils/ip.dart'; import 'package:proxy_manager/proxy_manager.dart'; +/// @author wanghongen +/// 2023/7/26 class SystemProxy { static String? _hardwarePort; diff --git a/lib/network/util/x509.dart b/lib/network/util/x509.dart index 75b5086..1b079ee 100644 --- a/lib/network/util/x509.dart +++ b/lib/network/util/x509.dart @@ -5,6 +5,9 @@ import 'package:basic_utils/basic_utils.dart'; import 'package:pointycastle/asn1/unsupported_object_identifier_exception.dart'; import 'package:pointycastle/pointycastle.dart'; + +/// @author wanghongen +/// 2023/7/26 class X509Generate { static const String BEGIN_CERT = '-----BEGIN CERTIFICATE-----'; static const String END_CERT = '-----END CERTIFICATE-----'; diff --git a/lib/ui/component/json/json_text.dart b/lib/ui/component/json/json_text.dart index 673381f..a0bd820 100644 --- a/lib/ui/component/json/json_text.dart +++ b/lib/ui/component/json/json_text.dart @@ -16,7 +16,7 @@ class JsonText extends StatelessWidget { Widget build(BuildContext context) { var jsnParser = JsnParser(json, colorTheme, indent); var future = - compute((message) => message.getJsonTree(), jsnParser).catchError((error) => [Text(error.toString())]); + compute((message) => message.getJsonTree(), jsnParser).catchError((error) => [Text(error.toString())]); return FutureBuilder( future: future, @@ -30,7 +30,10 @@ class JsonText extends StatelessWidget { } else { widget = SizedBox( width: double.infinity, - height: MediaQuery.of(context).size.height - 160, + height: MediaQuery + .of(context) + .size + .height - 160, child: ListView.builder( physics: const BouncingScrollPhysics(), controller: trackingScroll(), @@ -69,13 +72,15 @@ class JsnParser { JsnParser(this.json, this.colorTheme, this.indent); List getJsonTree() { - List textList; + List textList = []; if (json is Map) { - textList = getMapText(json, prefix: indent); + textList.add(const Text('{')); + textList.addAll(getMapText(json, prefix: indent)); } else if (json is List) { - textList = getArrayText(json); + textList.add(const Text('[')); + textList.addAll(getArrayText(json)); } else { - textList = [Text(json.toString())]; + textList.add(Text(json.toString())); } return textList; } diff --git a/lib/ui/component/utils.dart b/lib/ui/component/utils.dart index e6d6c47..542792d 100644 --- a/lib/ui/component/utils.dart +++ b/lib/ui/component/utils.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/http/http.dart'; -IconData getIcon(HttpResponse? response) { +Icon getIcon(HttpResponse? response) { var map = { ContentType.json: Icons.data_object, ContentType.html: Icons.html, @@ -13,11 +13,16 @@ IconData getIcon(HttpResponse? response) { ContentType.css: Icons.css, ContentType.font: Icons.font_download, }; + if (response == null) { - return Icons.question_mark; + return const Icon(Icons.question_mark, size: 16, color: Colors.green); } + if (response.status.code < 0) { + return const Icon(Icons.error, size: 16, color: Colors.red); + } + var contentType = response.contentType; - return map[contentType] ?? Icons.http; + return Icon(map[contentType] ?? Icons.http, size: 16, color: Colors.green); } String copyRequest(HttpRequest request, HttpResponse? response) { diff --git a/lib/ui/content/panel.dart b/lib/ui/content/panel.dart index d81bd4a..4c2a576 100644 --- a/lib/ui/content/panel.dart +++ b/lib/ui/content/panel.dart @@ -108,7 +108,7 @@ class NetworkTabState extends State with SingleTickerProvi const SizedBox(height: 20), rowWidget("Request Method", request.method.name), const SizedBox(height: 20), - rowWidget("Status Code", response?.status.code.toString()), + rowWidget("Status Code", response?.status.toString()), const SizedBox(height: 20), rowWidget("Remote Address", response?.remoteAddress), const SizedBox(height: 20), @@ -142,7 +142,7 @@ class NetworkTabState extends State with SingleTickerProvi var scrollController = ScrollController(); return ListView(controller: scrollController, children: [ - rowWidget("StatusCode", widget.response.get()?.status.code.toString()), + rowWidget("StatusCode", widget.response.get()?.status.toString()), ...message(widget.response.get(), "Response", scrollController) ]); } diff --git a/lib/ui/desktop/left/path.dart b/lib/ui/desktop/left/path.dart index c744628..d5bca14 100644 --- a/lib/ui/desktop/left/path.dart +++ b/lib/ui/desktop/left/path.dart @@ -54,7 +54,7 @@ class _PathRowState extends State { onSecondaryLongPressDown: menu, child: ListTile( minLeadingWidth: 25, - leading: Icon(getIcon(widget.response.get()), size: 16, color: widget.color), + leading: getIcon(widget.response.get()), title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 1), subtitle: Text( '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''} ', diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 45a3c8f..5cb35b8 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -108,7 +108,7 @@ class _SslState extends State { ]), alignment: Alignment.center, children: [ - Text(" 安装证书到本系统,${Platform.isMacOS ? "“安装完选择“始终信任此证书”" : "选择“受信任的根证书颁发机构”"}"), + Text(" 安装证书到本系统,${Platform.isMacOS ? "“安装完选择“始终信任此证书”。 如安装打开失败,请下载证书拖拽到系统根证书里" : "选择“受信任的根证书颁发机构”"}"), const SizedBox(height: 10), FilledButton(onPressed: _installCert, child: const Text("安装证书")), const SizedBox(height: 10), diff --git a/lib/ui/mobile/request/request.dart b/lib/ui/mobile/request/request.dart index 17e5fdb..0a096d6 100644 --- a/lib/ui/mobile/request/request.dart +++ b/lib/ui/mobile/request/request.dart @@ -49,8 +49,9 @@ class RequestRowState extends State { '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''}'; return ListTile( + textColor: (response?.status.code ?? 0) < 0 ? Colors.red : null, visualDensity: const VisualDensity(vertical: -4), - leading: widget.displayDomain ? null : Icon(getIcon(response), size: 16, color: Colors.green), + leading: widget.displayDomain ? null : getIcon(response), title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 1, style: const TextStyle(fontSize: 14)), subtitle: Text(subTitle, maxLines: 1, style: const TextStyle(fontSize: 12)), trailing: const Icon(Icons.chevron_right),