In Progress
Unit 1, Lesson 21
In Progress

Http.rb

Video transcript & code

Look, let's not mince words: Ruby's built-in HTTP client library leaves a lot to be desired. In fact, it's so famously idiosyncratic that a huge number of alternative HTTP client gems have sprung up over the years.

I've used most of these alternatives, and found most of them wanting in some respect or other.

However, recently I've been using the http gem by Tony Arcieri, and I've been really enjoying it.

require "http"

I'm not going to give you a full tutorial today, because that's not really what I do on this show. Instead, I just want to show you one or two points that stand out.

First, let's get this out of the way: yes, it has a very simple API for making very simple requests. We can simply send an HTTP method name, such as get, to the HTTP module along with a URL.

require "http"

response = HTTP.get("https://rubytapas.com")

The resulting response object has a full complement of methods for retrieving the response status, whether the request succeeded, the body type, and the body itself as a string.

response.status                 # => #<HTTP::Response::Status 200 OK>
response.status.success?        # => true
response.content_type
# => #<struct HTTP::ContentType mime_type="text/html", charset="UTF-8">
response.body.to_s.size         # => 105776

But these are all features boasted by most HTTP client gems, and thus not very exciting. Where the http gem gets interesting is in its API design for more complex calls.

For instance, let's say we're talking to an API, and we want to specifically request JSON documents. We can do this by inserting a new method call between the module and the request method.

require "http"

HTTP.accept("application/json")
  .get("http://example.org/api/list")

This sets the request's Accept header.

Let's say we also need to authenticate our requests using HTTP basic auth. We inject another call specifying username and password.

require "http"

HTTP.accept("application/json")
  .basic_auth(user: "frodo", password: "friend")
  .get("http://example.org/api/list")

It wouldn't be much fun if we had to repeat this setup for every request. Fortunately, we don't have to.

We break this code apart just before the request method, and assign the result to a variable we'll client.

require "http"

client = HTTP.accept("application/json")
           .basic_auth(user: "frodo", password: "friend")

We can now make requests against this client object, and all of our initial setup will be applied to each call.

require "http"

client = HTTP.accept("application/json")
           .basic_auth(user: "frodo", password: "friend")

client.get("http://example.org/api/list")
client.get("http://example.org/api/show/1")

So far we're still specifying the entire URL each time. We're also setting up and tearing down a whole new TCP connection with each request.

We can address both of these issues by adding a call to persistent to the setup chain.

Then we can specify just paths for our requests, instead of fully-qualified URLs.

require "http"

client = HTTP.persistent("http://example.org")
           .accept("application/json")
           .basic_auth(user: "frodo", password: "friend")

client.get("/api/list")
client.get("/api/show/1")

Under the covers, the gem is setting up a persistent connection and re-using it for each request.

The pattern we can use with this gem is to set up everything that's common across all requests and save that in a client variable. Then if we need to do any request-specific configuration, we can always add it on.

For instance, if we wanted just one request to automatically follow redirects, we could add that on:

require "http"

client = HTTP.persistent("http://example.org")
           .accept("application/json")
           .basic_auth(user: "frodo", password: "friend")

client.get("/api/list")
client.follow.get("/api/show/1")

This is what I like most about the http gem over other alternatives: it has this flexible, consistent, simple to use fluent API for configuring requests. But there are some other points in its favor as well:

  • It has a pure-ruby implementation, making it quite portable across platforms and Ruby implementations.
  • It is mature and relatively complete, with support for the full set of RESTful HTTP methods, multipart form uploads, fine-grained timeouts, streaming uploads and downloads, and a lot more.
  • It has good documentation at the project wiki.

Now that I've started to use it, I think the http gem is likely to become a frequent tool in my toolbox. If this episode has piqued your interest, go to github.com/httprb/http to learn more about it, and about the many features I left out of this little demo.

Happy hacking!

Responses