lib/wrest/native/request.rb
# frozen_string_literal: true
# Copyright 2009 Sidu Ponnappa
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
module Wrest
module Native
# This represents a HTTP request. Typically you will never need to instantiate
# one of these yourself - you can use one of the more conveient APIs via Wrest::Uri
# or Wrest::Native::Get etc. instead.
class Request
attr_reader :http_request, :uri, :body, :headers, :username, :password, :follow_redirects,
:follow_redirects_limit, :follow_redirects_count, :timeout, :connection, :parameters,
:cache_store, :verify_mode, :options, :ca_path
# Valid tuples for the options are:
# :username => String, defaults to nil
# :password => String, defaults to nil
# :follow_redirects => Boolean, defaults to true for Get, false for anything else
# :follow_redirects_limit => Integer, defaults to 5. This is the number of redirects
# that Wrest will automatically follow before raising an
# Wrest::Exceptions::AutoRedirectLimitExceeded exception.
# For example, if you set this to 1, the very first redirect
# will raise the exception.
# :follow_redirects_count => Integer, defaults to 0. This is a count of the hops made to
# get to this request and increases by one for every redirect
# until the follow_redirects_limit is hit. You should never set
# this option yourself.
# :timeout => The period, in seconds, after which a Timeout::Error is raised
# in the event of a connection failing to open. Defaulted to 60 by Uri#create_connection.
# :connection => The HTTP Connection object to use. This is how a keep-alive connection can be
# used for multiple requests.
# :cache_store => The object which should be used as cache store for cacheable responses. If not supplied, caching will be disabled.
# :callback => A Hash whose keys are the response codes (or Range of response codes),
# and the values are the callback functions to be executed.
# eg: { <response code> => lambda { |response| some_operation } }
# The following options are Net::HTTP specific config options
# :detailed_http_logging => nil/$stdout/$stderr or File/Logger/IO object. Defaults to nil (recommended).
# *WARNING* : detailed_http_logging causes a serious security hole. Never use it in production code.
# :verify_mode => The verification mode to be used for Net::HTTP https connections. Defaults to OpenSSL::SSL::VERIFY_PEER
# :ca_path => The path to the certificates
def initialize(wrest_uri, http_request_klass, parameters = {}, body = nil, headers = {}, options = {}) # rubocop:disable Metrics/ParameterLists
setup_request_state!(body, headers, parameters, wrest_uri)
setup_options_state!(options)
@detailed_http_logging = options[:detailed_http_logging]
@follow_redirects = (@options[:follow_redirects] ||= false)
@follow_redirects_count = (@options[:follow_redirects_count] ||= 0)
@follow_redirects_limit = (@options[:follow_redirects_limit] ||= 5)
@callback = @options[:callback] || Wrest::Callback.new({})
@callback = @callback.merge(Wrest::Callback.new(@options[:callback_block] || {}))
@http_request = build_request(http_request_klass, @uri, @parameters, @headers)
end
# Makes a request, runs the appropriate callback if any and
# returns a Wrest::Native::Response.
#
# Data about the request is and logged to Wrest.logger
# The log entry contains the following information:
#
# <- indicates a request
# -> indicates a response
#
# The type of request is mentioned in caps, followed by a hash
# uniquely identifying a particular request/response pair.
# In a multi-process or multi-threaded scenario, this can be used
# to identify request-response pairs.
#
# The request hash is followed by a connection hash; requests using the
# same connection (effectively a keep-alive connection) will have the
# same connection hash.
#
# Passing nil for either username or password will skip HTTP authentication
#
# This is followed by the response code, the payload size and the time taken.
def invoke
response = nil
setup_connection!
response = execute_request(response)
execute_callback_if_any(response)
@follow_redirects ? response.follow(@options) : response
rescue Timeout::Error => e
raise Wrest::Exceptions::Timeout, e
end
# :nodoc:
def build_request(request_klass, uri, parameters, headers)
if uri.query.empty?
request_klass.new(parameters.empty? ? uri.uri_path.to_s : "#{uri.uri_path}?#{Utils.hash_to_param(parameters)}", headers)
else
request_klass.new(
parameters.empty? ? "#{uri.uri_path}?#{uri.query}" : "#{uri.uri_path}?#{uri.query}&#{Utils.hash_to_param(parameters)}", headers
)
end
end
# :nodoc:
def do_request
@connection.request(@http_request, @body)
end
# :nodoc:
def execute_callback_if_any(actual_response)
@callback.execute(actual_response)
end
private
def setup_connection!
@connection ||= @uri.create_connection(timeout: timeout, verify_mode: verify_mode, ca_path: ca_path)
@connection.set_debug_output @detailed_http_logging
http_request.basic_auth(username, password) unless username.nil? || password.nil?
end
def execute_request(response)
prefix = "#{http_request.method} #{hash} #{@connection.hash} #{Thread.current.object_id}"
log_before_request(prefix)
time = Benchmark.realtime { response = Wrest::Native::Response.new(do_request) }
log_after_request(prefix, time)
response
end
def log_after_request(prefix, time)
Wrest.logger.debug "<- (#{prefix}) Time: #{time}"
end
def log_before_request(prefix)
Wrest.logger.debug "<- (#{prefix}) #{@uri.protocol}://#{@uri.host}:#{@uri.port}#{@http_request.path}"
Wrest.logger.debug "<- (#{prefix}) Body: #{@body}"
end
def setup_request_state!(body, headers, parameters, wrest_uri)
@uri = wrest_uri
@headers = headers.transform_keys(&:to_s)
@parameters = parameters
@body = body
end
def setup_options_state!(options)
@options = options.clone
@username = @options[:username]
@password = @options[:password]
@timeout = @options[:timeout]
@connection = @options[:connection]
@cache_store = @options[:cache_store]
@verify_mode = @options[:verify_mode]
@ca_path = @options[:ca_path]
end
end
end
end