def request(verb, path, data = {}, headers = {}, &block)
      # Build the URI and request object from the given information
      uri = build_uri(verb, path, data)
      request = class_for_request(verb).new(uri.request_uri)

      # Add headers
      default_headers.merge(headers).each do |key, value|
        request.add_field(key, value)
      end

      # Add basic authentication
      if username && password
        request.basic_auth(username, password)
      elsif api_key
        request.add_field("X-JFrog-Art-Api", api_key)
      end

      # Setup PATCH/POST/PUT
      if [:patch, :post, :put].include?(verb)
        if data.respond_to?(:read)
          request.content_length = data.size
          request.body_stream = data
        elsif data.is_a?(Hash)
          request.form_data = data
        else
          request.body = data
        end
      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)

      # The artifacts being uploaded might be large, so there’s a good chance
      # we'll need to bump this higher than the `Net::HTTP` default of 60
      # seconds.
      connection.read_timeout = read_timeout

      # Apply SSL, if applicable
      if uri.scheme == "https"
        require "net/https" unless defined?(Net::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
          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|

        if block_given?
          http.request(request) do |response|
            response.read_body do |chunk|
              yield chunk
            end
          end
        else
          response = http.request(request)

          case response
          when Net::HTTPRedirection
            redirect = URI.parse(response["location"])
            request(verb, redirect, data, headers)
          when Net::HTTPSuccess
            success(response)
          else
            error(response)
          end
        end
      end
    rescue SocketError, Errno::ECONNREFUSED, EOFError
      raise Error::ConnectionError.new(endpoint)
    end
def request(verb, path, data = {}, headers = {}, &block)
      # Build the URI and request object from the given information
      uri = build_uri(verb, path, data)
      request = class_for_request(verb).new(uri.request_uri)

      # Add headers
      default_headers.merge(headers).each do |key, value|
        request.add_field(key, value)
      end

      # Add basic authentication
      if username && password
        request.basic_auth(username, password)
      elsif api_key
        request.add_field("X-JFrog-Art-Api", api_key)
      end

      # Setup PATCH/POST/PUT
      if [:patch, :post, :put].include?(verb)
        if data.respond_to?(:read)
          request.content_length = data.size
          request.body_stream = data
        elsif data.is_a?(Hash)
          request.form_data = data
        else
          request.body = data
        end
      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)

      # The artifacts being uploaded might be large, so there’s a good chance
      # we'll need to bump this higher than the `Net::HTTP` default of 60
      # seconds.
      connection.read_timeout = read_timeout

      # Apply SSL, if applicable
      if uri.scheme == "https"
        require "net/https" unless defined?(Net::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
          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|

        if block_given?
          http.request(request) do |response|
            response.read_body do |chunk|
              yield chunk
            end
          end
        else
          response = http.request(request)

          case response
          when Net::HTTPRedirection
            redirect = URI.parse(response["location"])
            request(verb, redirect, data, headers)
          when Net::HTTPSuccess
            success(response)
          else
            error(response)
          end
        end
      end
    rescue SocketError, Errno::ECONNREFUSED, EOFError
      raise Error::ConnectionError.new(endpoint)
    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. If a block
 is provided the response body is yielded in chunks/fragments as it is
 read from the undelrying socket.

 @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] headers
   the list of headers to use

 @yield [chunk] Partial piece of response body

 @return [String, Hash]
   the response body