def request(args, &block)
      if args[:params]
        url_terminator = (args[:url].request_uri.include?("?")) ? "&" : "?"
        request_uri = "#{args[:url].request_uri}#{url_terminator}"
        args[:params].each do |k, v|
          request_uri << "#{k}=#{url_encode(v.to_s)}&" if v
        end
      else
        request_uri = args[:url].request_uri
      end

      request_uri = request_uri[0..-2] if request_uri[-1] == '&'

      headers = args[:headers]

      # Digest will change every time due to how its setup
      @request_count += 1
      if @auth_mode == :digest
        if headers
          headers["Authorization"] = create_digest("GET", request_uri)
        else
          headers = {"Authorization" => create_digest("GET", request_uri)}
        end
      end

      headers = headers ? @headers.merge(headers) : @headers

      http = ::Net::HTTP.new(args[:url].host, args[:url].port)
      http.read_timeout = args[:read_timeout] if args[:read_timeout]
      http.set_debug_output(@config[:debug_output]) if @config[:debug_output]

      if args[:url].scheme == "https"
        http.use_ssl = true
        http.verify_mode = @config[:http][:verify_mode] || OpenSSL::SSL::VERIFY_NONE
        http.ca_file = @config[:http][:ca_file] if @config[:http][:ca_file]
        http.ca_path = @config[:http][:ca_path] if @config[:http][:ca_path]
      end

      puts "request_uri: #{request_uri}"
  #    puts "headers: #{headers.to_s}"

      http.start do
        http.request_get(request_uri, headers) do |response|
          # Pass along the cookies
          # Some servers will continually call Set-Cookie with the same value for every single request
          # to avoid authentication problems from cookies being stomped over (which is sad, nobody likes having their cookies crushed).
          # We keep a hash of every cookie set and only update it if something changed
          if response.header["set-cookie"]
            cookies_changed = nil

            response.header.get_fields("set-cookie").each do |cookie|
              key, value = cookie.split(";").first.split("=")
              key.strip!
              value.strip!

              # If it's a RETS-Session-ID, it needs to be shoved into the RETS-UA-Authorization field
              # Save the RETS-Session-ID so it can be used with RETS-UA-Authorization
              if key.downcase == "rets-session-id"
                @rets_data[:session_id] = value
                self.setup_ua_authorization(@rets_data) if @rets_data[:version]
              end

              cookies_changed = true if @cookie_list[key] != value
              @cookie_list[key] = value
            end

            if cookies_changed
              @headers.merge!("Cookie" => @cookie_list.map {|k, v| "#{k}=#{v}"}.join("; "))
            end
          end

          # puts "response.code: #{response.code}"
          # puts "response.body: #{response.body}"
          # puts "response.message: #{response.message}"

          # Rather than returning HTTP 401 when User-Agent authentication is needed, Retsiq returns HTTP 200
          # with RETS error 20037. If we get a 20037, will let it pass through and handle it as if it was a HTTP 401.
          # Retsiq apparently returns a 20041 now instead of a 20037 for the same use case.
          # StratusRETS returns 20052 for an expired season
          rets_code = nil
          if response.code != "401" and ( response.code != "200" or args[:check_response] )
            if response.body =~ /<RETS/i
        #      puts "found RETS in response body"
              rets_code, text = self.get_rets_response(Nokogiri::XML(response.body).xpath("//RETS").first)
              unless rets_code == "20037" or rets_code == "20041" or rets_code == "20052" or rets_code == "0"
                raise RETS::APIError.new("#{rets_code}: #{text}", rets_code, text)
              end
            elsif !args[:check_response]
       #       puts "didn't find RETS in response body and check_response is false"
              raise RETS::HTTPError.new("#{response.code}: #{response.message}", response.code, response.message)
            end
          end

          # Strictly speaking, we do not need to set a RETS-Version in most cases, if RETS-UA-Authorization is not used
          # It makes more sense to be safe and set it. Innovia at least does not set this until authentication is successful
          # which is why this check is also here for HTTP 200s and not just 401s
          if response.code == "200" and !@rets_data[:version] and response.header["rets-version"] != ""
            @rets_data[:version] = response.header["rets-version"]
          end

          # Digest can become stale requiring us to reload data
          if @auth_mode == :digest and response.header["www-authenticate"] =~ /stale=true/i
            save_digest(get_digest(response.header.get_fields("www-authenticate")))

            args[:block] ||= block
            return self.request(args)

          elsif response.code == "401" or rets_code == "20037" or rets_code == "20041" or rets_code == "20052"
            raise RETS::Unauthorized, "Cannot login, check credentials" if ( @auth_mode and @retried_request ) or ( @retried_request and rets_code == "20037" )
            @retried_request = true

            # We already have an auth mode, and the request wasn't retried.
            # Meaning we know that we had a successful authentication but something happened so we should relogin.
            if @auth_mode
              @headers.delete("Cookie")
              @cookie_list = {}

              self.request(:url => login_uri)
              return self.request(args.merge(:block => block))
            end

            # Find a valid way of authenticating to the server as some will support multiple methods
            if response.header.get_fields("www-authenticate") and !response.header.get_fields("www-authenticate").empty?
              digest = get_digest(response.header.get_fields("www-authenticate"))
              if digest
                save_digest(digest)
                @auth_mode = :digest
              else
                @headers.merge!("Authorization" => create_basic)
                @auth_mode = :basic
              end

              unless @auth_mode
                raise RETS::HTTPError.new("Cannot authenticate, no known mode found", response.code)
              end
            end

            # Check if we need to deal with User-Agent authorization
            if response.header["rets-version"] and response.header["rets-version"] != ""
              @rets_data[:version] = response.header["rets-version"]

            # If we get a 20037 error, it could be due to not having a RETS-Version set
            # Under Innovia, passing RETS/1.7 will cause some errors
            # because they don't pass the RETS-Version header until a successful login which is a HTTP 200
            # They also don't use RETS-UA-Authorization, and it's better to not imply the RETS-Version header
            # unless necessary, so will only do it for 20037 errors now.
            elsif !@rets_data[:version] and rets_code == "20037"
              @rets_data[:version] = "RETS/1.7"
            end

            self.setup_ua_authorization(@rets_data)

            args[:block] ||= block
            return self.request(args)

          # We just tried to auth and don't have access to the original block in yieldable form
          elsif args[:block]
            @retried_request = nil
            args.delete(:block).call(response)

          elsif block_given?
            @retried_request = nil
            yield response
          end
        end
      end
    end
