如何在Dart编写的本地代理服务器中处理CONNECT方法(HTTP隧道)
HTTPS代理CONNECT方法处理及图片拦截方案
你的核心问题在于HTTPS的CONNECT请求需要先完成隧道握手,且必须通过中间人解密才能获取明文响应,现有代码未完成这两个关键步骤,导致无法处理HTTPS流量。以下是针对性的解决方案和代码修改:
关键原理说明
- CONNECT请求处理流程:客户端发送
CONNECT请求是为了建立加密隧道,代理必须先返回HTTP 200 OK确认隧道建立,再进行双向数据转发。 - HTTPS解密:要拦截HTTPS响应,代理需作为"中间人",用自签名CA证书与客户端建立SSL连接,同时与目标服务器建立SSL连接,以此解密获取明文流量。
完整代码实现
1. 修正CONNECT请求处理逻辑
var proxyServer = await HttpServer.bind(addr, port); proxyServer.listen((HttpRequest request) async { if (request.method == 'CONNECT') { final host = request.headers.host!; final port = int.tryParse(request.uri.path.split(':').last) ?? 443; try { // 1. 连接目标服务器 final targetSocket = await Socket.connect(host, port, timeout: const Duration(seconds: 5)); // 2. 返回200确认隧道建立 request.response.statusCode = HttpStatus.ok; await request.response.close(); // 3. 获取客户端Socket final clientSocket = await request.response.detachSocket(); // 4. 初始化SSL上下文(加载自签名CA证书) final securityContext = SecurityContext(); // 从assets加载证书和私钥(需提前生成并配置) final certBytes = await rootBundle.load('assets/proxy.crt'); final keyBytes = await rootBundle.load('assets/proxy.key'); securityContext.useCertificateChainBytes(certBytes.buffer.asUint8List()); securityContext.usePrivateKeyBytes(keyBytes.buffer.asUint8List()); // 5. 建立双向SSL连接(客户端<->代理<->目标服务器) final clientSsl = await SecureSocket.secure(clientSocket, serverName: host, context: securityContext); final targetSsl = await SecureSocket.secure(targetSocket, host: host); // 6. 拦截并转发流量 _interceptAndForward(clientSsl, targetSsl); } catch (e) { LogService.log('CONNECT失败: $e'); request.response.statusCode = HttpStatus.badGateway; await request.response.close(); } } else { // 原有HTTP请求处理逻辑 ... } });
2. 流量拦截与图片验证函数
void _interceptAndForward(SecureSocket clientSsl, SecureSocket targetSsl) { // 转发客户端请求到目标服务器 clientSsl.listen( (data) => targetSsl.add(data), onDone: () => targetSsl.close(), onError: (_) => targetSsl.close(), ); // 拦截目标服务器响应 final responseBuffer = BytesBuilder(); targetSsl.listen( (data) { responseBuffer.add(data); // 检测响应头是否完整(HTTP响应以\r\n\r\n分隔头和体) final responseStr = utf8.decode(responseBuffer.toBytes(), allowMalformed: true); if (responseStr.contains('\r\n\r\n')) { final [headers, ...bodyParts] = responseStr.split('\r\n\r\n'); // 判断是否为图片响应 if (headers.contains('Content-Type: image/')) { final bodyBytes = responseBuffer.toBytes().skip(headers.length + 4).toList(); // 验证图片有效性 if (!_isValidImage(bodyBytes)) { // 图片无效,返回拦截响应并关闭连接 clientSsl.add(utf8.encode('HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n')); clientSsl.close(); targetSsl.close(); return; } } // 验证通过,转发已缓存的响应数据 clientSsl.add(responseBuffer.toBytes()); responseBuffer.clear(); } }, onDone: () { // 转发剩余响应体 if (responseBuffer.isNotEmpty) clientSsl.add(responseBuffer.toBytes()); clientSsl.close(); }, onError: (_) { clientSsl.close(); targetSsl.close(); }, ); } // 图片有效性验证(检查文件头) bool _isValidImage(List<int> bytes) { if (bytes.length < 4) return false; // JPEG头:FF D8 FF if (bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF) return true; // PNG头:89 50 4E 47 if (bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47) return true; // 可扩展支持其他图片格式(WebP/GIF等) return false; }
必要配置步骤
1. 生成自签名CA证书
用OpenSSL生成证书和私钥:
# 生成私钥 openssl genrsa -out proxy.key 2048 # 生成证书签名请求 openssl req -new -key proxy.key -out proxy.csr # 生成自签名证书(有效期365天) openssl x509 -req -days 365 -in proxy.csr -signkey proxy.key -out proxy.crt
将proxy.crt和proxy.key放入Flutter项目assets目录,并在pubspec.yaml中声明:
assets: - assets/proxy.crt - assets/proxy.key
2. 安卓系统配置
- 添加网络权限到
AndroidManifest.xml:<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - 配置信任用户证书(安卓7+默认不信任):
在res/xml/network_security_config.xml中添加:
并在<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config> <trust-anchors> <certificates src="system" /> <certificates src="user" /> </trust-anchors> </base-config> </network-security-config>AndroidManifest.xml的application标签中引用:<application ... android:networkSecurityConfig="@xml/network_security_config"> - 安装证书到安卓设备:将
proxy.crt发送到设备,通过系统设置"安全->加密与凭据->从存储安装"添加并信任。
内容的提问来源于stack exchange,提问作者CIF
相关产品推荐
相关产品推荐

