我正在尝试使用 Java (Android) 连接到带有 SSL 套接字的服务器。请注意,这不是 HTTP 数据。这是混合了文本和二进制数据的专有协议(protocol)。
我想通过 HTTP 代理中继该 SSL 连接,但我遇到了很多问题。现在我使用的场景以及我的浏览器似乎与 squid 代理一起使用的场景如下
[客户端]->[http连接]->[代理]->[ssl连接]->[服务器]
这适用于浏览器,因为在代理建立 ssl 连接后,会立即进行 TLS 协商。但是我的代码似乎没有这样做。
final TrustManager[] trustManager = new TrustManager[] { new MyX509TrustManager() };
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManager, null);
SSLSocketFactory factory = context.getSocketFactory();
Socket s = factory.createSocket(new Socket(proxy_ip, 3128), hostName, port, true);
我遇到的问题是 createSocket 永远不会返回。通过代理机器的 wireshark 转储,我可以看到代理和服务器之间发生了一次 tcp 握手。通过 Web session 的转储,我可以看到客户端通常会在此时启动 SSL 握手,这在我的场景中不会发生。
这不是信任管理器的问题,因为证书永远不会返回给我,也永远不会被验证。
编辑:
经过讨论,这是我尝试运行的代码的更完整版本。上面这个版本使用简单的 (new Socket(...)) 作为参数是我后来尝试过的。
我尝试调试的代码的原始版本抛出错误
java.net.ConnectException:无法连接到/192.168.1.100(端口 443):连接失败:ETIMEDOUT(连接超时)
顺序如下(再次简化):
final Socket proxySocket = new Socket();
proxySocket.connect(proxyAddress, 2000); // 2 seconds as the connection timeout for connecting to the proxy server
[Start a thread and write to outputStream=socket.getOutputStream()]
final String proxyRequest = String.format("CONNECT %s:%d HTTP/1.1\r\nProxy-Connection: keep-alive\r\nConnection: keep-alive\r\nHost: %s:%d\r\n\r\n", hostName, port, hostName, port);
outputStream.close(); // Closing or not doesn't change anything
[Stop using that thread and let it exit by reaching the end of its main function]
然后使用以下代码读取响应:
final InputStreamReader isr = new InputStreamReader(proxySocket.getInputStream());
final BufferedReader br = new BufferedReader(isr);
final String statusLine = br.readLine();
boolean proxyConnectSuccess = false;
// readLine consumed the CRLF
final Pattern statusLinePattern = Pattern.compile("^HTTP/\\d+\\.\\d+ (\\d\\d\\d) .*");
final Matcher statusLineMatcher = statusLinePattern.matcher(statusLine);
if (statusLineMatcher.matches())
{
final String statusCode = statusLineMatcher.group(1);
if (null != statusCode && 0 < statusCode.length() && '2' == statusCode.charAt(0))
{
proxyConnectSuccess = true;
}
}
// Consume rest of proxy response
String line;
while ( "".equals((line = br.readLine())) == false )
{
}
我可以说此代码有效,因为它在没有 SSL 的情况下也能正常工作。此处创建的套接字 proxySocket
是传递给 createSocket 函数的套接字,而不是像我的原始示例中那样即时创建一个新套接字。
最佳答案
java.net.Proxy
或 https.proxyHost/proxyPort
属性,仅支持通过 HttpURLConnection 进行 HTTP 代理,
不通过 套接字。
要使它适用于您自己的 SSLSocket
,您只需创建一个纯文本套接字,在其上发出 HTTP CONNECT
命令,检查响应为 200,然后将其包装在 SSLSocket.
EDIT 当发送 CONNECT 命令时,当然不能关闭套接字;并且在阅读它的回复时你不能使用 BufferedReader,
否则你会丢失数据;要么手动阅读该行,要么使用 DataInputStream.readLine(),
尽管它已被弃用。您还需要完全遵循 RFC 2616。
关于java - 如何通过 HTTP 代理连接 SSL 套接字?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27979752/