HTTP client timeouts

http, timeout, network, rest, api 6 mins edit

We have already touched upon the importance of timeouts and described most important related JDBC knobs. The next aspect of timeouts I would like to focus on is using API clients. Specifically HTTP clients which are by far the most popular. We will review couple of popular HTTP client libraries and their configuration regarding timeouts.

Waiting

HttpURLConnection timeouts

HttpURLConnection available since JDK 1.1 has gained the ability to timeout its network communication in version JDK 5. The 2 available timeouts setConnectionTimeout, setReadTimeout control how long to wait until connection is established and how long to wait for a data from the server respectively. The default values are infinite ‼️.

Apache HttpClient timeouts

HttpClient from Apache HttpComponents suite has been a standard choice for http communication. It is a mature project, with rich API that fills many HttpURLConnection shortcomings e.g. connection pooling. Many of the APIs have been deprecated e.g. DefaultHttpClient, org.apache.http.params.CoreConnectionPNames hence one needs to be careful when setting the timeouts they fallback to system defined socket level defaults.

There are 3 timeouts settings available:

val requestConfig = RequestConfig.custom()
    // Determines the timeout in milliseconds until a connection is established.
    .setConnectTimeout(5_000) 
    // Defines the socket timeout in milliseconds,
    // which is the timeout for waiting for data or, put differently,
    // a maximum period inactivity between two consecutive data packets).
    .setSocketTimeout(5_000)
    // Returns the timeout in milliseconds used when requesting a connection
    // from the connection manager.
    .setConnectionRequestTimeout(2_000)
    .build()

The requestConfig can be further used as a default for an HttpClient instance:

val httpClient = HttpClients.custom()
    .setDefaultRequestConfig(requestConfig)
    .build()

It is also possible to configure each request separately:

val get = HttpGet("http://httpbin.org/get").apply { 
    config = requestConfig
}
httpClient.execute(get)

OkHttp

OkHttp is my favorite HTTP & HTTP/2 client for Android and Java applications. It is efficient and has good configuration defaults. There are 3 timeout settings available:

val client = OkHttpClient.Builder()
    // Sets the default connect timeout for new connections.
    .connectTimeout(5, TimeUnit.SECONDS)
    // Sets the default read timeout for new connections.
    .readTimeout(10, TimeUnit.SECONDS)
    // Sets the default write timeout for new connections.
    .writeTimeout(20, TimeUnit.SECONDS)
    .build()

All connectTimeout, readTimeout and writeTimeout default to 10 seconds 👍.

XMLHttpRequest and Fetch API timeouts

XMLHttpRequest is the standard foundation of network communication of Web application for over 10 years now. Nowadays it is being replaced with Fetch API but it still is, and will continue to be, the most popular for couple of years. There is only a single timeout configuration available in XMLHttpRequest:

The XMLHttpRequest.timeout property is an unsigned long representing the number of milliseconds a request can take before automatically being terminated. The default value is 0, which means there is no timeout.

Default is infinite ‼️

Since the default value is not configured we should diligently set the timeout in our code! It may be tempting to think that client side timeout is not so important compared to the one on the server. This is a questionable attitude to say the least. We need to keep in mind that there is a hard limit on the number of connections a browser will make to a single domain which is very important if we use HTTP 1.*. When we reach maximum number of concurrently opened connections, any new XMLHttpRequest is going to be queued indefinitely. The limit value varies in browsers and the recent RCF relaxes it. HTTP/2 alleviates the issue with connection multiplexing nonetheless its adoption is still low. According to w3techs it is about 20% as of today. The timeout value used in XMLHttpRequest is even more important in Single Page Applications. In SPAs the XMLHttpRequest without a timeout can live for as long as server and intermediate network parties allow effectively blocking all subsequent network calls.

Fetch API is meant to replace XMLHttpRequest. It is thus sad that the ability to timeout a request has not yet made it into the standard. Currently there is no standard way to enforce a timeout. There are couple of GitHub issues active: Add timeout option, Add option to reject the fetch promise automatically after a certain time elapsed that go over potential solutions. There was a proposal for cancelable promises which had been withdrawn after lots of discussion and lack of consensus. A brand new way has recently been implemented by Edge and Firefox allows one to timeout a Fetch API call 🎉 through the DOM standardized AbortController. Hopefully it will get into the Fetch API standard soon.

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controller.abort(), 5000);

fetch(url, { signal }).then(response => {
  return response.text();
}).then(text => {
  console.log(text);
});

URLSession timeouts

URLSession is the successor to NSURLConnection that underlays most if not all iOS http clients e.g. Alamofire. There are 2 main timeout values to configure both of which have default values available via URLSessionConfiguration.default:

let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 20.0
sessionConfig.timeoutIntervalForResource = 40.0

let session = URLSession(configuration: sessionConfig)

Fortunately there are default values configured:

  • timeoutIntervalForRequest:

    This property determines the request timeout interval for all tasks within sessions based on this configuration. The request timeout interval controls how long (in seconds) a task should wait for additional data to arrive before giving up. The timer associated with this value is reset whenever new data arrives. The default value is 60.

  • timeoutIntervalForResource:

    This property determines the resource timeout interval for all tasks within sessions based on this configuration. The resource timeout interval controls how long (in seconds) to wait for an entire resource to transfer before giving up. The default value is 7 days.

Note that timeoutIntervalForResource is a higher level timeout that what we have considered in other HTTP clients. It encompasses retries and or request timeouts hence has a large default.

Summary

Many of HTTP clients do not have a good default timeout configuration. Hence, if you care about your application resource usage and system stability you have to carefully review and configure timeouts where applicable. It is reassuring to see that modern HTTP clients e.g. OkHttp and URLSession have a short but sane default.