Class: RestClient::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/restclient/request.rb

Overview

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:

  • :method

  • :url

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

  • :headers a hash containing the request headers

  • :cookies may be a Hash=> String of cookie values, an

    Array<HTTP::Cookie>, or an HTTP::CookieJar containing cookies. These
    will be added to a cookie jar before the request is sent.
    
  • :user and :password for basic auth, will be replaced by a user/password available in the :url

  • :block_response call the provided block with the HTTPResponse as parameter

  • :raw_response return a low-level RawResponse instead of a Response

  • :max_redirects maximum number of redirections (default to 10)

  • :proxy An HTTP proxy URI to use for this request. Any value here (including nil) will override RestClient.proxy.

  • :verify_ssl enable ssl verification, possible values are constants from

    OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER
    
  • :read_timeout and :open_timeout are how long to wait for a response and

    to open a connection, in seconds. Pass nil to disable the timeout.
    
  • :timeout can be used to set both timeouts

  • :ssl_client_cert, :ssl_client_key, :ssl_ca_file, :ssl_ca_path,

    :ssl_cert_store, :ssl_verify_callback, :ssl_verify_callback_warnings
    
  • :ssl_version specifies the SSL version for the underlying Net::HTTP connection

  • :ssl_ciphers sets SSL ciphers for the connection. See

    OpenSSL::SSL::SSLContext#ciphers=
    
  • :before_execution_proc a Proc to call before executing the request. This

    proc, like procs from RestClient.before_execution_procs, will be
    called with the HTTP request and request params.
    

Constant Summary collapse

SSLOptionList =
%w{client_cert client_key ca_file ca_path cert_store
version ciphers verify_callback verify_callback_warnings}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Request

Returns a new instance of Request.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/restclient/request.rb', line 68

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

  @stream_log_percent = args[:stream_log_percent] || 10
  if @stream_log_percent <= 0 || @stream_log_percent > 100
    raise ArgumentError.new(
      "Invalid :stream_log_percent #{@stream_log_percent.inspect}")
  end

  @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

  @log = args[:log]
  @max_redirects = args[:max_redirects] || 10
  @processed_headers = make_headers headers
  @processed_headers_lowercase = Hash[@processed_headers.map {|k, v| [k.downcase, v]}]
  @args = args

  @before_execution_proc = args[:before_execution_proc]
end

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



49
50
51
# File 'lib/restclient/request.rb', line 49

def args
  @args
end

#headersObject (readonly)

Returns the value of attribute headers.



49
50
51
# File 'lib/restclient/request.rb', line 49

def headers
  @headers
end

#max_redirectsObject (readonly)

Returns the value of attribute max_redirects.



49
50
51
# File 'lib/restclient/request.rb', line 49

def max_redirects
  @max_redirects
end

#methodObject (readonly)

Returns the value of attribute method.



49
50
51
# File 'lib/restclient/request.rb', line 49

def method
  @method
end

#open_timeoutObject (readonly)

Returns the value of attribute open_timeout.



49
50
51
# File 'lib/restclient/request.rb', line 49

def open_timeout
  @open_timeout
end

#passwordObject (readonly)

Returns the value of attribute password.



49
50
51
# File 'lib/restclient/request.rb', line 49

def password
  @password
end

#payloadObject (readonly)

Returns the value of attribute payload.



49
50
51
# File 'lib/restclient/request.rb', line 49

def payload
  @payload
end

#processed_headersObject (readonly)

Returns the value of attribute processed_headers.



49
50
51
# File 'lib/restclient/request.rb', line 49

def processed_headers
  @processed_headers
end

#proxyObject (readonly)

Returns the value of attribute proxy.



49
50
51
# File 'lib/restclient/request.rb', line 49

def proxy
  @proxy
end

#raw_responseObject (readonly)

Returns the value of attribute raw_response.



49
50
51
# File 'lib/restclient/request.rb', line 49

def raw_response
  @raw_response
end

#read_timeoutObject (readonly)

Returns the value of attribute read_timeout.



49
50
51
# File 'lib/restclient/request.rb', line 49

def read_timeout
  @read_timeout
end

#redirection_historyObject

An array of previous redirection responses



55
56
57
# File 'lib/restclient/request.rb', line 55

def redirection_history
  @redirection_history
end

#ssl_optsObject (readonly)

Returns the value of attribute ssl_opts.



49
50
51
# File 'lib/restclient/request.rb', line 49

