When trying to reach an external service (via HTTP for example) it is recommended to specify a timeout. If the service your are trying to contact is not responding, you don’t want you connection to hang on forever.

When you make an HTTP request you can specify 3 values for timeout : connect, write and read.

Connect Timeout

This is the obvious one: you want the connection to timeout if it has not been established after X seconds. Meaning, in general, that you cannot contact the service.

Setting a low connect timeout, like 2 seconds, might be useful to prevent your application (worker, job, etc.) to remain “blocked” for a long time, as the server probably has a problem.

Write Timeout

The write timeout it is the maximum time you allow to write (send) the request data to the service. If you have a very short request body, it will probably be sent in a matter of milliseconds to the server.

However, if you POST a very large file to an API, it might take severall seconds on a 3G network.

Adapt the timeout value to your application usage. When we make requests with a very small JSON body like { key: "some-value" }we set a short write timeout to prevent our worker to hang for a long time.

Read Timeout

This is the most useful and easy to estimate timeout of all: the time you wait for the response. It might be quick to connect to a web server, very quick to send you request, but long for it to return a web page or complex data.

Adapting timeout to your usage

We regularly checks email addresses validity / quality using synchronous API. When we request the information, the connection remains open while the service checks domain name, email servers, and gathers many other pieces of data.

We use a 60s read timeout because we know from experience that it take a very long time. Because our app runs this request asynchronously, we can wait for 60s.

For a more time critical operation, like doing a fraud pre-check on an e-commerce website, we cannot use a 30s timeout. If the API is down, the customer trying to pay for his order won’t wait for 30s. We need to respond quickly, probably skip the pre-check and move to the payment.

By experience (again) we know how the service behaves 99% of the time : we connect and write to the API in less than a second, and it responds in less than 2 seconds.

Timeout with Ruby 2.6

There’s a great blog post by BigBinary that explains write_timeout attribute added to Net::HTTP with Ruby 2.6.

However, we encourage you to experiment with the HTTP gem which we’ve used in production for many years.

HTTP gem : timeouts, the easy way

With this gem, we can set precise timeouts like 1s connect, 1s write and 2s read :

HTTP.timeout(connect: 1, write: 1, read: 2).get("https://api.io")

We could also do a simpler 2s global timeout :

HTTP.timeout(2).get("https://api.io")

There are many other reasons why we use it for most of our projects:

  • It is simple to use and configure (especially if you’ve headaches with the unintuitive Net::HTTP syntax for POST requests)
  • It is faster, very reliable, and consumes a lot less memory (especially when streaming responses)
  • It comes with a great documentation
  • It is easy to stub in tests as Webmock and VCR support it
  • It can log requests and responses using any Ruby logger, which comes in handy in developpment or production

You can dig into the this gem’s greatness in Janko Marohnić’s amazing post.