【问题标题】:Java Proxy Tunnelling HTTPSJava 代理隧道 HTTPS
【发布时间】:2015-07-12 07:36:20
【问题描述】:

我正在用 Java 构建代理服务器;服务器正确处理 HTTP 流量,但无法正确隧道 HTTPS 流量。根据IETF draft,进行以下过程:

1) 我成功收到了来自浏览器的 CONNECT 消息,该消息包含要以纯文本形式连接的主机。

2)我解析此消息以提取主机详细信息并成功建立与远程主机的连接。

3)然后我将 HTTP/1.0 200 Connection established 消息发送回客户端,然后立即尝试使用以下代码从连接的任一端中继流量。

问题是在我返回上述 200 消息而不是发送 HTTPS 数据后,浏览器似乎进入了无限循环并继续向代理发送更多 CONNECT 消息。这是我用来在客户端和主机之间中继数据的代码:

 public static void stageTunnelledConnection(Socket clientSocket,Socket targetHostSocket) throws IOException{

     //set client socket read timeout to 2 seconds. The targethost connection will ALREADY have been
     //set to this value at the time this method is called.
     clientSocket.setSoTimeout(2000);

     InputStream[] socketInputStreamsArr = new InputStream[]{clientSocket.getInputStream(),targetHostSocket.getInputStream()};

     OutputStream[] socketOutputStreamsArr = new OutputStream[]{clientSocket.getOutputStream(),targetHostSocket.getOutputStream()};

     //holds current socket index to read from, this will be switched between the two sockets
     //at 0 and 1 indexes of the sockets array respectively.
     int curReadIndex = 0;
     //this will be set according to the "curReadIndex" value and will typically be
     //the logical NOT of that value; that is, where curReadIndex equals 0 the curWriteIndex to will equal 1 and visa versa.
     int curWriteIndex = 1; 
     while(true){

        try{

         //attempt to read from socket stream at current index and write
         //to the socket at the alternate index.
         byte[] dataBuff = new byte[2048];

         int bytesRead = 0;
         //we read into the dataBuff this operation will block for
         //a max of 2 seconds should no data be available to read
         while((bytesRead = socketInputStreamsArr[curReadIndex].read(dataBuff)) != -1){

            //ByteArrayInputStream bais = new ByteArrayInputStream(dataBuff);

            //BufferedReader br = new BufferedReader(new InputStreamReader(bais));

            //System.out.println(br.readLine());

            //write the buffer to the outputsteam at the index 
            //computed and stored to the "curWriteIndex" var above.
            socketOutputStreamsArr[curWriteIndex].write(dataBuff);
            socketOutputStreamsArr[curWriteIndex].flush();

             //System.out.println("Bytes read=".concat(String.valueOf(dataBuff)));
            //System.out.println("wroteBytes: "+bytesRead);

         }



        }

        catch(SocketTimeoutException ste){

         //we switch read/write index each time a read timeout error occurs. I.e 
         //were there is no further data to read from the socket at the currrent read index.
         if(ste.getMessage().contains("Read")){
         //System.out.println("Switching connection.");
         curReadIndex = (curReadIndex == 0) ? 1 : 0;
         curWriteIndex = (curReadIndex == 0) ? 1 : 0;
         }
         else{

            //clientSocket.close();
            //targetHostSocket.close();
            ste.printStackTrace();
         }

        }
        catch(SocketException ioe){

            //if an input/output exception occurs we must close both sockets
             clientSocket.close();
             targetHostSocket.close();

             ioe.printStackTrace();

        }



     }


 }

**重要提示:** 由于被隧道传输的实际数据是加密的,因此对代理不透明,因此代理必须随时准备从任一侧读取/写入。为了在单个线程中促进此过程,我在两侧设置了一个相对较短的 Socket Timeout(2 秒)并进入一个循环,该循环在每次迭代时交替读取和写入哪一侧,其中没有可用的数据 SocketTimeoutException发生被捕获时,此时切换读取的一侧,循环继续执行。这种旨在从单个线程中读取两个套接字的策略是否会导致问题?

【问题讨论】:

  • 您确定与 CONNECT 的握手和响应(您没有显示)是正确的吗?如果它不正确,它可能会解释浏览器再次尝试使用新的 CONNECT。您可以添加客户端和代理之间连接的数据包捕获以显示实际情况(使用 cloudshark.org)。

标签: java sockets proxy


【解决方案1】:
socketOutputStreamsArr[curWriteIndex].write(dataBuff);

应该是这样的

socketOutputStreamsArr[curWriteIndex].write(dataBuff, 0, bytesRead);

为了在单个线程中促进此过程,我在两侧设置了一个相对较短的 Socket Timeout(2 秒)并进入一个循环,该循环在每次迭代时交替读取和写入哪一侧,其中没有数据可用的 SocketTimeoutException 发生并被捕获,此时切换读取端并继续执行循环。这种旨在从单个线程中读取两个套接字的策略是否会导致问题?

是的。您应该使用两个线程或非阻塞 NIO。否则,您只会增加很多不必要的延迟。

【讨论】:

  • 我认为方法:write(dataBuff) 等价于:write(dataBuff,0,dataBuff.length) 为了简单,我使用前者。
  • 除非 OutputStream 将写入空字节,其中 dataBuff 包含少于 2048 个字节。我将更改代码以使用您所描述的重载 write 方法并在此处报告。
  • 哈哈它的工作原理!真的非常感谢!!谁会想到这么简单的改变(即只需要使用重载的 write() 方法。结果证明我用一个线程从两个套接字读取的策略也很有效。就像我之前暗示的那样,错误一定已经发生了由于整个缓冲区被写入输出流,即使它包含少于规定的 2048 字节。我原以为输出流实现足够智能,可以忽略提供的数组中的空索引。
  • @GilesThompson 这不是等价的,也不是更简单。这是错误的。这不是 OutputStream 实现“足够智能以忽略空字节”的问题。多余的字节不一定是空的。它们不受先前迭代的干扰。这是您的代码告诉它写入整个缓冲区的问题,它确实如此。这就是合同中的内容,也是它必须表现的方式。
  • 您当然是正确的,我刚刚查看了与这两种方法有关的文档。非常感谢您的及时和有益的回答。
猜你喜欢
  • 2014-08-04
  • 1970-01-01
  • 2013-05-08
  • 1970-01-01
  • 1970-01-01
  • 2011-05-22
  • 1970-01-01
  • 2018-04-03
相关资源
最近更新 更多