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.