Using Faraday
I've seen the faraday ruby gem used in a number of ruby projects over the years, but I hadn't used it directly myself until recently.
There are a LOT of HTTP clients in ruby land, but faraday is consistently on the top of the list as being the most popular. I decided to try it out for a project I was leading at work. I needed to build a generalized HTTP client that could be used to interact with a number of different websites that accepted and responded with different content types. I also wanted to add some defaults such as a proxy uri, basic error handling, and a timeout. I really liked faraday's approach to mixing in ad-hoc request/response middleware to handle making requests, so I decided to give it a go. I built a little utility that wraps a faraday connection and its response. Nothing too fancy, but it made working with the connection and response objects more flexible for my specific use case. The example below is simplified, but it looked something like the following:
require "faraday"
require "http-cookie"
# Wraps a Faraday::Connection
#
# - Uses default configuration options for a proxy uri, timeout, and error handling
# - Accepts all configuration options for a faraday connection
# - Accepts a block to configure additional middleware
# - Returns a custom custom Response object
class HTTPConnection
attr_reader :connection
def initialize(url:, **kwargs, &block)
@connection = Faraday.new(
url:,
proxy: { uri: "https://someproxy.com" },
request: { timeout: 15 }, # seconds
**kwargs
) do |builder|
builder.response(:raise_error)
block&.call(builder)
end
end
%w[get post put delete patch].each do |request_method|
define_method(request_method) do |path, *args|
Response.new(connection.public_send(request_method, path, *args))
end
end
# Wraps a Faraday::Response
#
# - Adds helper to get the request_method
# - Adds helper to get the url
# - Adds helper to easily retrieve cookies from the response
class Response
extend Forwardable
attr_reader :response, :env
def_delegators :response, :status, :body, :headers
def_delegators :env, :request_body, :request_headers
def url
env.url.to_s
end
def request_method
env.method.to_s
end
def cookies
Cookies.new(
cookie_header: headers['Set-Cookie'],
url:
)
end
def initialize(response)
@response = response
@env = response.env
end
end
class Cookies
def initialize(cookie_header:, url:)
@jar = HTTP::CookieJar.new
@url = url
@jar.parse(cookie_header.to_s, @url)
end
def to_a
@jar.cookies(@url)
end
def to_header
{ 'Cookie' => HTTP::Cookie.cookie_value(to_a) }
end
end
end
conn = HTTPConnection.new(
url: "https://example.com",
headers: { "Content-Type" => "application/json" }
) do |builder|
# Can add any additional middleware here, depending on the requirements of the endpoint
builder.request(:url_encoded)
builder.response(:json)
end
# Works just like a normal faraday request
response = conn.post('/endpoint', { 'name' => 'Jason' })