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

基于Java实现TCP代理时SSL/HTTPS连接报错问题求助

嘿,这个问题我太熟悉了!之前帮朋友做Java代理的时候也踩过一模一样的坑,核心原因是你现在的代码只处理了普通HTTP请求,没正确适配HTTPS的CONNECT隧道模式,咱们一步步来搞定:

为什么会出现SSL_ERROR_RX_RECORD_TOO_LONG?

简单说,浏览器访问HTTPS网站时,先给代理发了个CONNECT请求要建隧道,但你的代理没识别这个请求,反而把后续浏览器发的SSL握手数据当成HTTP请求来解析,返回了HTTP格式的响应。浏览器收到这堆不符合SSL规范的数据,自然就报这个错了。

解决步骤:实现CONNECT请求处理

HTTPS代理和HTTP代理的逻辑完全不同,核心是先建立隧道再透明转发,具体要做这几件事:

1. 识别CONNECT请求

当客户端(浏览器)发来请求时,先读第一行,判断是不是以CONNECT开头的。比如请求格式是:

CONNECT example.com:443 HTTP/1.1
Host: example.com:443
...

2. 建立到目标服务器的连接

从CONNECT请求里解析出目标主机和端口(比如上面的example.com和443),然后新建一个Socket连接到这个目标地址。

3. 返回隧道建立成功的响应

给客户端返回HTTP/1.1 200 Connection Established\r\n\r\n,注意必须有两个换行(\r\n\r\n)来结束HTTP响应头,否则浏览器会一直等。

4. 双向转发二进制数据

隧道打通后,代理就变成“透明管道”:

  • 把客户端输入流的所有数据原封不动转发到目标服务器的输出流
  • 把目标服务器输入流的所有数据原封不动转发到客户端的输出流
    这里绝对不能解析或修改数据,因为后续都是SSL加密的通信,代理只负责转发就行。

给你一个简化的代码示例

下面是核心逻辑的代码片段,你可以参考整合到你的现有代码里:

public class ProxyHandler implements Runnable {
    private final Socket clientSocket;

    public ProxyHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try (BufferedReader requestReader = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
             OutputStream clientOut = clientSocket.getOutputStream()) {

            // 读取请求首行
            String requestLine = requestReader.readLine();
            if (requestLine == null) return;

            if (requestLine.startsWith("CONNECT")) {
                // 处理HTTPS隧道请求
                processConnectRequest(requestLine, clientOut);
            } else {
                // 处理普通HTTP请求,用你原来的逻辑
                processHttpRequest(requestLine, requestReader, clientOut);
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void processConnectRequest(String requestLine, OutputStream clientOut) throws IOException, InterruptedException {
        // 解析目标主机和端口
        String target = requestLine.split(" ")[1];
        String[] hostPortPair = target.split(":");
        String targetHost = hostPortPair[0];
        int targetPort = hostPortPair.length > 1 ? Integer.parseInt(hostPortPair[1]) : 443;

        // 连接目标服务器
        try (Socket targetSocket = new Socket(targetHost, targetPort)) {
            // 告诉浏览器隧道已建立
            clientOut.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
            clientOut.flush();

            // 启动双向转发线程
            Thread clientToTarget = new Thread(() -> forwardStream(clientSocket.getInputStream(), targetSocket.getOutputStream()));
            Thread targetToClient = new Thread(() -> forwardStream(targetSocket.getInputStream(), clientSocket.getOutputStream()));
            clientToTarget.start();
            targetToClient.start();

            // 等待转发结束
            clientToTarget.join();
            targetToClient.join();
        } catch (IOException e) {
            // 连接失败,返回503错误
            clientOut.write("HTTP/1.1 503 Service Unavailable\r\n\r\n".getBytes());
            clientOut.flush();
        }
    }

    // 通用流转发方法
    private void forwardStream(InputStream in, OutputStream out) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        try {
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
                out.flush();
            }
        } catch (IOException ignored) {
            // 连接断开时正常退出
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 你的原有HTTP请求处理方法
    private void processHttpRequest(String requestLine, BufferedReader requestReader, OutputStream clientOut) throws IOException {
        // 这里放你原来处理HTTP请求的代码逻辑
    }
}

额外注意事项

  • 不要在CONNECT之后的转发阶段解析数据,哪怕你好奇也不行,SSL握手对数据完整性要求极高,一点点修改都会导致握手失败。
  • 记得处理异常情况,比如连接目标服务器失败时要返回正确的HTTP错误响应,不然浏览器会一直卡着。
  • 流转发用固定大小的字节数组(比如4096)效率更高,不要逐字节读。

内容的提问来源于stack exchange,提问作者reschiram

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:13:35