lib/resync/client/http_helper.rb
require 'net/http'
require 'tempfile'
require 'uri'
require 'mime-types'
module Resync
class Client
# Utility class simplifying GET requests for HTTP/HTTPS resources.
class HTTPHelper
# ------------------------------------------------------------
# Constants
# The default number of redirects to follow before erroring out.
DEFAULT_MAX_REDIRECTS = 5
# ------------------------------------------------------------
# Accessors
# @!attribute [rw] user_agent
# @return [String] the User-Agent string to send when making requests
attr_accessor :user_agent
# @!attribute [rw] redirect_limit
# @return [Integer] the number of redirects to follow before erroring out
attr_accessor :redirect_limit
# ------------------------------------------------------------
# Initializer
# Creates a new +HTTPHelper+
#
# @param user_agent [String] the User-Agent string to send when making requests
# @param redirect_limit [Integer] the number of redirects to follow before erroring out
# (defaults to {DEFAULT_MAX_REDIRECTS})
def initialize(user_agent:, redirect_limit: DEFAULT_MAX_REDIRECTS)
@user_agent = user_agent
@redirect_limit = redirect_limit
end
# ------------------------------------------------------------
# Public methods
# Gets the content of the specified URI as a string.
# @param uri [URI] the URI to download
# @param limit [Integer] the number of redirects to follow (defaults to {#redirect_limit})
# @return [String] the content of the URI
def fetch(uri:, limit: redirect_limit)
make_request(uri, limit) do |success|
# not 100% clear why we need an explicit return here; it
# doesn't show up in unit tests but it does in example.rb
return success.body
end
end
# Gets the content of the specified URI and saves it to a file. If no
# file path is provided, saves it to a temporary file.
# @param uri [URI] the URI to download
# @param path [String] the path to save the download to (optional)
# @return [String] the path to the downloaded file
def fetch_to_file(uri:, path: nil, limit: redirect_limit)
make_request(uri, limit) do |success|
file = path ? File.new(path, 'w+') : Tempfile.new(['resync-client', ".#{extension_for(success)}"])
open file, 'w' do |out|
success.read_body { |chunk| out.write(chunk) }
end
return file.path
end
end
# ------------------------------------------------------------
# Private methods
private
def make_request(uri, limit, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
raise "Redirect limit (#{redirect_limit}) exceeded retrieving URI #{uri}" if limit <= 0
req = Net::HTTP::Get.new(uri, 'User-Agent' => user_agent)
Net::HTTP.start(uri.hostname, uri.port, use_ssl: (uri.scheme == 'https')) do |http|
http.request(req) do |response|
case response
when Net::HTTPSuccess
yield(response)
when Net::HTTPInformation, Net::HTTPRedirection
make_request(redirect_uri_for(response, uri), limit - 1, &block)
else
raise "Error #{response.code}: #{response.message} retrieving URI #{uri}"
end
end
end
end
def extension_for(response)
content_type = response['Content-Type']
mime_type = MIME::Types[content_type].first || MIME::Types['application/octet-stream'].first
mime_type.preferred_extension || 'bin'
end
def redirect_uri_for(response, original_uri)
if response.is_a?(Net::HTTPInformation)
original_uri
else
location = response['location']
new_uri = URI(location)
new_uri.relative? ? original_uri + location : new_uri
end
end
end
end
end