class RestClient::Request

This class is used internally by RestClient to send the request, but you can also call it directly if you'd like to use a method not supported by the main API. For example:

RestClient::Request.execute(:method => :head, :url => 'http://example.com')

Mandatory parameters:

Optional parameters (have a look at ssl and/or uri for some explanations):

Constants

SSLOptionList

Attributes

args[R]
headers[R]
max_redirects[R]
method[R]
open_timeout[R]
password[R]
payload[R]
processed_headers[R]
proxy[R]
raw_response[R]
read_timeout[R]
redirection_history[RW]

An array of previous redirection responses

ssl_opts[R]
uri[R]
url[R]
user[R]

Public Class Methods

decode(content_encoding, body) click to toggle source
# File lib/restclient/request.rb, line 496
def self.decode content_encoding, body
  if (!body) || body.empty?
    body
  elsif content_encoding == 'gzip'
    Zlib::GzipReader.new(StringIO.new(body)).read
  elsif content_encoding == 'deflate'
    begin
      Zlib::Inflate.new.inflate body
    rescue Zlib::DataError
      # No luck with Zlib decompression. Let's try with raw deflate,
      # like some broken web servers do.
      Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate body
    end
  else
    body
  end
end
default_ssl_cert_store() click to toggle source

Return a certificate store that can be used to validate certificates with the system certificate authorities. This will probably not do anything on OS X, which monkey patches OpenSSL in terrible ways to insert its own validation. On most *nix platforms, this will add the system certifcates using OpenSSL::X509::Store#set_default_paths. On Windows, this will use RestClient::Windows::RootCerts to look up the CAs trusted by the system.

@return [OpenSSL::X509::Store]

# File lib/restclient/request.rb, line 476
def self.default_ssl_cert_store
  cert_store = OpenSSL::X509::Store.new
  cert_store.set_default_paths

  # set_default_paths() doesn't do anything on Windows, so look up
  # certificates using the win32 API.
  if RestClient::Platform.windows?
    RestClient::Windows::RootCerts.instance.to_a.uniq.each do |cert|
      begin
        cert_store.add_cert(cert)
      rescue OpenSSL::X509::StoreError => err
        # ignore duplicate certs
        raise unless err.message == 'cert already in hash table'
      end
    end
  end

  cert_store
end
execute(args, & block) click to toggle source
# File lib/restclient/request.rb, line 51
def self.execute(args, & block)
  new(args).execute(& block)
end
new(args) click to toggle source
# File lib/restclient/request.rb, line 62
def initialize args
  @method = normalize_method(args[:method])
  @headers = (args[:headers] || {}).dup
  if args[:url]
    @url = process_url_params(normalize_url(args[:url]), headers)
  else
    raise ArgumentError, "must pass :url"
  end

  @user = @password = nil
  parse_url_with_auth!(url)

  # process cookie arguments found in headers or args
  @cookie_jar = process_cookie_args!(@uri, @headers, args)

  @payload = Payload.generate(args[:payload])

  @user = args[:user] if args.include?(:user)
  @password = args[:password] if args.include?(:password)

  if args.include?(:timeout)
    @read_timeout = args[:timeout]
    @open_timeout = args[:timeout]
  end
  if args.include?(:read_timeout)
    @read_timeout = args[:read_timeout]
  end
  if args.include?(:open_timeout)
    @open_timeout = args[:open_timeout]
  end
  @block_response = args[:block_response]
  @raw_response = args[:raw_response] || false

  @proxy = args.fetch(:proxy) if args.include?(:proxy)

  @ssl_opts = {}

  if args.include?(:verify_ssl)
    v_ssl = args.fetch(:verify_ssl)
    if v_ssl
      if v_ssl == true
        # interpret :verify_ssl => true as VERIFY_PEER
        @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
      else
        # otherwise pass through any truthy values
        @ssl_opts[:verify_ssl] = v_ssl
      end
    else
      # interpret all falsy :verify_ssl values as VERIFY_NONE
      @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
    end
  else
    # if :verify_ssl was not passed, default to VERIFY_PEER
    @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
  end

  SSLOptionList.each do |key|
    source_key = ('ssl_' + key).to_sym
    if args.has_key?(source_key)
      @ssl_opts[key.to_sym] = args.fetch(source_key)
    end
  end

  # Set some other default SSL options, but only if we have an HTTPS URI.
  if use_ssl?

    # If there's no CA file, CA path, or cert store provided, use default
    if !ssl_ca_file && !ssl_ca_path && !@ssl_opts.include?(:cert_store)
      @ssl_opts[:cert_store] = self.class.default_ssl_cert_store
    end
  end

  @tf = nil # If you are a raw request, this is your tempfile
  @max_redirects = args[:max_redirects] || 10
  @processed_headers = make_headers headers
  @args = args

  @before_execution_proc = args[:before_execution_proc]
