How does one choose the perfect HTTP Client? The Ruby ecosystem offers a wealth of gems to make an HTTP request. Some are pure Ruby, some are based on Ruby's native Net::HTTP
, and some are wrappers for existing libraries or Ruby bindings for libcurl. In this article, I will present the most popular gems by providing a short description and code snippets of making a request to the
Dad Jokes API
. The gems will be provided in the order from the most-downloaded one to the least. To conclude I will compare them all in a table format and provide a quick summary, as well as guidance on which gem to choose.
Note: Although there is a multitude of gems at your disposal, I will present the most popular ones, which I define by the number of downloads and Github stars.
Setup
The requests
In comparing the most popular Ruby HTTP Clients, we will make a GET and a POST request.
When making a GET request to the Dad Jokes API , we will expect to see such an output:
{
:id=>"5wHexPC5hib",
:joke=>"I made a belt out of watches once... It was a waist of time.",
:status=>200
}
We will make POST requests to JSONPlaceholder , which is an API testing tool. We will provide this placeholder data:
{
:title=>"Why I love Hitchhiker's Guide to the Galaxy",
:body=>"42",
:userId=>42
}
Running your code
You can either work in the irb
console in your terminal, or create a file http_client.rb
, the contents of which you'll replace. To run the file, navigate to the directory where it's located and run:
$ ruby http_client.rb
A note on benchmarking
You will see that I offer performance comparison in the
HTTP Clients overview
section. The numbers you see there were an output of the Benchmark.measure
method coming from the benchmark
Ruby gem. If you'd like to check it yourself, install the gem through your terminal ($ gem install benchmark
) and then add these three lines to the code snippets:
require 'benchmark'
puts Benchmark.measure{
# code goes here
}
This will result in the following output:
0.810000 0.000000 0.810000 ( 0.816964)
The numbers represent: the user CPU time (the time spent executing your code), the system CPU time (the time spent in the kernel), both user and system CPU time added up, and the actual time (or wall clock time) it took for the block to execute in brackets.
NOTE: the outputs will vary from machine to machine, and from user to user!
Faraday
Faraday is a friendly, well-supported and widely popular Ruby HTTP Client - in fact, its downloads doubled over the past two years. Its official documentation states:
Faraday is an HTTP client library that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.
Let's now have a look at the GET request:
require 'faraday'
require 'json'
url = 'https://icanhazdadjoke.com/'
response = Faraday.get(url, {a: 1}, {'Accept' => 'application/json'})
joke_object = JSON.parse(response.body, symbolize_names: true)
# => {:id=>"FdNZTnWvHBd", :joke=>"I’ve just been reading a book about anti-gravity, it’s impossible to put down!", :status=>200}
joke_object[:joke]
# "I’ve just been reading a book about anti-gravity, it’s impossible to put down!"
And here is the POST request:
require 'faraday'
require 'json'
url = 'https://jsonplaceholder.typicode.com/posts'
response = Faraday.post(url, {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42})
puts JSON.parse(response.body, symbolize_names: true)
# {:id=>"FdNZTnWvHBd", :joke=>"I’ve just been reading a book about anti-gravity, it’s impossible to put down!", :status=>200}
As you see, the calls are short -- but that's not the only benefit of Faraday. Its documentation is clear and concise. It supports requests with and without a body, form upload, detailed HTTP requests, and establishing connection, which makes it possible to store a common URL base path or HTTP headers to apply to every request.
It is also important and exciting to mention that Faraday offers lots of middleware :
- Request Middleware to modify request details before the adapter runs
- BasicAuthentication to set the Authorization header to the
user:password base64
representation, and TokenAuthentication to set the Authorization header to a specified token - Multipart to convert a request body hash into a form request and UrlEncoded to convert it into a url-encoded request body
- Retry to automatically retry requests uppon different failures
- Instrumentation to instrument requests using different tools
- Logger to log both the request and the response body and headers
- RaiseError to check the response HTTP code and to flag out the 4xx or 5xx code
This level of commitment to developer happiness is impressive and is reflected in the Open Source community. This is the gem that brought us TurboVax , a website that helped millions of New Yorkers get vaccinated with light-speed responses, or that handles daily tons of requests by the students of the biggest coding bootcamp, Flatiron School, which created a gem that connects github repos to an educational CMS.
It comes as no surprise that Ruby Toolbox ranks this gem as the leader of all Ruby HTTP clients.
Rest-Client
Rest-client is "a simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete." There really is very little complication as rest-client seems to be built to make RESTful API requests intuitive.
Here's the GET request:
require 'rest-client'
require 'json'
response = RestClient.get('https://icanhazdadjoke.com/', headers={ 'Accept' => 'application/json' })
joke_object = JSON.parse(response.body)
# => {"id"=>"szItcaFQnjb", "joke"=>"Why was the broom late for the meeting? He overswept.", "status"=>200}
puts joke_object["joke"]
# "Why was the broom late for the meeting? He overswept."
And the POST request:
require 'rest-client'
require 'json'
response = RestClient.post('https://jsonplaceholder.typicode.com/posts', :body => {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42})
puts JSON.parse(response.body, symbolize_names: true)
# {"title"=>"Why I love Hitchhiker's Guide to the Galaxy", "body"=>"42", "userId"=>"42", "id"=>101}
This gem for many developers feel comfortable but, there is a warning sign on the horizon: the gem has a very low commit activity over the past 3 years, and there has been no new release in over a year. What does that mean, really? It basically means that there might be very little support, or even that the gem is no longer maintained. It is especially problematic given the new recent releases of Ruby and Rails. This may indicate potential poor maintanence, high vulnerability and suboptimal support.
HTTParty
The HTTParty gem was created to 'make http fun'. Indeed, with its intuitive and straightforward syntax, the gem is especially popular among Ruby newbies and in the side projects.
The following two lines are all we need to make a successful GET request:
require "HTTParty"
require 'json'
response = HTTParty.get('https://icanhazdadjoke.com/' , :headers => { 'Accept' => 'application/json' } )
joke_object = JSON.parse(response.body)
# {"id"=>"xXgyXvXgFlb", "joke"=>"Doctor you've got to help me, I'm addicted to Twitter. Doctor: I don't follow you.", "status"=>200}
puts joke_object["joke"]
# "Doctor you've got to help me, I'm addicted to Twitter. Doctor: I don't follow you."
And a POST request:
require 'HTTParty'
require 'json'
response = HTTParty.post('https://jsonplaceholder.typicode.com/posts', :body => {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42})
puts JSON.parse(response.body, symbolize_names: true)
# {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>"42", :id=>101}
Because of how seemingly-simple its syntax is, HTTParty used to be seen as the HTTP Client. Up until a year ago GitLab used it for a number of features but, as outlined in this issue , they decided against it as HTTParty is "is both slow and uses a lot of resources", which included proposterous memory allocation (see the table in HTTP Clients Overview ).
http.rb
http.rb is a reliable and pleasant gem for people who need an easy-to-use client library for making requests from Ruby. According to the documentation:
It uses a simple method chaining system for building requests, similar to Python's Requests. Under the hood, via Ruby FFI bindings, http.rb uses the Node.js http-parser, a fast HTTP parsing native extension. This library isn't just yet another wrapper around Net::HTTP. It implements the HTTP protocol natively and outsources the parsing to native extensions.
Its whole mission and vision is that it is "an easy-to-use API that should be a breath of fresh air after using something like Net::HTTP" (see the section on Net:HTTP below ). Other than that, it supports features like persistent connections and fine-grained timeouts and prides itself in "the best performance of any Ruby HTTP library which implements the HTTP protocol in Ruby instead of C."
Here's the GET request:
require "http"
http = HTTP.accept(:json)
response = http.get('https://icanhazdadjoke.com/')
joke_object = response.parse
# => {"id"=>"UnWS0wcF6Ed", "joke"=>"Every machine in the coin factory broke down all of a sudden without explanation. It just doesn’t make any cents.", "status"=>200}
puts joke_object["joke"]
# "Every machine in the coin factory broke down all of a sudden without explanation. It just doesn’t make any cents."
And the POST request:
require "http"
require 'json'
response = HTTP.post('https://jsonplaceholder.typicode.com/posts', :form => {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42})
puts JSON.parse(response.body)["joke"]
# {"title"=>"Why I love Hitchhiker's Guide to the Galaxy", "body"=>"42", "userId"=>"42", "id"=>101}
But, that's not all: http.rb offers flexibility to modify your requests with ease, to handle exceptions with pleasure and even to convert the response into different data types. You also have timeouts, compression, and persistent connections at your disposal. This gem is definitely pleasant to use, and reliable.
There is, however, a red flag: just like Rest-Client , http.rb has not seen any new updates in over a year, and there is a high volume of open issues. This may indicate potential poor maintanence, high vulnerability and suboptimal support.
Net::HTTP
Now that we covered the HTTP Clients that are popular and that bring joy, let's offer a reference point on the opposite side of the spectrum of the dev happiness. Net::HTTP
is pervasive in some developer circles, which, honestly, comes as a surprise -- a sentiment which I will explain in this section.
Ruby's standard library is already equipped with an HTTP client: the net/http
gem. It is perhaps the most common and the most cursed solution to making API requests. It is common because it comes with Ruby, and also many of the other gems run on it. There is plenty criticism for this gem, which include the cluttered syntax, or its fact that oftentimes, it immediately retries making a request upon failure (
a bug documented back in 2012
, which adds to the general sense of discontent).
However, given its importance in the Ruby ecosystem, let's have a look how we'd make the request to receive a joke to would relieve some of the frustration:
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse('https://icanhazdadjoke.com/')
request = Net::HTTP::Get.new(uri)
request['Accept'] = 'application/json'
req_options = {
use_ssl: uri.scheme == 'https',
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
joke_object = JSON.parse(response.body)
# => {'id'=>'7wciy59MJe', 'joke'=>'What’s Forest Gump’s Facebook password? 1forest1', 'status'=>200}
puts joke_object[:joke]
# 'What’s Forest Gump’s Facebook password? 1forest1'
And this is how we'd create a new post about our favorite novel:
require 'net/http'
require 'uri'
uri = URI('https://jsonplaceholder.typicode.com/posts')
response = Net::HTTP.post_form(uri, {:title=>"Why I love Hitchhiker's Guide to the Galaxy", :body=>"42", :userId=>42})
puts JSON.parse(response.body)
# {"title"=>"Why I love Hitchhiker's Guide to the Galaxy", "body"=>"42", "userId"=>"42", "id"=>101}
This is definitely a long-winded way to make a request.
There seem to be no good reason to submit any developr to this monster of a gem, especially that is not not fast and has just a disastrous memory allocation rates (see the table in the next section).
Ruby HTTP Clients overview
Now that you have seen the five main Ruby clients, let's zoom out and compare them:
Name | Downloads (in m) | Stars | Issue closure rate | GET Benchmark | POST Benchmark | Memory Allocation (in MB) |
---|---|---|---|---|---|---|
Faraday | 322 | 4,994 | 94% | 0.015469 | 0.003083 | N/A |
http.rb | 47 | 2,722 | 73% | 0.004946 | 0.003870 | 0.02 |
httparty | 130 | 5,372 | 92% | 0.003956 | 0.004761 | 12.59 |
rest-client | 185 | 5,025 | 84% | 0.002574 | 0.004073 | 12.57 |
Net::HTTP (standard lib) | N/A | N/A | N/A | 0.003086 | 0.005176 | 12.36 |
NOTE: The memory allocation comparison comes from this gist and concerns the memory allocation for download.
As you see above, Faraday is leading the way in almost all respects: number of downloads (which indicates its popularity), issue closure rate (which indicates maintainer support) and POST performance.
Not surprisingly, Net::HTTP is not leading the way in any of the categories.
Conclusion
Now that I have provided an overview, code snippets, and metrics of all gems, as well as Ruby community updates connected to them, I would like to offer a quick suggestion: if you are wondering which gem to use for your code base or a passion side project, do go with Faraday. It has the best support, the biggest community, it is widely popular, shockingly-well maintained, fast and performant in terms of memory. It comes as no surprise that this opinion is also shared by the Ruby toolbox .
Ruby as a language was created to make developers happy. Please don't encroach on your team's happiness by choosing Net::HTTP
. There are other, better, options at your disposal!