Daily Code Reading #23 – RestClient::Request#execute

After looking at the other RestClient::Resource methods from yesterday, I noticed that they are all almost identical to #get. So today I’m going to dig into the code one level down, RestClient::Request.

The Code

RestClient::Request#execute

1
2
3
4
5
6
7
module RestClient
  class Request
    def self.execute(args, &block)
      new(args).execute &block
    end
  end
end

RestClient::Resource#get calls this method, which is just a wrapper for creating a new Request object and calling it’s #execute method.

RestClient::Request#initialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    def initialize args
      @method = args[:method] or raise ArgumentError, "must pass :method"
      @url = args[:url] or raise ArgumentError, "must pass :url"
      @headers = args[:headers] || {}
      @cookies = @headers.delete(:cookies) || args[:cookies] || {}
      @payload = Payload.generate(args[:payload])
      @user = args[:user]
      @password = args[:password]
      @timeout = args[:timeout]
      @open_timeout = args[:open_timeout]
      @raw_response = args[:raw_response] || false
      @verify_ssl = args[:verify_ssl] || false
      @ssl_client_cert = args[:ssl_client_cert] || nil
      @ssl_client_key = args[:ssl_client_key] || nil
      @ssl_ca_file = args[:ssl_ca_file] || nil
      @tf = nil # If you are a raw request, this is your tempfile
      @processed_headers = make_headers headers
      @args = args
    end

#initialize just checks the args and sets a bunch of attributes. The :method and :url attributes are required, which if you remember #get sets automatically. It’s also good that the original arguments are stored away into @args, I always forget to do that.

RestClient::Request#execute

1
2
3
4
    def execute &block
      uri = parse_url_with_auth(url)
      transmit uri, net_http_request_class(method).new(uri.request_uri, processed_headers), payload, &block
    end

After initialize, the class method #execute calls the object method #execute above. There is a lot happening in these short lines so I need to break it out a bit:

  • the request uri is parsed out using parse_url_with_auth.
  • net_http_request_class converts the :get request into Get which is an actual [net/http](http://www.ensta.fr/~diam/ruby/online/ruby-doc-stdlib/libdoc/net/http/rdoc/classes/Net/HTTP.html) class.
  • then a new Net/HTTP object is created, given the uri and request headers.
  • finally the uri, Net/HTTP request object, payload, and block is passed to #transmit

RestClient::Request#transmit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    def transmit uri, req, payload, &block
      setup_credentials req
 
      net = net_http_class.new(uri.host, uri.port)
      net.use_ssl = uri.is_a?(URI::HTTPS)
      if @verify_ssl == false
        net.verify_mode = OpenSSL::SSL::VERIFY_NONE
      elsif @verify_ssl.is_a? Integer
        net.verify_mode = @verify_ssl
      end
      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.read_timeout = @timeout if @timeout
      net.open_timeout = @open_timeout if @open_timeout
 
      RestClient.before_execution_procs.each do |before_proc|
        before_proc.call(req, args)
      end
 
      log_request
 
      net.start do |http|
        res = http.request(req, payload) { |http_response| fetch_body(http_response) }
        log_response res
        process_result res, &block
      end
    rescue EOFError
      raise RestClient::ServerBrokeConnection
    rescue Timeout::Error
      raise RestClient::RequestTimeout
    end
  end
end

Now I’ve found where the request is sent.

This is one of the core methods of RestClient. It starts with a bit of setup for the credentials and SSL. Sending the actual request is pretty simple and just uses net/http and a few helper methods:

1
2
3
4
5
      net.start do |http|
        res = http.request(req, payload) { |http_response| fetch_body(http_response) }
        log_response res
        process_result res, &block
      end

It also looks like transmit is use two separate net/http objects; req and net. I’m not really sure why they needed two objects, especially since only one of them is actually used to connect to the server.

Now that I’ve seen the Request side, I think I’ll take a look at the response side.