简体   繁体   English

HttpsUrlConnection 和保持活动状态

[英]HttpsUrlConnection and keep-alive

I am using com.sun.net.httpserver.HttpsServer in my current project which deals with client-authentification etc.. Currently it only prints out the clients address/port, so that I can check if one TCP-connection is used for multiple requests ( keep-alive ) or if a new connection is established for every request (and thus a new SSL-handshake is made every time).我在当前项目中使用com.sun.net.httpserver.HttpsServer处理客户端身份验证等。目前它只打印出客户端地址/端口,以便我可以检查一个 TCP 连接是否用于多个请求( keep-alive )或者是否为每个请求建立了新连接(因此每次都进行新的 SSL 握手)。 When I use FireFox to make multiple request against the server I can see that keep-alive is working.当我使用 FireFox 向服务器发出多个请求时,我可以看到 keep-alive 正在工作。 So the server part works fine with GET and POST-requests.所以服务器部分可以很好地处理 GET 和 POST 请求。

If I use HttpURLConnection to make a request against the Server (in this case using no SSL) keep-alive works, too: Only one connection is established for multiple sequentially started requests.如果我使用HttpURLConnection使用SSL在这种情况下),以使对服务器的请求keep-alive的作品,太:只有一个连接,建立了多个顺序开始请求。

But if I use HttpsURLConnection (using exactly the same code, but using SSL) then keep-alive is not working anymore.但是,如果我使用HttpsURLConnection (使用完全相同的代码,但使用SSL),那么keep-alive将不再起作用。 So for each request a new connection is established, although I am using the same SSLContext (and SSLSocketFactory ):因此,对于每个请求,都会建立一个新连接,尽管我使用的是相同的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

How do I force HttpsURLConnection to use keep-alive because many requests will lead to many SSL-handshakes which is a real performance issue?我如何强制HttpsURLConnection使用keep-alive因为许多请求会导致许多 SSL 握手,这是一个真正的性能问题?

Update (2012-04-02): Instead of calling mySsl.getSocketFactory() each time, I tried to cache the SSLSocketFactory .更新(2012-04-02):我没有每次都调用mySsl.getSocketFactory() ,而是尝试缓存SSLSocketFactory But nothing changed.但什么都没有改变。 The problem still exists.问题仍然存在。

I ran into this exact same problem and finally have a solution after some in-depth debugging.我遇到了这个完全相同的问题,经过一些深入的调试后终于有了解决方案。

Http(s)UrlConnection does handle Keep-Alive by default but sockets must be in a very specific condition in order to be reused.默认情况下,Http(s)UrlConnection 确实处理 Keep-Alive,但套接字必须处于非常特定的状态才能重用。

These are:这些是:

  • Input streams must be fully consumed.必须完全消耗输入流。 You must call read on the input stream until it returns -1 and also close it.您必须在输入流上调用 read 直到它返回 -1 并关闭它。
  • Settings on the underlying socket must use the exact same objects.底层套接字上的设置必须使用完全相同的对象。
  • You should call disconnect (yes this is counter-intuitive) on the Http(s)URLConnection when done with it.完成后,您应该在 Http(s)URLConnection 上调用 disconnect (是的,这是违反直觉的)。

In the above code, the problem is:在上面的代码中,问题是:

conn.setSSLSocketFactory(mySsl.getSocketFactory());

Saving the result of getSocketFactory() to a static variable during initialization and then passing that in to conn.setSSLSocketFactory should allow the socket to be reused.在初始化期间将 getSocketFactory() 的结果保存到静态变量,然后将其传递给 conn.setSSLSocketFactory 应该允许重新使用套接字。

I couldn't get it working with HttpsUrlConnection .我无法让它与HttpsUrlConnection一起工作。 But Apache's HTTP client handles keep-alive with SSL connections very well.但是 Apache 的 HTTP 客户端可以很好地处理 SSL 连接的保持连接。

SSL connection establishment is really expensive either for service calls or when getting many resources from a browser. SSL 连接的建立对于服务调用或从浏览器获取大量资源来说非常昂贵。

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

I have not found the source code of the default SSLSocketFactory and probably the keep-alive mechanism is implemented there.我还没有找到默认 SSLSocketFactory的源代码,可能在那里实现了保持活动机制。 As a confirmation, disable your own SSLSocketFactory implementation for a test, with a custom trust store in javax.net.ssl.trustStore so that your self-signed certificate is accepted.作为确认,禁用您自己的SSLSocketFactory实现以进行测试,并在javax.net.ssl.trustStore使用自定义信任存储,以便接受您的自签名证书。

