Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Decorating Ruby's Net::HTTP for Fun and Profit
# encoding: UTF-8
# frozen_string_literal: true
require 'net/http'
require 'json'
require 'uri'
class HTTPDecorator
# Timeouts
OPEN_TIMEOUT = 10 # in seconds
READ_TIMEOUT = 120 # in seconds
# Content-types
CONTENT_TYPE_JSON = 'application/json'
CONTENT_TYPE_FORM = 'application/x-www-form-urlencoded'
CONTENT_TYPE_MULTIPART = "multipart/form-data; boundary=#{ Rack::Multipart::MULTIPART_BOUNDARY }"
def initialize(domain)
# Build up our HTTP object
@http = Net::HTTP.new(domain, 443)
@http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
@http.open_timeout = OPEN_TIMEOUT
@http.read_timeout = READ_TIMEOUT
# In local development we can log requests and responses to $stdout.
# DO NOT EVER do this in production. EVER.
if ENV['RACK_ENV'] == 'development'
@http.set_debug_output($stdout)
end
end
# Open a connection for multiple calls.
# - Accepts a block, otherwise just opens the connection.
# - You'll need to close the connection if you just open it.
def start
if block_given?
# Open the connection.
@http.start unless @http.started?
# Yield to the calling block.
yield(self)
# Clean up the connection.
@http.finish if @http.started?
else
# Open the connection.
@http.start unless @http.started?
end
end
# Clean up the connection if needed.
def finish
@http.finish if @http.started?
end
# GET
def get(path, params = {})
uri = URI.parse(path)
uri.query = URI.encode_www_form(params) unless params.empty?
request = Net::HTTP::Get.new(uri.to_s)
parse fetch(request)
end
# POST
def post(path, params = {}, as: :json)
request = Net::HTTP::Post.new(path)
case as
when :json
request.content_type = CONTENT_TYPE_JSON
request.body = JSON.generate(params) unless params.empty?
else
request.content_type = CONTENT_TYPE_FORM
request.body = URI.encode_www_form(params) unless params.empty?
end
parse fetch(request)
end
# DELETE
def delete(path)
request = Net::HTTP::Delete.new(path)
parse fetch(request)
end
# PATCH
def patch(path, params = {}, as: :form)
request = Net::HTTP::Patch.new(path)
case as
when :json
request.content_type = CONTENT_TYPE_JSON
request.body = JSON.generate(params) unless params.empty?
else
request.content_type = CONTENT_TYPE_FORM
request.body = URI.encode_www_form(params) unless params.empty?
end
parse fetch(request)
end
# PUT
def put(path, params = {}, as: :json)
request = Net::HTTP::Put.new(path)
case as
when :json
request.content_type = CONTENT_TYPE_JSON
request.body = JSON.generate(params) unless params.empty?
else
request.content_type = CONTENT_TYPE_FORM
request.body = URI.encode_www_form(params) unless params.empty?
end
parse fetch(request)
end
# POST multipart
def multipart(path, params)
request = Net::HTTP::Post.new(path)
request.content_type = CONTENT_TYPE_MULTIPART
request.body = Rack::Multipart::Generator.new(
'file' => Rack::Multipart::UploadedFile.new(params['file'][:tempfile].path, params['file'][:type])
).dump
parse fetch(request)
end
private
# Perform the request.
def fetch(request)
# Shore up default headers for the request.
request['Accept'] = CONTENT_TYPE_JSON
request['Connection'] = 'keep-alive'
request['User-Agent'] = 'HTTPDecorator v0.1'
# Actually make the request.
response = @http.request(request)
# Net::HTTPResponse.value will raise an error for non-200 responses.
# Simpler than trying to detect every possible exception.
response.value || response
end
def parse(response)
# Parse the response as JSON if possible.
if response.content_type == CONTENT_TYPE_JSON
JSON.parse(response.body)
# Otherwise just return the response body.
else
response.body
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.