lib/rets4r/client.rb in rets4r-0.8.2 vs lib/rets4r/client.rb in rets4r-0.8.3

- old
+ new

@@ -37,11 +37,34 @@ DEFAULT_USER_AGENT = 'RETS4R/0.8.2' DEFAULT_RETS_VERSION = '1.7' SUPPORTED_RETS_VERSIONS = ['1.5', '1.7'] CAPABILITY_LIST = ['Action', 'ChangePassword', 'GetObject', 'Login', 'LoginComplete', 'Logout', 'Search', 'GetMetadata', 'Update'] SUPPORTED_PARSERS = [] # This will be populated by parsers as they load - + + # These are the response messages as defined in the RETS 1.5e2 and 1.7d6 specifications. + # Provided for convenience and are used by the HTTPError class to provide more useful + # messages. + RETS_HTTP_MESSAGES = { + '200' => 'Operation successful.', + '400' => 'The request could not be understood by the server due to malformed syntax.', + '401' => 'Either the header did not contain an acceptable Authorization or the username/password was invalid. The server response MUST include a WWW-Authenticate header field.', + '402' => 'The requested transaction requires a payment which could not be authorized.', + '403' => 'The server understood the request, but is refusing to fulfill it.', + '404' => 'The server has not found anything matching the Request-URI.', + '405' => 'The method specified in the Request-Line is not allowed for the resource identified by the Request-URI.', + '406' => 'The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.', + '408' => 'The client did not produce a request within the time that the server was prepared to wait.', + '411' => 'The server refuses to accept the request without a defined Content-Length.', + '412' => 'Transaction not permitted at this point in the session.', + '413' => 'The server is refusing to process a request because the request entity is larger than the server is willing or able to process.', + '414' => 'The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret. This error usually only occurs for a GET method.', + '500' => 'The server encountered an unexpected condition which prevented it from fulfilling the request.', + '501' => 'The server does not support the functionality required to fulfill the request.', + '503' => 'The server is currently unable to handle the request due to a temporary overloading or maintenance of the server.', + '505' => 'The server does not support, or refuses to support, the HTTP protocol version that was used in the request message.', + } + attr_accessor :mimemap, :logger # We load our parsers here so that they can modify the client class appropriately. Because # the default parser will be the first parser to list itself in the DEFAULT_PARSER array, # we need to require them in the order of preference. Hence, XMLParser is loaded first because @@ -80,10 +103,39 @@ if block_given? yield self end end + # Assigns a block that will be called just before the request is sent. + # This block must accept three parameters: + # * self + # * Net::HTTP instance + # * Hash of headers + # + # The block's return value will be ignored. If you want to prevent the request + # to go through, raise an exception. + # + # == Example + # + # client = RETS4R::Client.new(...) + # # Make a new pre_request_block that calculates the RETS-UA-Authorization header. + # client.set_pre_request_block do |rets, http, headers| + # a1 = Digest::MD5.hexdigest([headers["User-Agent"], @password].join(":")) + # if headers.has_key?("Cookie") then + # cookie = headers["Cookie"].split(";").map(&:strip).select {|c| c =~ /rets-session-id/i} + # cookie = cookie ? cookie.split("=").last : "" + # else + # cookie = "" + # end + # + # parts = [a1, "", cookie, headers["RETS-Version"]] + # headers["RETS-UA-Authorization"] = "Digest " + Digest::MD5.hexdigest(parts.join(":")) + # end + def set_pre_request_block(&block) + @pre_request_block = block + end + # We only allow external read access to URLs because they are internally set based on the # results of various queries. def urls @urls end @@ -341,13 +393,12 @@ 'Content-Type' => response['content-type'], 'Object-ID' => response['Object-ID'], 'Content-ID' => response['Content-ID'] } - if response['Content-Length'].to_i > 100 + if response['Transfer-Encoding'].to_s.downcase == "chunked" || response['Content-Length'].to_i > 100 then data_object = DataObject.new(info, response.body) - if block_given? yield data_object results += 1 else results << data_object @@ -464,10 +515,12 @@ end headers = @headers headers.merge(header) unless header.empty? + @pre_request_block.call(self, http, headers) if @pre_request_block + logger.debug(headers.inspect) if logger @semaphore.unlock response = http.get(uri, headers) @@ -475,10 +528,14 @@ @semaphore.lock 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 + @semaphore.unlock if @semaphore.locked? + raise HTTPError.new(response) else cookies = [] if set_cookies = response.get_fields('set-cookie') then set_cookies.each do |cookie| cookies << cookie.split(";").first @@ -492,10 +549,13 @@ if retry_auth > 0 retry_auth -= 1 set_header('Authorization', Auth.authenticate(response, @username, @password, url.path, method, @headers['RETS-Request-ID'], get_user_agent, @nc)) retry + else + @semaphore.unlock if @semaphore.locked? + raise LoginError.new(response.message) end end logger.debug(response.body) if logger end @@ -541,9 +601,39 @@ attr_accessor :file end # The client does not currently support a specified action. class Unsupported < ClientException + end + + # The HTTP response returned by the server indicates that there was an error processing + # the request and the client cannot continue on its own without intervention. + class HTTPError < ClientException + attr_accessor :http_response + + # Takes a HTTPResponse object + def initialize(http_response) + self.http_response = http_response + end + + # Shorthand for calling HTTPResponse#code + def code + http_response.code + end + + # Shorthand for calling HTTPResponse#message + def message + http_response.message + end + + # Returns the RETS specification message for the HTTP response code + def rets_message + Client::RETS_HTTP_MESSAGES[code] + end + + def to_s + "#{code} #{message}: #{rets_message}" + end end # A general RETS level exception was encountered. This would include HTTP and RETS # specification level errors as well as informative mishaps such as authentication being # required for access.