def request(verb, path, data = {}, params = {}, request_options = {})
      log.info  "#{verb.to_s.upcase} #{path}..."
      log.debug "Chef flavor: #{flavor.inspect}"

      # Build the URI and request object from the given information
      if [:delete, :get].include?(verb)
        uri = build_uri(verb, path, data)
      else
        uri = build_uri(verb, path, params)
      end
      request = class_for_request(verb).new(uri.request_uri)

      # Add request headers
      add_request_headers(request)

      # Setup PATCH/POST/PUT
      if [:patch, :post, :put].include?(verb)
        if data.respond_to?(:read)
          log.info "Detected file/io presence"
          request.body_stream = data
        elsif data.is_a?(Hash)
          # If any of the values in the hash are File-like, assume this is a
          # multi-part post
          if data.values.any? { |value| value.respond_to?(:read) }
            log.info "Detected multipart body"

            multipart = Multipart::Body.new(data)

            log.debug "Content-Type: #{multipart.content_type}"
            log.debug "Content-Length: #{multipart.content_length}"

            request.content_length = multipart.content_length
            request.content_type   = multipart.content_type

            request.body_stream    = multipart.stream
          else
            log.info "Detected form data"
            request.form_data = data
          end
        else
          log.info "Detected regular body"
          request.body = data
        end
      end

      # Sign the request
      if request_options[:sign] == false
        log.info "Skipping signed header authentication (user requested)..."
      else
        add_signing_headers(verb, uri.path, request)
      end

      # Create the HTTP connection object - since the proxy information defaults
      # to +nil+, we can just pass it to the initializer method instead of doing
      # crazy strange conditionals.
      connection = Net::HTTP.new(uri.host, uri.port,
        proxy_address, proxy_port, proxy_username, proxy_password)

      # Large or filtered result sets can take a long time to return, so allow
      # setting a longer read_timeout for our client if we want to make an
      # expensive request.
      connection.read_timeout = read_timeout if read_timeout

      # Apply SSL, if applicable
      if uri.scheme == 'https'
        # Turn on SSL
        connection.use_ssl = true

        # Custom pem files, no problem!
        if ssl_pem_file
          pem = File.read(ssl_pem_file)
          connection.cert = OpenSSL::X509::Certificate.new(pem)
          connection.key = OpenSSL::PKey::RSA.new(pem)
          connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
        end

        # Naughty, naughty, naughty! Don't blame when when someone hops in
        # and executes a MITM attack!
        unless ssl_verify
          log.warn "Disabling SSL verification..."
          log.warn "Neither ChefAPI nor the maintainers are responsible for " \
            "damages incurred as a result of disabling SSL verification. " \
            "Please use this with extreme caution, or consider specifying " \
            "a custom certificate using `config.ssl_pem_file'."
          connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
        end
      end

      # Create a connection using the block form, which will ensure the socket
      # is properly closed in the event of an error.
      connection.start do |http|
        response = http.request(request)

        log.debug "Raw response:"
        log.debug response.body

        case response
        when Net::HTTPRedirection
          redirect = URI.parse(response['location']).to_s
          log.debug "Performing HTTP redirect to #{redirect}"
          request(verb, redirect, data)
        when Net::HTTPSuccess
          success(response)
        else
          error(response)
        end
      end
    rescue SocketError, Errno::ECONNREFUSED, EOFError
      log.warn "Unable to reach the Chef Server"
      raise Error::HTTPServerUnavailable.new
    end
def request(verb, path, data = {}, params = {}, request_options = {})
      log.info  "#{verb.to_s.upcase} #{path}..."
      log.debug "Chef flavor: #{flavor.inspect}"

      # Build the URI and request object from the given information
      if [:delete, :get].include?(verb)
        uri = build_uri(verb, path, data)
      else
        uri = build_uri(verb, path, params)
      end
      request = class_for_request(verb).new(uri.request_uri)

      # Add request headers
      add_request_headers(request)

      # Setup PATCH/POST/PUT
      if [:patch, :post, :put].include?(verb)
        if data.respond_to?(:read)
          log.info "Detected file/io presence"
          request.body_stream = data
        elsif data.is_a?(Hash)
          # If any of the values in the hash are File-like, assume this is a
          # multi-part post
          if data.values.any? { |value| value.respond_to?(:read) }
            log.info "Detected multipart body"

            multipart = Multipart::Body.new(data)

            log.debug "Content-Type: #{multipart.content_type}"
            log.debug "Content-Length: #{multipart.content_length}"

            request.content_length = multipart.content_length
            request.content_type   = multipart.content_type

            request.body_stream    = multipart.stream
          else
            log.info "Detected form data"
            request.form_data = data
          end
        else
          log.info "Detected regular body"
          request.body = data
        end
      end

      # Sign the request
      if request_options[:sign] == false
        log.info "Skipping signed header authentication (user requested)..."
      else
        add_signing_headers(verb, uri.path, request)
      end

      # Create the HTTP connection object - since the proxy information defaults
      # to +nil+, we can just pass it to the initializer method instead of doing
      # crazy strange conditionals.
      connection = Net::HTTP.new(uri.host, uri.port,
        proxy_address, proxy_port, proxy_username, proxy_password)

      # Large or filtered result sets can take a long time to return, so allow
      # setting a longer read_timeout for our client if we want to make an
      # expensive request.
      connection.read_timeout = read_timeout if read_timeout

      # Apply SSL, if applicable
      if uri.scheme == 'https'
        # Turn on SSL
        connection.use_ssl = true

        # Custom pem files, no problem!
        if ssl_pem_file
          pem = File.read(ssl_pem_file)
          connection.cert = OpenSSL::X509::Certificate.new(pem)
          connection.key = OpenSSL::PKey::RSA.new(pem)
          connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
        end

        # Naughty, naughty, naughty! Don't blame when when someone hops in
        # and executes a MITM attack!
        unless ssl_verify
          log.warn "Disabling SSL verification..."
          log.warn "Neither ChefAPI nor the maintainers are responsible for " \
            "damages incurred as a result of disabling SSL verification. " \
            "Please use this with extreme caution, or consider specifying " \
            "a custom certificate using `config.ssl_pem_file'."
          connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
        end
      end

      # Create a connection using the block form, which will ensure the socket
      # is properly closed in the event of an error.
      connection.start do |http|
        response = http.request(request)

        log.debug "Raw response:"
        log.debug response.body

        case response
        when Net::HTTPRedirection
          redirect = URI.parse(response['location']).to_s
          log.debug "Performing HTTP redirect to #{redirect}"
          request(verb, redirect, data)
        when Net::HTTPSuccess
          success(response)
        else
          error(response)
        end
      end
    rescue SocketError, Errno::ECONNREFUSED, EOFError
      log.warn "Unable to reach the Chef Server"
      raise Error::HTTPServerUnavailable.new
    end
Make an HTTP request with the given verb, data, params, and headers. If
 the response has a return type of JSON, the JSON is automatically parsed
 and returned as a hash; otherwise it is returned as a string.

 @raise [Error::HTTPError]
   if the request is not an HTTP 200 OK

 @param [Symbol] verb
   the lowercase symbol of the HTTP verb (e.g. :get, :delete)
 @param [String] path
   the absolute or relative path from {Defaults.endpoint} to make the
   request against
 @param [#read, Hash, nil] data
   the data to use (varies based on the +verb+)
 @param [Hash] params
   the params to use for :patch, :post, :put
 @param [Hash] request_options
   the list of options/configurables for the actual request

 @option request_options [true, false] :sign (default: +true+)
   whether to sign the request using mixlib authentication headers

 @return [String, Hash]
   the response body