def request(args, &block)
      if args[:params]
        url_terminator = (args[:url].request_uri.include?("?")) ? "&" : "?"
        request_uri = "#{args[:url].request_uri}#{url_terminator}"
        args[:params].each do |k, v|
          request_uri << "#{k}=#{url_encode(v.to_s)}&" if v
        end
      else
        request_uri = args[:url].request_uri
      end

      request_uri = request_uri[0..-2] if request_uri[-1] == '&'

      headers = args[:headers]

      # Digest will change every time due to how its setup
      @request_count += 1
      if @auth_mode == :digest
        if headers
          headers["Authorization"] = create_digest("GET", request_uri)
        else
          headers = {"Authorization" => create_digest("GET", request_uri)}
        end
      end

      headers = headers ? @headers.merge(headers) : @headers

      http = ::Net::HTTP.new(args[:url].host, args[:url].port)
      http.read_timeout = args[:read_timeout] if args[:read_timeout]
      http.set_debug_output(@config[:debug_output]) if @config[:debug_output]

      if args[:url].scheme == "https"
        http.use_ssl = true
        http.verify_mode = @config[:http][:verify_mode] || OpenSSL::SSL::VERIFY_NONE
        http.ca_file = @config[:http][:ca_file] if @config[:http][:ca_file]
        http.ca_path = @config[:http][:ca_path] if @config[:http][:ca_path]
      end

      puts "request_uri: #{request_uri}"
  #    puts "headers: #{headers.to_s}"

      http.start do
        http.request_get(request_uri, headers) do |response|
          # Pass along the cookies
          # Some servers will continually call Set-Cookie with the same value for every single request
          # to avoid authentication problems from cookies being stomped over (which is sad, nobody likes having their cookies crushed).
          # We keep a hash of every cookie set and only update it if something changed
          if response.header["set-cookie"]
            cookies_changed = nil

            response.header.get_fields("set-cookie").each do |cookie|
              key, value = cookie.split(";").first.split("=")
              key.strip!
              value.strip!

              # If it's a RETS-Session-ID, it needs to be shoved into the RETS-UA-Authorization field
              # Save the RETS-Session-ID so it can be used with RETS-UA-Authorization
              if key.downcase == "rets-session-id"
                @rets_data[:session_id] = value
                self.setup_ua_authorization(@rets_data) if @rets_data[:version]
              end

              cookies_changed = true if @cookie_list[key] != value
              @cookie_list[key] = value
            end

            if cookies_changed
              @headers.merge!("Cookie" => @cookie_list.map {|k, v| "#{k}=#{v}"}.join("; "))
            end
          end

          # puts "response.code: #{response.code}"
          # puts "response.body: #{response.body}"
          # puts "response.message: #{response.message}"

          # Rather than returning HTTP 401 when User-Agent authentication is needed, Retsiq returns HTTP 200
          # with RETS error 20037. If we get a 20037, will let it pass through and handle it as if it was a HTTP 401.
          # Retsiq apparently returns a 20041 now instead of a 20037 for the same use case.
          # StratusRETS returns 20052 for an expired season
          rets_code = nil
          if response.code != "401" and ( response.code != "200" or args[:check_response] )
            if response.body =~ /<RETS/i
        #      puts "found RETS in response body"
              rets_code, text = self.get_rets_response(Nokogiri::XML(response.body).xpath("//RETS").first)
              unless rets_code == "20037" or rets_code == "20041" or rets_code == "20052" or rets_code == "0"
                raise RETS::APIError.new("#{rets_code}: #{text}", rets_code, text)
              end
            elsif !args[:check_response]
       #       puts "didn't find RETS in response body and check_response is false"
              raise RETS::HTTPError.new("#{response.code}: #{response.message}", response.code, response.message)
            end
          end

          # Strictly speaking, we do not need to set a RETS-Version in most cases, if RETS-UA-Authorization is not used
          # It makes more sense to be safe and set it. Innovia at least does not set this until authentication is successful
          # which is why this check is also here for HTTP 200s and not just 401s
          if response.code == "200" and !@rets_data[:version] and response.header["rets-version"] != ""
            @rets_data[:version] = response.header["rets-version"]
          end

          # Digest can become stale requiring us to reload data
          if @auth_mode == :digest and response.header["www-authenticate"] =~ /stale=true/i
            save_digest(get_digest(response.header.get_fields("www-authenticate")))

            args[:block] ||= block
            return self.request(args)

          elsif response.code == "401" or rets_code == "20037" or rets_code == "20041" or rets_code == "20052"
            raise RETS::Unauthorized, "Cannot login, check credentials" if ( @auth_mode and @retried_request ) or ( @retried_request and rets_code == "20037" )
            @retried_request = true

            # We already have an auth mode, and the request wasn't retried.
            # Meaning we know that we had a successful authentication but something happened so we should relogin.
            if @auth_mode
              @headers.delete("Cookie")
              @cookie_list = {}

              self.request(:url => login_uri)
              return self.request(args.merge(:block => block))
            end

            # Find a valid way of authenticating to the server as some will support multiple methods
            if response.header.get_fields("www-authenticate") and !response.header.get_fields("www-authenticate").empty?
              digest = get_digest(response.header.get_fields("www-authenticate"))
              if digest
                save_digest(digest)
                @auth_mode = :digest
              else
                @headers.merge!("Authorization" => create_basic)
                @auth_mode = :basic
              end

              unless @auth_mode
                raise RETS::HTTPError.new("Cannot authenticate, no known mode found", response.code)
              end
            end

            # Check if we need to deal with User-Agent authorization
            if response.header["rets-version"] and response.header["rets-version"] != ""
              @rets_data[:version] = response.header["rets-version"]

            # If we get a 20037 error, it could be due to not having a RETS-Version set
            # Under Innovia, passing RETS/1.7 will cause some errors
            # because they don't pass the RETS-Version header until a successful login which is a HTTP 200
            # They also don't use RETS-UA-Authorization, and it's better to not imply the RETS-Version header
            # unless necessary, so will only do it for 20037 errors now.
            elsif !@rets_data[:version] and rets_code == "20037"
              @rets_data[:version] = "RETS/1.7"
            end

            self.setup_ua_authorization(@rets_data)

            args[:block] ||= block
            return self.request(args)

          # We just tried to auth and don't have access to the original block in yieldable form
          elsif args[:block]
            @retried_request = nil
            args.delete(:block).call(response)

          elsif block_given?
            @retried_request = nil
            yield response
          end
        end
      end
    end
Sends a request to the RETS server.

 @param [Hash] args
 @option args [URI] :url URI to request data from
 @option args [Hash, Optional] :params Query string to include with the request
 @option args [Integer, Optional] :read_timeout How long to wait for the socket to return data before timing out

 @raise [RETS::APIError]
 @raise [RETS::HTTPError]
 @raise [RETS::Unauthorized]