HttpsUrlConnection and keep-alive
Asked Answered
S

8

33

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). When I use FireFox to make multiple request against the server I can see that keep-alive is working. So the server part works fine with GET and POST-requests.

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.

But if I use HttpsURLConnection (using exactly the same code, but using SSL) then keep-alive is not working anymore. So for each request a new connection is established, although I am using the same SSLContext (and 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?

Update (2012-04-02): Instead of calling mySsl.getSocketFactory() each time, I tried to cache the SSLSocketFactory. But nothing changed. The problem still exists.

Stern answered 30/3, 2012 at 12:51 Comment(3)
HttpsURLConnection uses a service provider of sorts to determine the actual implementation of SSLSocketFactory to use. When running in some contexts (like an app server), different impls will be used. What jdk are you using in testing this? What sort of env?Upswing
Also, try setting -Djava.net.debug=ssl when running your code. Might provide additional information.Upswing
The code is running as a normal Java 7 application using Sun's JRE/JDK. I already have -Djava.net.debug=ssl set but couldn't see anything wrong. However I will check that again next time at work.Stern
S
5

I couldn't get it working with HttpsUrlConnection. But Apache's HTTP client handles keep-alive with SSL connections very well.

Stern answered 9/9, 2012 at 18:38 Comment(3)
IMO you'll need to enable the ssl session on the client side as well. see https://mcmap.net/q/453553/-reusing-ssl-sessions-in-android-with-httpclient/194609. But because the answer is insufficient I've asked it again (in a different context): https://mcmap.net/q/453554/-enabling-ssl-session-for-jetty-to-speed-up-https-requests/194609Skelly
HttpClient was deprecated in API level 22. developer.android.com/reference/org/apache/http/client/…Baccalaureate
Btw, I ran into issues with HttpsUrlConnection and since Apache's HTTP client is deprecated, I tried OkHttp and it worked flawlessly. Worth checking out for those running into trouble.Alpha
I
26

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.

These are:

  • Input streams must be fully consumed. You must call read on the input stream until it returns -1 and also close it.
  • 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.

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.

Inhalator answered 9/4, 2015 at 19:1 Comment(3)
Finally the answer to overload of extra keep-alive sockets - have to call disconnect!Capacious
@bill-healey - were you running this on Android? The two implementations (Android vs Oracle) document this differently, and behave differently - Android states that disconnect() may not get rid of resources, whereas Oracle is a bit more definite about it.Goble
pretty sure the disconnect is not necessary (may even be counter-productive). just close the output stream and consume the input and error streams. (tested on jdk7)Ceresin
S
5

I couldn't get it working with HttpsUrlConnection. But Apache's HTTP client handles keep-alive with SSL connections very well.

Stern answered 9/9, 2012 at 18:38 Comment(3)
IMO you'll need to enable the ssl session on the client side as well. see https://mcmap.net/q/453553/-reusing-ssl-sessions-in-android-with-httpclient/194609. But because the answer is insufficient I've asked it again (in a different context): https://mcmap.net/q/453554/-enabling-ssl-session-for-jetty-to-speed-up-https-requests/194609Skelly
HttpClient was deprecated in API level 22. developer.android.com/reference/org/apache/http/client/…Baccalaureate
Btw, I ran into issues with HttpsUrlConnection and since Apache's HTTP client is deprecated, I tried OkHttp and it worked flawlessly. Worth checking out for those running into trouble.Alpha
O
3

SSL connection establishment is really expensive either for service calls or when getting many resources from a browser.

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

I have not found the source code of the default SSLSocketFactory and probably the keep-alive mechanism is implemented there. 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.

According to OpenJDK 7 ServerImpl implementation which uses ServerConfig the HttpsServer you used emits a keep-alive with 5 minutes timeout per default.

I propose you set the property sun.net.httpserver.debug to true server-side to get details.

Take care your code does not add the header Connection: close which disables keep-alive mechanism.

Outofdoors answered 2/4, 2012 at 21:20 Comment(6)
I already checked the connection-header. It is never set to "close". If it's present, it is set to "keep-alive". When I turn on httpserver-debugging, I get the following message: "java.io.IOException: Engine is closed". I already use a custom trust/key store with self-signed certificates. Currently I am using Apache's HttpClient which works fine with self-CA-signed client/server-certificate and keep-alive connections (using the same SSLContext and code). However it would be nice to know if I've done something wrong with HttpsURLConnection. ;)Stern
In my opinion, it should work. The error comes from docjar.com/html/api/sun/net/httpserver/SSLStreams.java.html obviously a code called close too early. Please publish your project somewhere or a SSCCE so that we may investigate further.Outofdoors
The Streams of HttpsUrlConnection are closed automatically by Java 7's try (InputStream is = ...) etc.. But that should be okay: link. It says: When the application finishes reading the response body or when the application calls close() on the InputStream returned by URLConnection.getInputStream(), the JDK's HTTP protocol handler will try to clean up the connection and if successful, put the connection into a connection cache for reuse by future HTTP requests. Currently I'm at home where I don't have the code.Stern
Have you tested with Java 6 too ?Outofdoors
I have the same problem and I'm using Java 6. I can't get the HttpsUrlConnection class to reuse connections, but I can get the Apache HTTP Client library to do it. I'm also using a client certificate via a custom SSLSocketFactory.Miscarriage
Java HttpsUrlConnection does not implement a connection pool, but you can work-around that lack of feature by setting keep-alive and send multiple queries on the same connection. By the way, I recommend Apache HTTP client.Outofdoors
D
0

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. Since SSL involves multiple steps of handshaking among "different hops" (e.g. CA and the server) for each new connection, I think Keep-Alive may not be applicable in SSL context. So, that can be why Keep-Alive header is ignored using HTTPS connections. Based on this this question, you may need to ensure one instance of HTTP connection is used to guarantee Keep-Alive observation. Also, in the question, it seems that Apache HTTPClient has been a better solution.

Delmore answered 2/4, 2012 at 9:42 Comment(0)
H
0

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.

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" Number of keepalive requests handled on this connection. 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...; otherwise this is always 0 (indicating the initial request).

Humbertohumble answered 23/9, 2014 at 9:0 Comment(0)
P
0

I faced the same problem, and Bill Healey is right. I tested my example code below with few https libraries. HttpsURLConnection and OKHTTP are exact same behavior. Volley is a bit different when session resumption, but almost same behavior. 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. Sharing SSLContext and getting facotry each request enable session resumption. I don't know how TLS stack works, but just confirmed these connection behaviors with some mobile devices.

If you want to enable keep-alive among multiple classes, you should share the instance of SSLSocketFactory using singleton pattern.

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).

Plaster answered 24/3, 2017 at 8:7 Comment(0)
V
0

In addition to @Bill Healey answer the HostnameVerifier also must be declared static. 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();
        }
}
Vierno answered 1/12, 2020 at 13:3 Comment(0)
C
-3

try to add the following code:

con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Keep-Alive", "header");
Contrabass answered 9/4, 2012 at 7:12 Comment(1)
The first is the default and the second is non-existent. -1Drily

© 2022 - 2024 — McMap. All rights reserved.