【发布时间】: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)。