HTTP Response Handling in Vanilla Ruby

Vanilla can be delicious, especially when you learn to make Ruby expressive and flexible simply by following good object-oriented programming practices. Mike shares a method for handling HTTP responses in Ruby’s ‘net/http’ library.

Beware Dependencies

Adding a dependency to your project often adds hidden costs to maintaining your code. Even the best tested, most carefully maintained libraries will have their issues. Some libraries you choose will inevitably be abandoned, leaving you with the equally undesirable options of re-implementing features or taking on maintenance of a community project. I recently began forcing myself away from add-on HTTP libraries in Ruby. I’m making more of an effort now to be productive in the standard library, and I want to share a method for handling HTTP responses in Ruby’s ‘net/http’ library. I know now it can be expressive and flexible simply by following good object-oriented programming practices.

A Sample Request

Consider this call to a fictional API endpoint.

require 'net/http'
Net::HTTP.start 'api.example.com', 80 do |http|
http.get '/endpoint' do |response|
p response
end
end
view raw request.rb hosted with ❤ by GitHub

This works, but it’s hard to test because it doesn’t return anything and our entire implementation is in the Net::HTTP.start block. If we could move the work of evaluating the response outside the block, we might find better ways to handle it. Luckily for us, Net::HTTP.start (as you probably already knew) returns whatever the block returns. Knowing that, we can refactor this slightly.

Think Outside the Block

require 'net/http'
response = Net::HTTP.start 'api.example.com', 80 { |http|
http.get '/endpoint'
}
p response.body
view raw request.rb hosted with ❤ by GitHub

We’re still just printing the response to the console, but having it available outside of the block gives us options we didn’t have before. Consider that different types of responses get different response classes. We can instantiate handlers for those responses by reusing those class names in our own code.

require 'net/http'
response = Net::HTTP.start 'api.example.com', 80 { |http|
http.get '/endpoint'
}
response_base_class = response.class.name.split('::').last
begin
handler_class = Object.const_get("ApiResponse::#{response_base_class}")
handler_class.new(response).handle
rescue NameError => e
ApiResponse::DefaultHandler.new(response).handle
end
module ApiResponse
class DefaultHandler
# ...
end
class HTTPOK
# ...
end
end
view raw request.rb hosted with ❤ by GitHub

This is a pattern popularized most recently by Sandi Metz, and it works in a variety of situations. I encourage you to read the linked post. Understanding the refactoring process to get to this code is more valuable than the implementation itself.

Question Old Habits

In the past, I might have grumbled something about beautiful APIs and flippantly added HTTParty to my Gemfile, probably because I saw it in a project once. That might have been okay as a new developer, but experience requires us to constantly reevaluate the decisions we’re making. Breaking away from those rote decisions is part of what lets us look in horror at the code we wrote six months ago and feel better about what we’re writing today.