HttpsUrlConnection并保持活着

时间:2012-03-30 12:51:48

标签: java https keep-alive

我在我当前的项目中使用com.sun.net.httpserver.HttpsServer处理客户端身份验证等。目前它只打印出客户端地址/端口,以便我可以检查是否有一个TCP连接用于多个请求(keep-alive)或者是否为每个请求建立了新连接(因此每次都进行新的SSL握手)。当我使用FireFox对服务器发出多个请求时,我可以看到keep-alive正在运行。因此,服务器部分可以正常使用GET和POST请求。

如果我使用HttpURLConnection向服务器发出请求(在这种情况下使用 SSL)keep-alive也可以:只为多个连续建立一个连接开始请求。

但是,如果我使用HttpsURLConnection(使用完全相同的代码,但使用 SSL),那么keep-alive将无法使用。因此,对于每个请求,都会建立新的连接,但我使用相同的SSLContext(和SSLSocketFactory):

// URL myUrl = ...
// SSLContext mySsl = ...
HttpsURLConnection conn = (HttpsURLConnection) myUrl.openConnection();
conn.setUseCaches(false);
conn.setSSLSocketFactory(mySsl.getSocketFactory());

conn.setRequestMethod("POST");
// send Data
// receive Data

如何强制HttpsURLConnection使用keep-alive,因为许多请求会导致许多SSL握手,这是一个真正的性能问题?

更新(2012-04-02): 我没有每次都调用mySsl.getSocketFactory(),而是尝试缓存SSLSocketFactory。但没有改变。问题依然存在。

8 个答案:

答案 0 :(得分:17)

我遇到了同样的问题,经过一些深入的调试后终于找到了解决方案。

Http(s)UrlConnection默认处理Keep-Alive,但套接字必须处于非常特定的状态才能重复使用。

这些是:

  • 必须完全消耗输入流。您必须在输入流上调用read,直到它返回-1并关闭它。
  • 底层套接字上的设置必须使用完全相同的对象。
  • 完成后,你应该在Http(s)URLConnection上调用disconnect(是的,这是违反直觉的)。

在上面的代码中,问题是:

conn.setSSLSocketFactory(mySsl.getSocketFactory());

在初始化期间将getSocketFactory()的结果保存到静态变量,然后将其传递给conn.setSSLSocketFactory应该允许重用套接字。

答案 1 :(得分:5)

我无法使用HttpsUrlConnection。但Apache的HTTP客户端可以很好地处理SSL连接的保持活动状态。

答案 2 :(得分:2)

对于服务调用或从浏览器获取许多资源,SSL连接建立非常昂贵。

Java Http(s)UrlConnection处理HTTP(S)Keep-Alive by default

我还没有找到default SSLSocketFactory的源代码,可能还有实现了keep-alive机制。作为确认,请在SSLSocketFactory中使用自定义信任存储禁用您自己的javax.net.ssl.trustStore测试实施,以便接受您的自签名证书。

根据使用ServerImpl的OpenJDK 7 ServerConfig实现,您使用的HttpsServer会发出保持活动状态,默认情况下会超时5分钟。

我建议您将属性sun.net.httpserver.debug设置为true服务器端以获取详细信息。

请注意,您的代码不会添加禁用保持活动机制的标头Connection: close

答案 3 :(得分:0)

据我所知HTTP/1.1HTTPS协议,还记录了hereKeep-Alive不是端到端标头但跳到跳标头。由于SSL涉及每个新连接的“不同跃点”(例如CA和服务器)之间的多个握手步骤,我认为Keep-Alive可能不适用于SSL上下文。因此,使用HTTPS连接会忽略that can be why Keep-Alive标头。根据此this question,您可能需要确保一个 HTTP连接实例用于保证Keep-Alive观察。此外,在这个问题中,似乎Apache HTTPClient是一个更好的解决方案。

答案 4 :(得分:0)

尝试添加以下代码:

con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Keep-Alive", "header");

答案 5 :(得分:0)

我们可能会设置一个Apache Web服务器,添加以下指令以查看Apache的access.log是否具有http客户端的保持连接。

LogFormat "%k %v %h %l %u %t \"%r\" %>s %b" common
CustomLog "logs/access.log" common 

http://httpd.apache.org/docs/current/mod/mod_log_config.html

"%K"此连接上处理的keepalive请求数。如果使用KeepAlive很有意思,那么,例如,' 1'表示初始请求之后的第一个keepalive请求,' 2'第二个......等等;否则这始终为0(表示初始请求)。

答案 6 :(得分:0)

我遇到了同样的问题,比尔希利是对的。 我用几个https库测试了我的示例代码。 HttpsURLConnection和OKHTTP是完全相同的行为。 当会话恢复时,Volley有点不同,但几乎相同的行为。 我希望这会有所帮助。

public class SampleActivity extends Activity implements OnClickListener {

    // Keep default context and factory
    private SSLContext mDefaultSslContext;
    private SSLSocketFactory mDefaultSslFactory;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        findViewById(R.id.button_id).setOnClickListener(this);

        try {
            // Initialize context and factory
            mDefaultSslContext = SSLContext.getInstance("TLS");
            mDefaultSslContext.init(null, null, null);
            mDefaultSslFactory = mDefaultSslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            Log.e(TAG, e.getMessage(), e);
        }

    }

    @Override
    public void onClick(View v){
        SSLContext sslcontext;
        SSLSocketFactory sslfactory;

        try {
            // If using this factory, enable Keep-Alive
            sslfactory = mDefaultSslFactory;

            // If using this factory, enable session resumption (abbreviated handshake)
            sslfactory = mDefaultSslContext.getSocketFactory();

            // If using this factory, enable full handshake each time
            sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(null, null, null);
            sslfactory = sslcontext.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            Log.e(TAG, e.getMessage(), e);
        }

        URL url = new URL("https://example.com");
        HttpsURLConnection = conn = (HttpsURLConnection) url.openConnection();
        conn.setSSLSocketFactory(sslfactory);
        conn.connect();
    }
}

更新

共享SSLSocketFactory可启用keep-alive。共享SSLContext并获取每个请求的facotry启用会话恢复。我不知道TLS堆栈是如何工作的,但只是确认了一些移动设备的这些连接行为。

如果要在多个类之间启用keep-alive,则应使用singleton模式共享SSLSocketFactory的实例。

如果要启用会话恢复,请确保服务器端的会话超时设置足够长,例如SSLSessionCacheTimeout(apache),ssl_session_timeout(nginx)。

答案 7 :(得分:0)

除了@Bill Healey回答之外,还必须将HostnameVerifier声明为静态。 我尝试了几种模式,无论是否关闭输入流和连接,它们对我来说都没有改变。唯一重要的是提到的属性的静态声明。

/**
SSLSocketFactory and HostnameVerifier must be declared static in order to be able to use keep-alive option
*/
private static SSLSocketFactory factory = null;
private static HostnameVerifier hostnameVerifier = new HostnameVerifier() {
    @Override
    public boolean verify(String s, SSLSession sslSession) {
        return true;
    }
};
public static void prepareForCustomTrustIfNeeded(HttpsURLConnection connection) {
        try {
            if(factory == null) {
                SSLContext sslc = SSLContext.getInstance("TLS");
                sslc.init(null, customTrustedCerts, new SecureRandom());
                factory = sslc.getSocketFactory();
            }
            connection.setSSLSocketFactory(factory);
            connection.setHostnameVerifier(hostnameVerifier);
        } catch (Exception e) {
            e.printStackTrace();
        }
}