def send_http_request(url, method, headers, body)
      # use upstream proxy
      if @proxy.nil?
        http = Net::HTTP::Proxy(nil).new(
          @url.host,
          @url.port
        )
      elsif @proxy.scheme.eql?('http') || @proxy.scheme.eql?('https')
        http = Net::HTTP::Proxy(
          @proxy.host,
          @proxy.port
        ).new(
          @url.host,
          @url.port
        )
      elsif @proxy.scheme.eql?('socks')
        http = Net::HTTP.SOCKSProxy(
          @proxy.host,
          @proxy.port
        ).new(
          @url.host,
          @url.port
        )
      else
        raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
              'Unsupported upstream proxy specified. Scheme must be http(s) or socks.'
      end

      # set SSL
      if @url.scheme.eql?('https')
        http.use_ssl = true
        http.verify_mode = @insecure ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
      end

      # set socket options
      http.open_timeout = @timeout
      http.read_timeout = @timeout

      # set http request method
      case method
      when 'GET'
        request = Net::HTTP::Get.new(url, headers.to_hash)
      when 'HEAD'
        request = Net::HTTP::Head.new(url, headers.to_hash)
      when 'DELETE'
        request = Net::HTTP::Delete.new(url, headers.to_hash)
      when 'POST'
        request = Net::HTTP::Post.new(url, headers.to_hash)
      when 'PUT'
        request = Net::HTTP::Put.new(url, headers.to_hash)
      when 'OPTIONS'
        request = Net::HTTP::Options.new(url, headers.to_hash)
      else
        logger.info("Request method #{method.inspect} not implemented")
        raise SSRFProxy::HTTP::Error::InvalidClientRequest,
              "Request method #{method.inspect} not implemented"
      end

      # set http request credentials
      request.basic_auth(@user, @pass) unless @user.eql?('') && @pass.eql?('')

      # send http request
      response = {}
      logger.info('Sending request: ' \
                  "#{@url.scheme}://#{@url.host}:#{@url.port}#{url}")
      begin
        unless body.eql?('')
          request.body = body
          logger.info("Using request body: #{request.body.inspect}")
        end
        response = http.request(request)
      rescue Net::HTTPBadResponse, EOFError
        logger.info('Server returned an invalid HTTP response')
        raise SSRFProxy::HTTP::Error::InvalidResponse,
              'Server returned an invalid HTTP response'
      rescue Errno::ECONNREFUSED, Errno::ECONNRESET
        logger.info('Connection failed')
        raise SSRFProxy::HTTP::Error::ConnectionFailed,
              'Connection failed'
      rescue Timeout::Error, Errno::ETIMEDOUT
        logger.info("Connection timed out [#{@timeout}]")
        raise SSRFProxy::HTTP::Error::ConnectionTimeout,
              "Connection timed out [#{@timeout}]"
      rescue => e
        logger.error("Unhandled exception: #{e}")
        raise e
      end

      if response.code.eql?('401')
        if @user.eql?('') && @pass.eql?('')
          logger.warn('Authentication required')
        else
          logger.warn('Authentication failed')
        end
      end

      response
    end
def send_http_request(url, method, headers, body)
      # use upstream proxy
      if @proxy.nil?
        http = Net::HTTP::Proxy(nil).new(
          @url.host,
          @url.port
        )
      elsif @proxy.scheme.eql?('http') || @proxy.scheme.eql?('https')
        http = Net::HTTP::Proxy(
          @proxy.host,
          @proxy.port
        ).new(
          @url.host,
          @url.port
        )
      elsif @proxy.scheme.eql?('socks')
        http = Net::HTTP.SOCKSProxy(
          @proxy.host,
          @proxy.port
        ).new(
          @url.host,
          @url.port
        )
      else
        raise SSRFProxy::HTTP::Error::InvalidUpstreamProxy.new,
              'Unsupported upstream proxy specified. Scheme must be http(s) or socks.'
      end

      # set SSL
      if @url.scheme.eql?('https')
        http.use_ssl = true
        http.verify_mode = @insecure ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
      end

      # set socket options
      http.open_timeout = @timeout
      http.read_timeout = @timeout

      # set http request method
      case method
      when 'GET'
        request = Net::HTTP::Get.new(url, headers.to_hash)
      when 'HEAD'
        request = Net::HTTP::Head.new(url, headers.to_hash)
      when 'DELETE'
        request = Net::HTTP::Delete.new(url, headers.to_hash)
      when 'POST'
        request = Net::HTTP::Post.new(url, headers.to_hash)
      when 'PUT'
        request = Net::HTTP::Put.new(url, headers.to_hash)
      when 'OPTIONS'
        request = Net::HTTP::Options.new(url, headers.to_hash)
      else
        logger.info("Request method #{method.inspect} not implemented")
        raise SSRFProxy::HTTP::Error::InvalidClientRequest,
              "Request method #{method.inspect} not implemented"
      end

      # set http request credentials
      request.basic_auth(@user, @pass) unless @user.eql?('') && @pass.eql?('')

      # send http request
      response = {}
      logger.info('Sending request: ' \
                  "#{@url.scheme}://#{@url.host}:#{@url.port}#{url}")
      begin
        unless body.eql?('')
          request.body = body
          logger.info("Using request body: #{request.body.inspect}")
        end
        response = http.request(request)
      rescue Net::HTTPBadResponse, EOFError
        logger.info('Server returned an invalid HTTP response')
        raise SSRFProxy::HTTP::Error::InvalidResponse,
              'Server returned an invalid HTTP response'
      rescue Errno::ECONNREFUSED, Errno::ECONNRESET
        logger.info('Connection failed')
        raise SSRFProxy::HTTP::Error::ConnectionFailed,
              'Connection failed'
      rescue Timeout::Error, Errno::ETIMEDOUT
        logger.info("Connection timed out [#{@timeout}]")
        raise SSRFProxy::HTTP::Error::ConnectionTimeout,
              "Connection timed out [#{@timeout}]"
      rescue => e
        logger.error("Unhandled exception: #{e}")
        raise e
      end

      if response.code.eql?('401')
        if @user.eql?('') && @pass.eql?('')
          logger.warn('Authentication required')
        else
          logger.warn('Authentication failed')
        end
      end

      response
    end
Send HTTP request to the SSRF server

 @param [String] url URI to fetch
 @param [String] method HTTP request method
 @param [Hash] headers HTTP request headers
 @param [String] body HTTP request body

 @raise [SSRFProxy::HTTP::Error::InvalidSsrfRequestMethod]
        Invalid SSRF request method specified.
        Method must be GET/HEAD/DELETE/POST/PUT/OPTIONS.
 @raise [SSRFProxy::HTTP::Error::ConnectionTimeout]
        The request to the remote host timed out.
 @raise [SSRFProxy::HTTP::Error::InvalidUpstreamProxy]
        Invalid upstream proxy specified.

 @return [Hash] Hash of the HTTP response (status, code, headers, body)