end

Public Instance Methods

cookies() click to toggle source

Render a hash of key => value pairs for cookies in the #cookie_jar that are valid for the #uri. This will not necessarily include all cookies if there are duplicate keys. It's safer to use the #cookie_jar directly if that's a concern.

@see #cookie_jar

@return [Hash]

# File lib/restclient/request.rb, line 222
def cookies
  hash = {}

  @cookie_jar.cookies(uri).each do |c|
    hash[c.name] = c.value
  end

  hash
end
default_headers() click to toggle source
# File lib/restclient/request.rb, line 576
def default_headers
  {
    :accept => '*/*',
    :accept_encoding => 'gzip, deflate',
    :user_agent => RestClient::Platform.default_user_agent,
  }
end
execute(& block) click to toggle source
# File lib/restclient/request.rb, line 142
def execute & block
  # With 2.0.0+, net/http accepts URI objects in requests and handles wrapping
  # IPv6 addresses in [] for use in the Host request header.
  transmit uri, net_http_request_class(method).new(uri, processed_headers), payload, & block
ensure
  payload.close if payload
end
inspect() click to toggle source
# File lib/restclient/request.rb, line 58
def inspect
  "<RestClient::Request @method=#{@method.inspect}, @url=#{@url.inspect}>"
end
log_request() click to toggle source
# File lib/restclient/request.rb, line 528
def log_request
  return unless RestClient.log

  out = []

  out << "RestClient.#{method} #{redacted_url.inspect}"
  out << payload.short_inspect if payload
  out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
  RestClient.log << out.join(', ') + "\n"
end
log_response(res) click to toggle source
# File lib/restclient/request.rb, line 539
def log_response res
  return unless RestClient.log

  size = if @raw_response
           File.size(@tf.path)
         else
           res.body.nil? ? 0 : res.body.size
         end

  RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
end
make_headers(user_headers) click to toggle source

Generate headers for use by a request. Header keys will be stringified using `#stringify_headers` to normalize them as capitalized strings.

The final headers consist of:

- default headers from #default_headers
- user_headers provided here
- headers from the payload object (e.g. Content-Type, Content-Lenth)
- cookie headers from #make_cookie_header

@param [Hash] user_headers User-provided headers to include

@return [Hash<String, String>] A hash of HTTP headers => values

# File lib/restclient/request.rb, line 363
def make_headers(user_headers)
  headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))

  # override headers from the payload (e.g. Content-Type, Content-Length)
  if @payload
    payload_headers = @payload.headers

    # Warn the user if we override any headers that were previously
    # present. This usually indicates that rest-client was passed
    # conflicting information, e.g. if it was asked to render a payload as
    # x-www-form-urlencoded but a Content-Type application/json was
    # also supplied by the user.
    payload_headers.each_pair do |key, val|
      if headers.include?(key) && headers[key] != val
        warn("warning: Overriding #{key.inspect} header " +
             "#{headers.fetch(key).inspect} with #{val.inspect} " +
             "due to payload")
      end
    end

    headers.merge!(payload_headers)
  end

  # merge in cookies
  cookies = make_cookie_header
  if cookies && !cookies.empty?
    if headers['Cookie']
      warn('warning: overriding "Cookie" header with :cookies option')
    end
    headers['Cookie'] = cookies
  end

  headers
end
net_http_do_request(http, req, body=nil, &block) click to toggle source
# File lib/restclient/request.rb, line 443
def net_http_do_request(http, req, body=nil, &block)
  if body && body.respond_to?(:read)
    req.body_stream = body
    return http.request(req, nil, &block)
  else
    return http.request(req, body, &block)
  end
