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.



62
63
64
65
66
67
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
# 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

Instance Attribute Details

#argsObject (readonly)

Returns the value of attribute args.



43
44
45
# File 'lib/restclient/request.rb', line 43

def args
  @args
end

#headersObject (readonly)

Returns the value of attribute headers.



43
44
45
# File 'lib/restclient/request.rb', line 43

def headers
  @headers
end

#max_redirectsObject (readonly)

Returns the value of attribute max_redirects.



43
44
45
# File 'lib/restclient/request.rb', line 43

def max_redirects
  @max_redirects
end

#methodObject (readonly)

Returns the value of attribute method.



43
44
45
# File 'lib/restclient/request.rb', line 43

def method
  @method
end

#open_timeoutObject (readonly)

Returns the value of attribute open_timeout.



43
44
45
# File 'lib/restclient/request.rb', line 43

def open_timeout
  @open_timeout
end

#passwordObject (readonly)

Returns the value of attribute password.



43
44
45
# File 'lib/restclient/request.rb', line 43

def password
  @password
end

#payloadObject (readonly)

Returns the value of attribute payload.



43
44
45
# File 'lib/restclient/request.rb', line 43

def payload
  @payload
end

#processed_headersObject (readonly)

Returns the value of attribute processed_headers.



43
44
45
# File 'lib/restclient/request.rb', line 43

def processed_headers
  @processed_headers
end

#proxyObject (readonly)

Returns the value of attribute proxy.



43
44
45
# File 'lib/restclient/request.rb', line 43

def proxy
  @proxy
end

#raw_responseObject (readonly)

Returns the value of attribute raw_response.



43
44
45
# File 'lib/restclient/request.rb', line 43

def raw_response
  @raw_response
end

#read_timeoutObject (readonly)

Returns the value of attribute read_timeout.



43
44
45
# File 'lib/restclient/request.rb', line 43

def read_timeout
  @read_timeout
end

#redirection_historyObject

An array of previous redirection responses



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

def redirection_history
  @redirection_history
end

#ssl_optsObject (readonly)

Returns the value of attribute ssl_opts.



43
44
45
# File 'lib/restclient/request.rb', line 43

def ssl_opts
  @ssl_opts
end

#uriObject (readonly)

Returns the value of attribute uri.



43
44
45
# File 'lib/restclient/request.rb', line 43

def uri
  @uri
end

#urlObject (readonly)

Returns the value of attribute url.



43
44
45
# File 'lib/restclient/request.rb', line 43

def url
  @url
end

#userObject (readonly)

Returns the value of attribute user.



43
44
45
# File 'lib/restclient/request.rb', line 43

def user
  @user
end

Class Method Details

.decode(content_encoding, body) ⇒ Object



496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# 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_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)


476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
# 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) ⇒ Object



51
52
53
# File 'lib/restclient/request.rb', line 51

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

Instance Method Details

Returns:

  • (HTTP::CookieJar)


233
234
235
# File 'lib/restclient/request.rb', line 233

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:



222
223
224
225
226
227
228
229
230
# 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_headersObject



576
577
578
579
580
581
582
# 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) ⇒ Object



142
143
144
145
146
147
148
# 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

#inspectObject



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

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

#log_requestObject



528
529
530
531
532
533
534
535
536
537
# 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) ⇒ Object



539
540
541
542
543
544
545
546
547
548
549
# 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

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:



244
245
246
247
248
249
250
251
# File 'lib/restclient/request.rb', line 244

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

Parameters:

  • user_headers (Hash)

    User-provided headers to include

Returns:

  • (Hash<String, String>)

    A hash of HTTP headers => values



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# 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) ⇒ Object



443
444
445
446
447
448
449
450
# 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) ⇒ Object



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# 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) ⇒ Object



439
440
441
# File 'lib/restclient/request.rb', line 439

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)


462
463
464
465
# File 'lib/restclient/request.rb', line 462

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.



301
302
303
304
305
306
307
308
309
310
311
312
313
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
# File 'lib/restclient/request.rb', line 301

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



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# 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_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)


405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# 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_uriObject



514
515
516
517
518
519
520
521
522
# 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_urlObject



524
525
526
# File 'lib/restclient/request.rb', line 524

def redacted_url
  redacted_uri.to_s
end

#stringify_headers(headers) ⇒ Object

Return a hash of headers whose keys are capitalized strings



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# 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?Boolean

Return true if the request URI will use HTTPS.

Returns:

  • (Boolean)


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

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

#verify_sslObject

SSL-related options



151
152
153
# File 'lib/restclient/request.rb', line 151

def verify_ssl
  @ssl_opts.fetch(:verify_ssl)
end