def ssl_opts
  @ssl_opts
end

#uriObject (readonly)

Returns the value of attribute uri.



49
50
51
# File 'lib/restclient/request.rb', line 49

def uri
  @uri
end

#urlObject (readonly)

Returns the value of attribute url.



49
50
51
# File 'lib/restclient/request.rb', line 49

def url
  @url
end

#userObject (readonly)

Returns the value of attribute user.



49
50
51
# File 'lib/restclient/request.rb', line 49

def user
  @user
end

Class Method Details

.default_ssl_cert_storeOpenSSL::X509::Store

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.

Returns:

  • (OpenSSL::X509::Store)


496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/restclient/request.rb', line 496

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) ⇒ Object



57
58
59
# File 'lib/restclient/request.rb', line 57

def self.execute(args, & block)
  new(args).execute(& block)
end

Instance Method Details

Returns:

  • (HTTP::CookieJar)


246
247
248
# File 'lib/restclient/request.rb', line 246

def cookie_jar
  @cookie_jar
end

#cookiesHash

Render a hash of key => value pairs for cookies in the Request#cookie_jar that are valid for the Request#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.

Returns:

  • (Hash)

See Also:



235
236
237
238
239
240
241
242
243
# File 'lib/restclient/request.rb', line 235

def cookies
  hash = {}

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

  hash
end

#default_headersHash<Symbol, String>

Default headers set by RestClient. In addition to these headers, servers will receive headers set by Net::HTTP, such as Accept-Encoding and Host.

Returns:

  • (Hash<Symbol, String>)


581
582
583
584
585
586
# File 'lib/restclient/request.rb', line 581

def default_headers
  {
    :accept => '*/*',
    :user_agent => RestClient::Platform.default_user_agent,
  }
end

#execute(&block) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/restclient/request.rb', line 155

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

#inspectObject



64
65
66
# File 'lib/restclient/request.rb', line 64

def inspect
  "<RestClient::Request @method=#{@method.inspect}, @url=#{@url.inspect}>"
end

#logObject

Default to the global logger if there’s not a request-specific one



531
532
533
# File 'lib/restclient/request.rb', line 531

def log
  @log || RestClient.log
end

#log_requestObject



535
536
537
538
539
540
541
542
543
544
# File 'lib/restclient/request.rb', line 535

def log_request
  return unless 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(", ")
  log << out.join(', ') + "\n"
end

Render a Cookie HTTP request header from the contents of the @cookie_jar, or nil if the jar is empty.

Returns:

  • (String, nil)

See Also:



257
258
259
260
261
262
263
264
# File 'lib/restclient/request.rb', line 257

def make_cookie_header
  return nil if cookie_jar.nil?

  arr = cookie_jar.cookies(url)
  return nil if arr.empty?

  return HTTP::Cookie.cookie_value(arr)
end

#make_headers(user_headers) ⇒ Hash<String, String>

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

BUG: stringify_headers does not alter the capitalization of headers that are passed as strings, it only normalizes those passed as symbols. This behavior will probably remain for a while for compatibility, but it means that the warnings that attempt to detect accidental header overrides may not always work. github.com/rest-client/rest-client/issues/599

Parameters:

  • user_headers (Hash)

    User-provided headers to include

Returns:

  • (Hash<String, String>)

    A hash of HTTP headers => values



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/restclient/request.rb', line 383

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) ⇒ Object



463
464
465
466
467
468
469
470
# File 'lib/restclient/request.rb', line 463

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) ⇒ Object



443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/restclient/request.rb', line 443

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) ⇒ Object



459
460
461
# File 'lib/restclient/request.rb', line 459

def net_http_request_class(method)
  Net::HTTP.const_get(method.capitalize, false)
end

#normalize_url(url) ⇒ String

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.

Parameters:

  • url (String)

    A URL string.

Returns:

  • (String)


482
483
484
485
# File 'lib/restclient/request.rb', line 482

