lib/wpxf/net/http_client.rb
# frozen_string_literal: true
require 'uri'
module Wpxf
module Net
# Provides HTTP client functionality.
module HttpClient
include Wpxf::Net::UserAgent
include Wpxf::Net::HttpOptions
include Wpxf::Net::TyphoeusHelper
# Initialize a new instance of {HttpClient}.
def initialize
super
initialize_options
initialize_advanced_options
@hydra = Typhoeus::Hydra.new(max_concurrency: max_http_concurrency)
end
# Initialize the basic HTTP options for the module.
def initialize_options
register_options([
HTTP_OPTION_HOST,
HTTP_OPTION_PORT,
HTTP_OPTION_SSL,
HTTP_OPTION_VHOST,
HTTP_OPTION_PROXY,
HTTP_OPTION_TARGET_URI
])
end
# Initialize the advanced HTTP options for the module.
def initialize_advanced_options
register_advanced_options([
HTTP_OPTION_BASIC_AUTH_CREDS,
HTTP_OPTION_PROXY_AUTH_CREDS,
HTTP_OPTION_HOST_VERIFICATION,
HTTP_OPTION_MAX_CONCURRENCY,
HTTP_OPTION_CLIENT_TIMEOUT,
HTTP_OPTION_USER_AGENT,
HTTP_OPTION_FOLLOW_REDIRECT,
HTTP_OPTION_PEER_VERIFICATION
])
set_option_value('user_agent', random_user_agent)
end
# @return [String] the base path to the WordPress application.
def target_uri
datastore['target_uri']
end
# @return [Integer] the target port number that the application is on.
def target_port
normalized_option_value('port')
end
# @return [String] the target host address.
def target_host
normalized_option_value('host')
end
# Normalize a URI to remove duplicated slashes and ensure a slash at
# the start of the string if it doesn't start with http:// or https://.
# @param parts the URI parts to join and normalize.
# @return [String] a normalized URI.
def normalize_uri(*parts)
path = parts * '/'
path = '/' + path unless path.start_with?('/', 'http://', 'https://')
url = URI.parse(path)
url.path.squeeze!('/')
url.to_s
end
# Returns the base URI string.
# @return [String] the base URI that the module targets.
def base_uri
uri_scheme = normalized_option_value('ssl') ? 'https' : 'http'
uri_port = target_port == 80 ? '' : ":#{target_port}"
"#{uri_scheme}://#{target_host}#{uri_port}"
end
# Returns the full URI string including the target path.
# @return [String] the full URI that the module targets.
def full_uri
normalize_uri(base_uri, target_uri)
end
# @return [Hash] the base headers to be used in HTTP requests.
def base_http_headers
headers = { 'User-Agent' => datastore['user_agent'] }
unless datastore['vhost'].nil? || datastore['vhost'].empty?
headers['Host'] = datastore['vhost']
end
headers
end
# Queue a HTTP request to be executed later by {#execute_queued_requests}.
# @param opts [Hash] a hash of request options.
# @param callback [Proc] a proc to call when the request is completed.
def queue_request(opts, &callback)
req = create_typhoeus_request(opts)
req.on_complete do |res|
callback.call(Wpxf::Net::HttpResponse.new(res)) if callback
end
@hydra.queue req
@hydra.queued_requests
end
# Execute multiple HTTP requests in parallel queued by {#queue_request}.
def execute_queued_requests
@hydra.run
end
# Execute a HTTP request.
# @param opts [Hash] a hash of request options.
# @return [HttpResponse, nil] a {Wpxf::Net::HttpResponse} or nil.
def execute_request(opts)
req = create_typhoeus_request(opts)
req.on_complete do |res|
return Wpxf::Net::HttpResponse.new(res)
end
emit_info "Requesting #{req.url}...", true
req.run
end
# Stream a response directly to a file (leaves the body attribute empty).
# @param opts [Hash] a hash of request options, local_filename being the
# path to stream the response to.
# @return [HttpResponse, nil] a {Wpxf::Net::HttpResponse} or nil.
def download_file(opts)
target_file = File.open(opts[:local_filename], 'wb')
req = create_typhoeus_request(opts)
req.on_headers do |response|
return Wpxf::Net::HttpResponse.new(response) if response.code != 200
end
req.on_body do |chunk|
target_file.write(chunk)
end
req.on_complete do |response|
target_file.close
resp = Wpxf::Net::HttpResponse.new(response)
resp.body = File.read(opts[:local_filename])
return resp
end
req.run
end
# Execute a HTTP GET request.
# @param opts [Hash] a hash of request options.
# @return [HttpResponse, nil] a {Wpxf::Net::HttpResponse} or nil.
def execute_get_request(opts)
execute_request(opts.merge(method: :get))
end
# Execute a HTTP POST request.
# @param opts [Hash] a hash of request options.
# @return [HttpResponse, nil] a {Wpxf::Net::HttpResponse} or nil.
def execute_post_request(opts)
execute_request(opts.merge(method: :post))
end
# Execute a HTTP PUT request.
# @param opts [Hash] a hash of request options.
# @return [HttpResponse, nil] a {Wpxf::Net::HttpResponse} or nil.
def execute_put_request(opts)
execute_request(opts.merge(method: :put))
end
# Execute a HTTP DELETE request.
# @param opts [Hash] a hash of request options.
# @return [HttpResponse, nil] a {Wpxf::Net::HttpResponse} or nil.
def execute_delete_request(opts)
execute_request(opts.merge(method: :delete))
end
# @return [Integer] the maximum number of threads to use when using
# {#execute_queued_requests}.
def max_http_concurrency
normalized_option_value('max_http_concurrency')
end
# Normalize a relative URI into an absolute URL.
# @param uri [String] the relative or absolute URI.
# @return [String] the absolute URL.
def normalize_relative_uri(uri)
if uri.start_with?('/')
normalize_uri(full_uri, uri)
else
uri
end
end
end
end
end