end
net_http_object(hostname, port) click to toggle source
# File lib/restclient/request.rb, line 423
def net_http_object(hostname, port)
  p_uri = proxy_uri

  if p_uri.nil?
    # no proxy set
    Net::HTTP.new(hostname, port)
  elsif !p_uri
    # proxy explicitly set to none
    Net::HTTP.new(hostname, port, nil, nil, nil, nil)
  else
    Net::HTTP.new(hostname, port,
                  p_uri.hostname, p_uri.port, p_uri.user, p_uri.password)

  end
end
net_http_request_class(method) click to toggle source
# File lib/restclient/request.rb, line 439
def net_http_request_class(method)
  Net::HTTP.const_get(method.capitalize, false)
end
normalize_url(url) click to toggle source

Normalize a URL by adding a protocol if none is present.

If the string has no HTTP-like scheme (i.e. scheme followed by '//'), a scheme of 'http' will be added. This mimics the behavior of browsers and user agents like cURL.

@param [String] url A URL string.

@return [String]

# File lib/restclient/request.rb, line 462
def normalize_url(url)
  url = 'http://' + url unless url.match(%r{\A[a-z][a-z0-9+.-]*://})
  url
end
process_url_params(url, headers) click to toggle source

Extract the query parameters and append them to the url

Look through the headers hash for a :params option (case-insensitive, may be string or symbol). If present and the value is a Hash or RestClient::ParamsArray, delete the key/value pair from the headers hash and encode the value into a query string. Append this query string to the URL and return the resulting URL.

@param [String] url @param [Hash] headers An options/headers hash to process. Mutation

warning: the params key may be removed if present!

@return [String] resulting url with query string

# File lib/restclient/request.rb, line 182
def process_url_params(url, headers)
  url_params = nil

  # find and extract/remove "params" key if the value is a Hash/ParamsArray
  headers.delete_if do |key, value|
    if key.to_s.downcase == 'params' &&
        (value.is_a?(Hash) || value.is_a?(RestClient::ParamsArray))
      if url_params
        raise ArgumentError.new("Multiple 'params' options passed")
      end
      url_params = value
      true
    else
      false
    end
  end

  # build resulting URL with query string
  if url_params && !url_params.empty?
    query_string = RestClient::Utils.encode_query_string(url_params)

    if url.include?('?')
      url + '&' + query_string
    else
      url + '?' + query_string
    end
  else
    url
  end
end
proxy_uri() click to toggle source

The proxy URI for this request. If `:proxy` was provided on this request, use it over `RestClient.proxy`.

Return false if a proxy was explicitly set and is falsy.

@return [URI, false, nil]

# File lib/restclient/request.rb, line 405
def proxy_uri
  if defined?(@proxy)
    if @proxy
      URI.parse(@proxy)
    else
      false
    end
  elsif RestClient.proxy_set?
    if RestClient.proxy
      URI.parse(RestClient.proxy)
    else
      false
    end
  else
    nil
  end
end
redacted_uri() click to toggle source
# File lib/restclient/request.rb, line 514
def redacted_uri
  if uri.password
    sanitized_uri = uri.dup
    sanitized_uri.password = 'REDACTED'
    sanitized_uri
  else
    uri
  end
end
redacted_url() click to toggle source
# File lib/restclient/request.rb, line 524
def redacted_url
  redacted_uri.to_s
end
stringify_headers(headers) click to toggle source

Return a hash of headers whose keys are capitalized strings

# File lib/restclient/request.rb, line 552
def stringify_headers headers
  headers.inject({}) do |result, (key, value)|
    if key.is_a? Symbol
      key = key.to_s.split(/_/).map(&:capitalize).join('-')
    end
    if 'CONTENT-TYPE' == key.upcase
      result[key] = maybe_convert_extension(value.to_s)
    elsif 'ACCEPT' == key.upcase
      # Accept can be composed of several comma-separated values
      if value.is_a? Array
        target_values = value
      else
        target_values = value.to_s.split ','
      end
      result[key] = target_values.map { |ext|
        maybe_convert_extension(ext.to_s.strip)
      }.join(', ')
    else
      result[key] = value.to_s
    end
    result
  end
end
use_ssl?() click to toggle source

Return true if the request URI will use HTTPS.

@return [Boolean]

# File lib/restclient/request.rb, line 164
def use_ssl?
  uri.is_a?(URI::HTTPS)
end
verify_ssl() click to toggle source

SSL-related options

# File lib/restclient/request.rb, line 151
def verify_ssl
  @ssl_opts.fetch(:verify_ssl)
end

Private Instance Methods

fetch_body(http_response) click to toggle source
# File lib/restclient/request.rb, line 768
def fetch_body(http_response)
  if @raw_response
    # Taken from Chef, which as in turn...
    # Stolen from http://www.ruby-forum.com/topic/166423
    # Kudos to _why!
    @tf = Tempfile.new('rest-client.')
    @tf.binmode
    size, total = 0, http_response['Content-Length'].to_i
    http_response.read_body do |chunk|
      @tf.write chunk
      size += chunk.size
      if RestClient.log
        if size == 0
          RestClient.log << "%s %s done (0 length file)\n" % [@method, @url]
        elsif total == 0
          RestClient.log << "%s %s (zero content length)\n" % [@method, @url]
        else
          RestClient.log << "%s %s %d%% done (%d of %d)\n" % [@method, @url, (size * 100) / total, size, total]
        end
      end
    end
    @tf.close
    @tf
  else
    http_response.read_body
  end
  http_response
end
maybe_convert_extension(ext) click to toggle source

Given a MIME type or file extension, return either a MIME type or, if none is found, the input unchanged.

>> maybe_convert_extension('json')
=> 'application/json'

>> maybe_convert_extension('unknown')
=> 'unknown'

>> maybe_convert_extension('application/xml')
=> 'application/xml'

@param ext [String]

@return [String]

# File lib/restclient/request.rb, line 834
def maybe_convert_extension(ext)
  unless ext =~ /\A[a-zA-Z0-9_@-]+\z/
    # Don't look up strings unless they look like they could be a file
    # extension known to mime-types.
    #
    # There currently isn't any API public way to look up extensions
    # directly out of MIME::Types, but the type_for() method only strips
    # off after a period anyway.
    return ext
  end

  types = MIME::Types.type_for(ext)
  if types.empty?
    ext
  else
    types.first.content_type
  end
end
normalize_method(method) click to toggle source

Parse a method and return a normalized string version.

Raise ArgumentError if the method is falsy, but otherwise do no validation.

@param method [String, Symbol]

@return [String]

@see #net_http_request_class

# File lib/restclient/request.rb, line 637
def normalize_method(method)
  raise ArgumentError.new('must pass :method') unless method
  method.to_s.downcase
end
parse_url_with_auth!(url) click to toggle source

Parse the `@url` string into a URI object and save it as `@uri`. Also save any basic auth user or password as @user and @password. If no auth info was passed, check for credentials in a Netrc file.

@param [String] url A URL string.

@return [URI]

@raise URI::InvalidURIError on invalid URIs

# File lib/restclient/request.rb, line 596
def parse_url_with_auth!(url)
  uri = URI.parse(url)

  if uri.hostname.nil?
    raise URI::InvalidURIError.new("bad URI(no host provided): #{url}")
  end

  @user = CGI.unescape(uri.user) if uri.user
  @password = CGI.unescape(uri.password) if uri.password
  if !@user && !@password
    @user, @password = Netrc.read[uri.hostname]
  end

  @uri = uri
end
parser() click to toggle source
# File lib/restclient/request.rb, line 814
def parser
  URI.const_defined?(:Parser) ? URI::Parser.new : URI
end
print_verify_callback_warnings() click to toggle source
process_result(res, & block) click to toggle source
# File lib/restclient/request.rb, line 797
def process_result res, & block
  if @raw_response
    # We don't decode raw requests
    response = RawResponse.new(@tf, res, self)
  else
    decoded = Request.decode(res['content-encoding'], res.body)
    response = Response.create(decoded, res, self)
  end

  if block_given?
    block.call(response, self, res, & block)
  else
    response.return!(&block)
  end

end
setup_credentials(req) click to toggle source
# File lib/restclient/request.rb, line 764
def setup_credentials(req)
  req.basic_auth(user, password) if user && !headers.has_key?("Authorization")
end
transmit(uri, req, payload, & block) click to toggle source
# File lib/restclient/request.rb, line 642
def transmit uri, req, payload, & block

  # We set this to true in the net/http block so that we can distinguish
  # read_timeout from open_timeout. Now that we only support Ruby 2.0+,
  # this is only needed for Timeout exceptions thrown outside of Net::HTTP.
  established_connection = false

  setup_credentials req

  net = net_http_object(uri.hostname, uri.port)
  net.use_ssl = uri.is_a?(URI::HTTPS)
  net.ssl_version = ssl_version if ssl_version
  net.ciphers = ssl_ciphers if ssl_ciphers

  net.verify_mode = verify_ssl

  net.cert = ssl_client_cert if ssl_client_cert
  net.key = ssl_client_key if ssl_client_key
  net.ca_file = ssl_ca_file if ssl_ca_file
  net.ca_path = ssl_ca_path if ssl_ca_path
  net.cert_store = ssl_cert_store if ssl_cert_store

  # We no longer rely on net.verify_callback for the main SSL verification
  # because it's not well supported on all platforms (see comments below).
  # But do allow users to set one if they want.
  if ssl_verify_callback
    net.verify_callback = ssl_verify_callback

    # Hilariously, jruby only calls the callback when cert_store is set to
    # something, so make sure to set one.
    # https://github.com/jruby/jruby/issues/597
    if RestClient::Platform.jruby?
      net.cert_store ||= OpenSSL::X509::Store.new
    end

    if ssl_verify_callback_warnings != false
      if print_verify_callback_warnings
        warn('pass :ssl_verify_callback_warnings => false to silence this')
      end
    end
  end

  if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
    warn('WARNING: OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE')
    warn('This dangerous monkey patch leaves you open to MITM attacks!')
    warn('Try passing :verify_ssl => false instead.')
  end

  if defined? @read_timeout
    if @read_timeout == -1
      warn 'Deprecated: to disable timeouts, please use nil instead of -1'
      @read_timeout = nil
    end
    net.read_timeout = @read_timeout
  end
  if defined? @open_timeout
    if @open_timeout == -1
      warn 'Deprecated: to disable timeouts, please use nil instead of -1'
      @open_timeout = nil
    end
    net.open_timeout = @open_timeout
  end

  RestClient.before_execution_procs.each do |before_proc|
    before_proc.call(req, args)
  end

  if @before_execution_proc
    @before_execution_proc.call(req, args)
  end

  log_request

  net.start do |http|
    established_connection = true

    if @block_response
      net_http_do_request(http, req, payload, &@block_response)
    else
      res = net_http_do_request(http, req, payload) { |http_response|
        fetch_body(http_response)
      }
      log_response res
      process_result res, & block
    end
  end
rescue EOFError
  raise RestClient::ServerBrokeConnection
rescue Net::OpenTimeout => err
  raise RestClient::Exceptions::OpenTimeout.new(nil, err)
rescue Net::ReadTimeout => err
  raise RestClient::Exceptions::ReadTimeout.new(nil, err)
rescue Timeout::Error, Errno::ETIMEDOUT => err
  # handling for non-Net::HTTP timeouts
  if established_connection
    raise RestClient::Exceptions::ReadTimeout.new(nil, err)
  else
    raise RestClient::Exceptions::OpenTimeout.new(nil, err)
  end

rescue OpenSSL::SSL::SSLError => error
  # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just
  # pass through OpenSSL::SSL::SSLError directly.
  #
  # Exceptions in verify_callback are ignored [1], and jruby doesn't support
  # it at all [2]. RestClient has to catch OpenSSL::SSL::SSLError and either
  # re-throw it as is, or throw SSLCertificateNotVerified based on the
  # contents of the message field of the original exception.
  #
  # The client has to handle OpenSSL::SSL::SSLError exceptions anyway, so
  # we shouldn't make them handle both OpenSSL and RestClient exceptions.
  #
  # [1] https://github.com/ruby/ruby/blob/89e70fe8e7/ext/openssl/ossl.c#L238
  # [2] https://github.com/jruby/jruby/issues/597

  if error.message.include?("certificate verify failed")
    raise SSLCertificateNotVerified.new(error.message)
  else
    raise error
  end
end