似乎Sun Java中内置的HttpsUrlConnection工具无法以服务器友好的方式处理具有客户端证书方案的大型HTTP PUT(即,不会使服务器SSL重新协商缓冲区溢出).
我检查了curl正在做什么来查看"服务器友好意味着什么",结果发现有一个名为"Expect"的HTTP 1.1标头,其卷曲发送值为"100-continue"(参见规范http://www.w3) .org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20).这个标题基本上说"我有一个巨大的有效载荷,但在我发送它之前请告诉我你是否可以处理它".这使端点有时间在发送有效负载之前重新协商客户端证书.
在Sun HttpUrlConnection实现中,似乎不允许此标头,并且实际上是在受限标头列表中; 意思是即使您使用HttpUrlConnection.setRequestProperty方法设置它,标头也不会实际发送到服务器.您可以使用系统属性sun.net.http.allowRestrictedHeaders覆盖受限制的标头,但是客户端只是因为套接字异常而崩溃,因为Sun实现不知道如何处理这部分协议.
有趣的是,似乎Java的OpenJDK实现确实支持这个头.此外,Apache HTTP Client库支持此标头(http://hc.apache.org/); 我已经使用Apache HTTP客户端库实现了一个测试程序,它可以使用客户端证书和Expect头成功执行大文件的HTTP PUT请求.
总结一下,解决方案是:
将Apache SSLRenegBufferSize指令设置为一个巨大的数字(如64MB).默认值为128K.此解决方案可能会产生拒绝服务风险
配置始终需要客户端证书的主机,而不是只有少数目录需要它的主机.这样可以避免重新谈判.这在我的场景中不是一个好的选择,因为大多数用户都是匿名或用户名/密码验证.只有一个上传目录可用于编程上传文件.我们必须为这个目录创建一个具有自己的SSL证书的新虚拟主机.
使用支持HTTP 1.1 Expect标头的客户端.不幸的是,Sun Java不支持开箱即用.必须使用第三方(如Apache HTTP Component Client库)或使用Java套接字API滚动您自己的解决方案.
通过最初发出不具有大负载的HTTP请求,但导致重新协商发生,然后重用HTTP PUT的连接来利用HTTP 1.1持久连接(使用keep-alive进行流水线操作).理论上,客户端应该能够在上载目录中发出HTTP HEAD或OPTIONS,然后重用相同的连接来执行PUT.为了使其工作,持久连接池可能只需要包含一个连接,以避免"启动"一个连接,然后为PUT发出另一个连接.但是,似乎HttpUrlConnection类不会保留/重用涉及客户端证书或SSL的持久连接,因为我无法使此解决方案起作用.请参阅(HttpsUrlConnection和keep-alive).