lib/rets4r/client/requester.rb
#require 'rets4r'
require 'rets4r/client/exceptions'
module RETS4R
class Client
class Requester
DEFAULT_USER_AGENT = "rets4r/#{::RETS4R::VERSION}"
DEFAULT_RETS_VERSION = '1.7'
attr_accessor :logger, :headers, :pre_request_block, :post_request_block, :auth
attr_reader :username, :password, :method
def initialize
@headers = {
'User-Agent' => DEFAULT_USER_AGENT,
'Accept' => '*/*',
'RETS-Version' => "RETS/#{DEFAULT_RETS_VERSION}",
}
@pre_request_block = nil
@post_request_block = nil
@method = METHOD_GET
@auth = RETS4R::Auth.new
end
def user_agent
@headers['User-Agent']
end
def user_agent=(name)
auth.user_agent = name
set_header('User-Agent', name)
end
def username=(name)
@username = name
auth.username = name
end
def password=(pass)
@password = pass
auth.password = pass
end
def method=(method)
@method = method
auth.method = method
end
def rets_version=(version)
if (SUPPORTED_RETS_VERSIONS.include? version)
set_header('RETS-Version', "RETS/#{version}")
else
raise Unsupported.new("The client does not support RETS version '#{version}'.")
end
end
def rets_version
(@headers['RETS-Version'] || "").gsub("RETS/", "")
end
def set_header(name, value)
if value.nil? then
@headers.delete(name)
else
@headers[name] = value
end
logger.debug("Set header '#{name}' to '#{value}'") if logger
end
# Given a hash, it returns a URL encoded query string.
def create_query_string(hash)
parts = hash.map {|key,value| "#{CGI.escape(key)}=#{CGI.escape(value)}" unless key.nil? || value.nil?}
return parts.join('&')
end
# This is the primary transaction method, which the other public methods make use of.
# Given a url for the transaction (endpoint) it makes a request to the RETS server.
#
#--
# This needs to be better documented, but for now please see the public transaction methods
# for how to make use of this method.
#++
def request(url, data = {}, header = {}, method = @method, retry_auth = DEFAULT_RETRY)
response = ''
http = Net::HTTP.new(url.host, url.port)
http.read_timeout = 600
if logger && logger.debug?
http.set_debug_output HTTPDebugLogger.new(logger)
end
begin
http.start do |sess|
begin
uri = url.path
if ! data.empty? && method == METHOD_GET
uri += "?#{create_query_string(data)}"
end
headers = @headers
headers.merge(header) unless header.empty?
@pre_request_block.call(self, sess, headers) if @pre_request_block
logger.debug("Sending headers #{headers.inspect}") if logger
post_data = data.map {|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&') if method == METHOD_POST
auth.update(url.path, method, @headers['RETS-Request-ID'])
set_header('Authorization', auth.to_s)
response = method == METHOD_POST ? sess.post(uri, post_data, headers) :
sess.get(uri, headers)
if response.code == '401'
# Authentication is required
raise AuthRequired
elsif response.code.to_i >= 300
# We have a non-successful response that we cannot handle
raise HTTPError.new(response)
else
cookies = collect_cookies response
logger.debug("Recieved cookies '#{cookies.inspect}'") if logger
set_cookies cookies
end
rescue AuthRequired
if retry_auth > 0
retry_auth -= 1
auth.update_with_response(response)
retry
else
raise LoginError.new(response.message)
end
end
logger.debug("Recieved headers #{response.to_hash.inspect}") if logger
logger.debug("Recieved response #{response.body}") if logger
end
rescue
raise ClientException.new("Error connecting to #{url.host}:#{url.port}\n#{$!}")
end
@post_request_block.call(self, http, headers) if @post_request_block
return response
end
def collect_cookies(response)
cookies = {}
cookie_fields = response.get_fields('set-cookie') || []
cookie_fields.each do |cookie|
# Cookies are of the form
# JSESSIONID=7939CD3932648EA8F3C0D27723154039; Path=/
# So we need to strip the path component
key, value = cookie.split(";").first.split('=')
cookies.store key, value
end
cookies
end
def set_cookies(cookies)
cookie_string = cookies.map{|k,v| "#{k}=#{v}"}.join('; ')
set_header('Cookie', cookie_string) unless cookies.empty?
end
end
end
end