def normalize_url(url)
  url = 'http://' + url unless url.match(%r{\A[a-z][a-z0-9+.-]*://}i)
  url
end

Process cookies passed as hash or as HTTP::CookieJar. For backwards compatibility, these may be passed as a :cookies option masquerading inside the headers hash. To avoid confusion, if :cookies is passed in both headers and Request#initialize, raise an error.

:cookies may be a:

  • Hash=> String

  • Array<HTTP::Cookie>

  • HTTP::CookieJar

Passing as a hash:

Keys may be symbols or strings. Values must be strings.
Infer the domain name from the request URI and allow subdomains (as
though '.example.com' had been set in a Set-Cookie header). Assume a
path of '/'.

  RestClient::Request.new(url: 'http://example.com', method: :get,
    :cookies => {:foo => 'Value', 'bar' => '123'}
  )

results in cookies as though set from the server by:

Set-Cookie: foo=Value; Domain=.example.com; Path=/
Set-Cookie: bar=123; Domain=.example.com; Path=/

which yields a client cookie header of:

Cookie: foo=Value; bar=123

Passing as HTTP::CookieJar, which will be passed through directly:

jar = HTTP::CookieJar.new
jar.add(HTTP::Cookie.new('foo', 'Value', domain: 'example.com',
                         path: '/', for_domain: false))

RestClient::Request.new(..., :cookies => jar)

infer the domain name for cookies passed as strings in a hash. To avoid this implicit behavior, pass a full cookie jar or use HTTP::Cookie hash values.

Parameters:

  • uri (URI::HTTP)

    The URI for the request. This will be used to

  • headers (Hash)

    The headers hash from which to pull the :cookies option. MUTATION NOTE: This key will be deleted from the hash if present.

  • args (Hash)

    The options passed to Request#initialize. This hash will be used as another potential source for the :cookies key. These args will not be mutated.

Returns:

  • (HTTP::CookieJar)

    A cookie jar containing the parsed cookies.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/restclient/request.rb', line 314

def process_cookie_args!(uri, headers, args)

  # Avoid ambiguity in whether options from headers or options from
  # Request#initialize should take precedence by raising ArgumentError when
  # both are present. Prior versions of rest-client claimed to give
  # precedence to init options, but actually gave precedence to headers.
  # Avoid that mess by erroring out instead.
  if headers[:cookies] && args[:cookies]
    raise ArgumentError.new(
      "Cannot pass :cookies in Request.new() and in headers hash")
  end

  cookies_data = headers.delete(:cookies) || args[:cookies]

  # return copy of cookie jar as is
  if cookies_data.is_a?(HTTP::CookieJar)
    return cookies_data.dup
  end

  # convert cookies hash into a CookieJar
  jar = HTTP::CookieJar.new

  (cookies_data || []).each do |key, val|

    # Support for Array<HTTP::Cookie> mode:
    # If key is a cookie object, add it to the jar directly and assert that
    # there is no separate val.
    if key.is_a?(HTTP::Cookie)
      if val
        raise ArgumentError.new("extra cookie val: #{val.inspect}")
      end

      jar.add(key)
      next
    end

    if key.is_a?(Symbol)
      key = key.to_s
    end

    # assume implicit domain from the request URI, and set for_domain to
    # permit subdomains
    jar.add(HTTP::Cookie.new(key, val, domain: uri.hostname.downcase,
                             path: '/', for_domain: true))
  end

  jar
end

#process_url_params(url, headers) ⇒ String

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.

Parameters:

  • url (String)
  • headers (Hash)

    An options/headers hash to process. Mutation warning: the params key may be removed if present!

Returns:

  • (String)

    resulting url with query string



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/restclient/request.rb', line 195

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_uriURI, ...

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.

Returns:

  • (URI, false, nil)


425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/restclient/request.rb', line 425

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_uriObject



516
517
518
519
520
521
522
523
524
# File 'lib/restclient/request.rb', line 516

def redacted_uri
  if uri.password
    sanitized_uri = uri.dup
    sanitized_uri.password = 'REDACTED'
    sanitized_uri
  else
    uri
  end
end

#redacted_urlObject



526
527
528
# File 'lib/restclient/request.rb', line 526

def redacted_url
  redacted_uri.to_s
end

#stringify_headers(headers) ⇒ Object

Return a hash of headers whose keys are capitalized strings

BUG: stringify_headers does not fix the capitalization of headers that are already Strings. Leaving this behavior as is for now for backwards compatibility. github.com/rest-client/rest-client/issues/599



553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/restclient/request.rb', line 553

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?Boolean

Return true if the request URI will use HTTPS.

Returns:

  • (Boolean)


177
178
179
# File 'lib/restclient/request.rb', line 177

def use_ssl?
  uri.is_a?(URI::HTTPS)
end

#verify_sslObject

SSL-related options



164
165
166
# File 'lib/restclient/request.rb', line 164

def verify_ssl
  @ssl_opts.fetch(:verify_ssl)
end