According to OpenJDK 7 ServerImpl implementation which uses ServerConfig the HttpsServer you used emits a keep-alive with 5 minutes timeout per default.根据使用ServerConfig的 OpenJDK 7 ServerImpl实现,您使用的HttpsServer发出保持活动状态,默认情况下超时 5 分钟。

I propose you set the property sun.net.httpserver.debug to true server-side to get details.我建议您将属性sun.net.httpserver.debug设置为true server-side 以获取详细信息。

Take care your code does not add the header Connection: close which disables keep-alive mechanism.请注意您的代码不会添加标题Connection: close禁用保持活动机制。

We may setup an Apache Webserver, add following directives to see whether the Apache's access.log has a keep-alive connection for the http client.我们可以设置一个 Apache Webserver,添加以下指令来查看 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.htmlhttp://httpd.apache.org/docs/current/mod/mod_log_config.html

"%k" Number of keepalive requests handled on this connection. "%k" 在此连接上处理的保活请求数。 Interesting if KeepAlive is being used, so that, for example, a '1' means the first keepalive request after the initial one, '2' the second, etc...;有趣的是,如果使用 KeepAlive,例如,'1' 表示第一个 keepalive 请求之后的第一个 keepalive 请求,'2' 表示第二个,等等……; otherwise this is always 0 (indicating the initial request).否则这始终为 0(表示初始请求)。

I faced the same problem, and Bill Healey is right.我遇到了同样的问题,比尔希利是对的。 I tested my example code below with few https libraries.我用几个 https 库测试了下面的示例代码。 HttpsURLConnection and OKHTTP are exact same behavior. HttpsURLConnection 和 OKHTTP 是完全相同的行为。 Volley is a bit different when session resumption, but almost same behavior.恢复会话时,Volley 有点不同,但行为几乎相同。 I hope this will be some help.我希望这会有所帮助。

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();
    }
}

Update:更新:

Sharing SSLSocketFactory enables keep-alive.共享 SSLSocketFactory 可以保持活动状态。 Sharing SSLContext and getting facotry each request enable session resumption.共享 SSLContext 并获取 facotry 每个请求启用会话恢复。 I don't know how TLS stack works, but just confirmed these connection behaviors with some mobile devices.我不知道 TLS 堆栈是如何工作的,但只是在某些移动设备上确认了这些连接行为。

If you want to enable keep-alive among multiple classes, you should share the instance of SSLSocketFactory using singleton pattern.如果要在多个类之间启用 keep-alive,则应使用单例模式共享 SSLSocketFactory 的实例。

If you want to enable session resumption, make sure the session timeout settings is long enough on server side, such as SSLSessionCacheTimeout (apache), ssl_session_timeout (nginx).如果要启用会话恢复,请确保服务器端的会话超时设置足够长,例如SSLSessionCacheTimeout (apache)、 ssl_session_timeout (nginx)。

In addition to @Bill Healey answer the HostnameVerifier also must be declared static.除了@Bill Healey 的回答,HostnameVerifier 也必须声明为静态。 I've tried several patterns with and without closing input stream and connection they make no change for me.我已经尝试了几种模式,无论是否关闭输入流和连接,它们对我没有任何改变。 The only thing that matters is the static declarations of mentioned properties.唯一重要的是上述属性的静态声明。

/**
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();
        }
}

As far as I can understand HTTP/1.1 and HTTPS protocol, also documented here , Keep-Alive is not an end-to-end header but a hop-to-hop header.据我所知, HTTP/1.1HTTPS协议(也记录在此处)Keep-Alive不是端到端标头,而是跳到跳标头。 Since SSL involves multiple steps of handshaking among "different hops" (eg CA and the server) for each new connection, I think Keep-Alive may not be applicable in SSL context.由于 SSL 涉及每个新连接的“不同跃点”(例如 CA 和服务器)之间的多个握手步骤,我认为Keep-Alive可能不适用于 SSL 上下文。 So, that can be why Keep-Alive header is ignored using HTTPS connections.因此,这可能是使用 HTTPS 连接忽略Keep-Alive标头的原因 Based on this this question , you may need to ensure one instance of HTTP connection is used to guarantee Keep-Alive observation.基于this this question ,您可能需要确保使用一个HTTP连接实例来保证Keep-Alive观察。 Also, in the question, it seems that Apache HTTPClient has been a better solution.此外,在问题中,Apache HTTPClient似乎是一个更好的解决方案。

try to add the following code:尝试添加以下代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM