You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Dart编写的本地代理服务器中处理CONNECT方法(HTTP隧道)

HTTPS代理CONNECT方法处理及图片拦截方案

你的核心问题在于HTTPS的CONNECT请求需要先完成隧道握手,且必须通过中间人解密才能获取明文响应,现有代码未完成这两个关键步骤,导致无法处理HTTPS流量。以下是针对性的解决方案和代码修改:

关键原理说明

  1. CONNECT请求处理流程:客户端发送CONNECT请求是为了建立加密隧道,代理必须先返回HTTP 200 OK确认隧道建立,再进行双向数据转发。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 10:25:13