HTTPray for Your HTTP Requests (in Ruby)

G Gordon Worley III
AdStage Engineering
3 min readMay 1, 2018

How much time do you spend waiting for HTTP responses? No matter how fast the server or how efficient your client code, if you make an HTTP request and you don’t already have an open socket, you’ll be looking at an overhead of around 100 ms. If you’re using HTTPS then the overhead creeps closer to 500 ms from performing a TLS handshake. Make enough requests and pretty soon you’re spending seconds waiting on IO instead of doing useful work.

The obvious solution is to use asynchronous requests so that you can keep doing work while you wait for IO. One way to implement this is with threads. Threads are theoretically easy to work, especially if you’re working in a language with futures since they let you make async HTTP requests with minimal code changes, but that only works if threads perform well in your stack. If you're programming in a language where thread support is dicey, you'll want to consider non-blocking IO instead. Unfortunately non-blocking IO is harder to work with and requires OS support, but if you can get it to work it lets you achieve even better performance than would be possible with threads.

A few months ago AdStage needed to figure out how to make efficient, asynchronous HTTP requests in Ruby so we could send large, structured event data to Sumo Logic to monitor our platform. From previous experience I knew that Ruby threads are wonky enough that I wanted to avoid them, but when I looked at existing Ruby gems for async HTTP all of them used threads, so if I wanted to to make HTTP requests with non-blocking IO I would have to do it myself. Luckily I had just been playing with writing to raw TCP sockets for another project so I figured making a bare-bones, non-blocking HTTP client in Ruby would be pretty easy, and sure enough in a couple weeks I cranked out HTTPray.

HTTPray lets you make an HTTP/HTTPS request using non-blocking IO so you don’t have to wait for a response. It will hand back a file descriptor so you can read the response as it streams in, but provides no convenience features to help you. Where HTTPray shines instead is in making many fire-and-forget style requests to the same server, and is able to aggressively reuse the same socket for repeated requests. If the connection drops it can automatically reconnect and supports circuit-breaker logic so you never wait longer than a specified timeout for a connection. This is important because even with non-blocking IO you still have to wait for a connection to the server to be established before you can send it data, and if you’re using non-blocking IO to send best-effort requests you don’t want your program blocking or crashing when the server is down. The result is effectively something like UDP over HTTP and is great for logging where you would rather fail to log than fail to serve a user request.

If you think HTTPray is cool, it’s just a small taste of the kind of stuff we get up to at AdStage where our engineering team is working on the cutting edge of data analytics and user experience to provide our customers with advanced reporting and automation tools for their online ads. Just a few of our projects include replacing legacy Ruby systems with modern Clojure and Elixir services, scaling out databases for our next phase of growth, and building complex yet user-friendly frontends. Check out our open positions to learn more!

--

--

G Gordon Worley III
AdStage Engineering

Phenomenological philosopher, mathematician, and programmer