rets 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 0.5.0 / 2013-09-05
2
+
3
+ * feature: Allow client.count to get integer count
4
+ * feature: Allow for downcased capability names
5
+ * fix: Handle the rets element being empty
6
+ * feature: Instrument rets client with stats reporting
7
+ * feature: Add a locking client
8
+ * feature: Support Basic Authentication
9
+
1
10
  ### 0.4.0 / 2012-08-29
2
11
 
3
12
  * fix: update authentication header to uri matches path
data/Manifest.txt CHANGED
@@ -4,7 +4,6 @@ README.md
4
4
  Rakefile
5
5
  bin/rets
6
6
  lib/rets.rb
7
- lib/rets/authentication.rb
8
7
  lib/rets/client.rb
9
8
  lib/rets/metadata.rb
10
9
  lib/rets/metadata/containers.rb
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'hoe'
3
+ require 'rake/testtask'
3
4
 
4
5
  Hoe.plugin :git, :doofus
5
6
  Hoe.plugin :travis
@@ -8,7 +9,7 @@ Hoe.plugin :gemspec
8
9
  Hoe.spec 'rets' do
9
10
  developer 'Estately, Inc. Open Source', 'opensource@estately.com'
10
11
 
11
- extra_deps << [ "net-http-persistent", "~> 1.7" ]
12
+ extra_deps << [ "httpclient", "~> 2.3.0" ]
12
13
  extra_deps << [ "nokogiri", "~> 1.5.2" ]
13
14
 
14
15
  extra_dev_deps << [ "mocha", "~> 0.11.0" ]
@@ -19,3 +20,8 @@ Hoe.spec 'rets' do
19
20
  self.history_file = 'CHANGELOG.md'
20
21
  self.readme_file = 'README.md'
21
22
  end
23
+
24
+
25
+ Rake::TestTask.new do |t|
26
+ t.pattern = "test/test_*.rb"
27
+ end
data/bin/rets CHANGED
@@ -4,12 +4,14 @@ require "optparse"
4
4
  require "pp"
5
5
 
6
6
  require "rubygems"
7
+
8
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
7
9
  require "rets"
8
10
 
9
11
  class RetsCli
10
12
  def self.parse(args)
11
13
 
12
- actions = %w(metadata search object)
14
+ actions = %w(metadata search count object)
13
15
  options = {:count => 5}
14
16
 
15
17
  opts = OptionParser.new do |opts|
@@ -107,8 +109,6 @@ query = ARGV[1]
107
109
 
108
110
  client = Rets::Client.new(options.merge(:login_url => url))
109
111
 
110
- COUNT = Struct.new(:exclude, :include, :only).new(0,1,2)
111
-
112
112
  if options[:capabilities]
113
113
  pp client.capabilities
114
114
  end
@@ -161,9 +161,17 @@ case options[:action]
161
161
  :search_type => options[:resource],
162
162
  :class => options[:class],
163
163
  :query => query,
164
- :count => COUNT.exclude,
164
+ :count => Rets::Client::COUNT.exclude,
165
165
  :limit => options[:limit])
166
166
 
167
+ when "count" then
168
+ pp client.find(:all,
169
+ :search_type => options[:resource],
170
+ :class => options[:class],
171
+ :query => query,
172
+ :count => Rets::Client::COUNT.only,
173
+ :limit => options[:limit])
174
+
167
175
  when "object" then
168
176
 
169
177
  def write_objects(parts)
@@ -191,4 +199,4 @@ case options[:action]
191
199
 
192
200
  end
193
201
 
194
-
202
+ client.logout
data/lib/rets.rb CHANGED
@@ -1,24 +1,45 @@
1
- require 'net/http'
2
1
  require 'uri'
3
- require 'cgi'
4
2
  require 'digest/md5'
5
-
6
- require 'rubygems'
7
- require 'net/http/persistent'
8
3
  require 'nokogiri'
9
4
 
10
5
  module Rets
11
- VERSION = '0.4.0'
6
+ VERSION = '0.5.0'
12
7
 
13
- AuthorizationFailure = Class.new(ArgumentError)
14
- InvalidRequest = Class.new(ArgumentError)
15
8
  MalformedResponse = Class.new(ArgumentError)
16
9
  UnknownResponse = Class.new(ArgumentError)
10
+ NoLogout = Class.new(ArgumentError)
11
+
12
+ class AuthorizationFailure < ArgumentError
13
+ attr_reader :status, :body
14
+ def initialize(status, body)
15
+ @status = status
16
+ @body = body
17
+ super("HTTP status: #{status} (#{body})")
18
+ end
19
+ end
20
+
21
+ class InvalidRequest < ArgumentError
22
+ attr_reader :error_code, :reply_text
23
+ def initialize(error_code, reply_text)
24
+ @error_code = error_code
25
+ @reply_text = reply_text
26
+ super("Got error code #{error_code} (#{reply_text})")
27
+ end
28
+ end
29
+
30
+ class UnknownCapability < ArgumentError
31
+ attr_reader :capability_name
32
+ def initialize(capability_name)
33
+ @capability_name = capability_name
34
+ super("unknown capabilitiy #{capability_name}")
35
+ end
36
+ end
17
37
  end
18
38
 
19
- require 'rets/authentication'
39
+ require 'rets/client'
20
40
  require 'rets/metadata'
21
41
  require 'rets/parser/compact'
22
42
  require 'rets/parser/multipart'
23
-
24
- require 'rets/client'
43
+ require 'rets/measuring_http_client'
44
+ require 'rets/locking_http_client'
45
+ require 'rets/client_progress_reporter'
data/lib/rets/client.rb CHANGED
@@ -1,12 +1,16 @@
1
+ require 'httpclient'
2
+ require 'logger'
3
+ require_relative 'http_client'
4
+
1
5
  module Rets
2
- Session = Struct.new(:auth_digest, :capabilities, :cookies)
6
+ class HttpError < StandardError ; end
3
7
 
4
8
  class Client
5
- DEFAULT_OPTIONS = { :persistent => true }
9
+ DEFAULT_OPTIONS = {}
6
10
 
7
- include Authentication
11
+ COUNT = Struct.new(:exclude, :include, :only).new(0,1,2)
8
12
 
9
- attr_accessor :login_uri, :options, :logger, :auth_digest
13
+ attr_accessor :login_url, :options, :logger
10
14
  attr_writer :capabilities, :metadata
11
15
 
12
16
  def initialize(options)
@@ -15,41 +19,47 @@ module Rets
15
19
  end
16
20
 
17
21
  def clean_setup
22
+ self.options = DEFAULT_OPTIONS.merge(@options)
23
+ self.login_url = self.options[:login_url]
18
24
 
19
- @auth_digest = nil
20
25
  @cached_metadata = nil
21
26
  @capabilities = nil
22
- @connection = nil
23
- @cookies = nil
24
27
  @metadata = nil
25
28
  @tries = nil
26
29
  self.capabilities = nil
27
30
 
28
- uri = URI.parse(@options[:login_url])
29
-
30
- uri.user = @options.key?(:username) ? CGI.escape(@options[:username]) : nil
31
- uri.password = @options.key?(:password) ? CGI.escape(@options[:password]) : nil
32
-
33
- self.options = DEFAULT_OPTIONS.merge(@options)
34
- self.login_uri = uri
35
-
36
31
  self.logger = @options[:logger] || FakeLogger.new
37
-
38
- self.session = @options.delete(:session) if @options[:session]
39
- @cached_metadata = @options[:metadata] || nil
32
+ @client_progress = ClientProgressReporter.new(self.logger, options[:stats_collector], options[:stats_prefix])
33
+ @cached_metadata = @options[:metadata]
34
+ @http = HTTPClient.new
35
+ @http.set_cookie_store(options[:cookie_store]) if options[:cookie_store]
36
+
37
+ @http_client = Rets::HttpClient.new(@http, @options, @logger, @login_url)
38
+ if options[:http_timing_stats_collector]
39
+ @http_client = Rets::MeasuringHttpClient.new(@http_client, options.fetch(:http_timing_stats_collector), options.fetch(:http_timing_stats_prefix))
40
+ end
41
+ if options[:lock_around_http_requests]
42
+ @http_client = Rets::LockingHttpClient.new(@http_client, options.fetch(:locker), options.fetch(:lock_name), options.fetch(:lock_options))
43
+ end
40
44
  end
41
45
 
42
-
43
46
  # Attempts to login by making an empty request to the URL
44
47
  # provided in initialize. Returns the capabilities that the
45
48
  # RETS server provides, per http://retsdoc.onconfluence.com/display/rets172/4.10+Capability+URL+List.
46
49
  def login
47
- response = request(login_uri.path)
48
- self.capabilities = extract_capabilities(Nokogiri.parse(response.body))
50
+ res = http_get(login_url)
51
+ self.capabilities = extract_capabilities(Nokogiri.parse(res.body))
49
52
  raise UnknownResponse, "Cannot read rets server capabilities." unless @capabilities
50
53
  @capabilities
51
54
  end
52
55
 
56
+ def logout
57
+ unless capabilities["Logout"]
58
+ raise NoLogout.new('No logout method found for rets client')
59
+ end
60
+ http_get(capability_url("Logout"))
61
+ end
62
+
53
63
  # Finds records.
54
64
  #
55
65
  # [quantity] Return the first record, or an array of records.
@@ -81,51 +91,41 @@ module Rets
81
91
 
82
92
  def find_with_retries(opts = {})
83
93
  retries = 0
94
+ resolve = opts.delete(:resolve)
84
95
  begin
85
- find_every(opts)
96
+ find_every(opts, resolve)
86
97
  rescue AuthorizationFailure, InvalidRequest => e
87
98
  if retries < 3
88
99
  retries += 1
89
- self.logger.warn "Failed with message: #{e.message}"
90
- self.logger.info "Retry #{retries}/3"
100
+ @client_progress.find_with_retries_failed_a_retry(e, retries)
91
101
  clean_setup
92
102
  retry
93
103
  else
104
+ @client_progress.find_with_retries_exceeded_retry_count(e)
94
105
  raise e
95
106
  end
96
107
  end
97
108
  end
98
109
 
99
- def find_every(opts = {})
100
- search_uri = capability_url("Search")
101
-
102
- resolve = opts.delete(:resolve)
103
-
104
- extras = fixup_keys(opts)
105
-
106
- defaults = {"QueryType" => "DMQL2", "Format" => "COMPACT"}
107
-
108
- query = defaults.merge(extras)
109
-
110
- body = build_key_values(query)
111
-
112
- extra_headers = {
113
- "Content-Type" => "application/x-www-form-urlencoded",
114
- "Content-Length" => body.size.to_s
115
- }
116
-
117
- results = request_with_compact_response(search_uri.path, body, extra_headers)
110
+ def find_every(opts, resolve)
111
+ params = {"QueryType" => "DMQL2", "Format" => "COMPACT"}.merge(fixup_keys(opts))
112
+ res = http_post(capability_url("Search"), params)
118
113
 
119
- if resolve
120
- rets_class = find_rets_class(opts[:search_type], opts[:class])
121
- decorate_results(results, rets_class)
114
+ if opts[:count] == COUNT.only
115
+ Parser::Compact.get_count(res.body)
122
116
  else
123
- results
117
+ results = Parser::Compact.parse_document(res.body)
118
+ if resolve
119
+ rets_class = find_rets_class(opts[:search_type], opts[:class])
120
+ decorate_results(results, rets_class)
121
+ else
122
+ results
123
+ end
124
124
  end
125
125
  end
126
126
 
127
127
  def find_rets_class(resource_name, rets_class_name)
128
- metadata.build_tree[resource_name].find_rets_class(rets_class_name)
128
+ metadata.tree[resource_name].find_rets_class(rets_class_name)
129
129
  end
130
130
 
131
131
  def decorate_results(results, rets_class)
@@ -141,12 +141,11 @@ module Rets
141
141
  result[key] = table.resolve(value.to_s)
142
142
  else
143
143
  #can't resolve just leave the value be
144
- logger.warn "Can't resolve find metadata for #{key.inspect}"
144
+ @client_progress.could_not_resolve_find_metadata(key)
145
145
  end
146
146
  end
147
147
  end
148
148
 
149
-
150
149
  # Returns an array of all objects associated with the given resource.
151
150
  def all_objects(opts = {})
152
151
  objects("*", opts)
@@ -164,7 +163,7 @@ module Rets
164
163
  end
165
164
 
166
165
  def create_parts_from_response(response)
167
- content_type = response["content-type"]
166
+ content_type = response.header["content-type"][0]
168
167
 
169
168
  if content_type.nil?
170
169
  raise MalformedResponse, "Unable to read content-type from response: #{response.inspect}"
@@ -175,13 +174,13 @@ module Rets
175
174
 
176
175
  parts = Parser::Multipart.parse(response.body, boundary)
177
176
 
178
- logger.debug "Found #{parts.size} parts"
177
+ logger.debug "Rets::Client: Found #{parts.size} parts"
179
178
 
180
179
  return parts
181
180
  else
182
181
  # fake a multipart for interface compatibility
183
182
  headers = {}
184
- response.each { |k,v| headers[k] = v }
183
+ response.headers.each { |k,v| headers[k] = v[0] }
185
184
 
186
185
  part = Parser::Multipart::Part.new(headers, response.body)
187
186
 
@@ -194,30 +193,25 @@ module Rets
194
193
  # resource RETS resource as defined in the resource metadata.
195
194
  # object_type an object type defined in the object metadata.
196
195
  # resource_id the KeyField value of the given resource instance.
197
- # object_id can be "*", or a comma delimited string of one or more integers.
196
+ # object_id can be "*" or a colon delimited string of integers or an array of integers.
198
197
  def object(object_id, opts = {})
199
- response = fetch_object(object_id, opts)
200
-
198
+ response = fetch_object(Array(object_id).join(':'), opts)
201
199
  response.body
202
200
  end
203
201
 
204
202
  def fetch_object(object_id, opts = {})
205
- object_uri = capability_url("GetObject")
206
-
207
- body = build_key_values(
208
- "Resource" => opts[:resource],
209
- "Type" => opts[:object_type],
210
- "ID" => "#{opts[:resource_id]}:#{object_id}",
211
- "Location" => opts[:location] || 0
212
- )
203
+ params = {
204
+ "Resource" => opts.fetch(:resource),
205
+ "Type" => opts.fetch(:object_type),
206
+ "ID" => "#{opts.fetch(:resource_id)}:#{object_id}",
207
+ "Location" => opts.fetch(:location, 0)
208
+ }
213
209
 
214
210
  extra_headers = {
215
- "Accept" => "image/jpeg, image/png;q=0.5, image/gif;q=0.1",
216
- "Content-Type" => "application/x-www-form-urlencoded",
217
- "Content-Length" => body.size.to_s
211
+ "Accept" => "image/jpeg, image/png;q=0.5, image/gif;q=0.1",
218
212
  }
219
213
 
220
- request(object_uri.path, body, extra_headers)
214
+ http_post(capability_url("GetObject"), params, extra_headers)
221
215
  end
222
216
 
223
217
  # Changes keys to be camel cased, per the RETS standard for queries.
@@ -238,166 +232,24 @@ module Rets
238
232
 
239
233
  if @cached_metadata && (@options[:skip_metadata_uptodate_check] ||
240
234
  @cached_metadata.current?(capabilities["MetadataTimestamp"], capabilities["MetadataVersion"]))
241
- logger.info "Use cached metadata"
235
+ @client_progress.use_cached_metadata
242
236
  self.metadata = @cached_metadata
243
237
  else
244
- logger.info @cached_metadata ? "Cached metadata out of date" : "Cached metadata unavailable"
238
+ @client_progress.bad_cached_metadata(@cached_metadata)
245
239
  metadata_fetcher = lambda { |type| retrieve_metadata_type(type) }
246
240
  self.metadata = Metadata::Root.new(&metadata_fetcher)
247
241
  end
248
242
  end
249
243
 
250
244
  def retrieve_metadata_type(type)
251
- metadata_uri = capability_url("GetMetadata")
252
-
253
- body = build_key_values(
254
- "Format" => "COMPACT",
255
- "Type" => "METADATA-#{type}",
256
- "ID" => "0"
257
- )
258
-
259
- extra_headers = {
260
- "Content-Type" => "application/x-www-form-urlencoded",
261
- "Content-Length" => body.size.to_s
262
- }
263
-
264
- response = request(metadata_uri.path, body, extra_headers)
265
-
266
- response.body
267
- end
268
-
269
- def raw_request(path, body = nil, extra_headers = {}, &reader)
270
- headers = build_headers(path).merge(extra_headers)
271
-
272
- post = Net::HTTP::Post.new(path, headers)
273
- post.body = body.to_s
274
-
275
- logger.debug <<EOF
276
- >>>> Request
277
- POST #{path}
278
- #{format_headers(headers)}
279
-
280
- #{binary?(body.to_s) ? '<<< BINARY BODY >>>' : body.to_s}
281
- EOF
282
-
283
- connection_args = [Net::HTTP::Persistent === connection ? login_uri : nil, post].compact
284
-
285
- response = connection.request(*connection_args) do |res|
286
- res.read_body(&reader)
287
- end
288
-
289
- logger.debug <<EOF
290
- <<<< Response
291
- #{response.code} #{response.message}
292
- #{format_headers(response.to_hash)}
293
-
294
- #{binary?(response.body.to_s) ? '<<< BINARY BODY >>>' : response.body.to_s}
295
- EOF
296
-
297
- handle_cookies(response)
298
- return response
299
- end
300
-
301
- def digest_auth_request(path, body = nil, extra_headers = {}, &reader)
302
- response = raw_request(path, body, extra_headers, &reader)
303
- if Net::HTTPUnauthorized === response
304
- @auth_digest = extract_digest_header(response)
305
- if @auth_digest
306
- response = raw_request(path, body, extra_headers, &reader)
307
- if Net::HTTPUnauthorized === response
308
- raise AuthorizationFailure, "Authorization failed, check credentials?"
309
- end
310
- end
311
- end
312
- response
313
- end
314
-
315
- def authorization(path)
316
- return nil unless @auth_digest
317
- uri2 = URI.parse(login_uri.to_s)
318
- uri2.user = login_uri.user
319
- uri2.password = login_uri.password
320
- uri2.path = path
321
- build_auth(@auth_digest, uri2, tries)
322
- end
323
-
324
-
325
- def request(*args, &block)
326
- handle_response(digest_auth_request(*args, &block))
327
- end
328
-
329
- def request_with_compact_response(path, body, headers)
330
- response = request(path, body, headers)
331
-
332
- Parser::Compact.parse_document response.body
333
- end
334
-
335
- def extract_digest_header(response)
336
- authenticate_headers = response.get_fields("www-authenticate")
337
- if authenticate_headers
338
- authenticate_headers.detect {|h| h =~ /Digest/}
339
- else
340
- nil
341
- end
342
- end
343
-
344
- def handle_response(response)
345
- if Net::HTTPSuccess === response
346
- ErrorChecker.check(response)
347
- elsif Net::HTTPUnauthorized === response
348
- raise AuthorizationFailure, "Authorization failed, check credentials?"
349
- else
350
- raise UnknownResponse, "Unable to handle response #{response.class}"
351
- end
352
- response
353
- end
354
-
355
- def handle_cookies(response)
356
- if cookies?(response)
357
- self.cookies = response.get_fields('set-cookie')
358
- logger.info "Cookies set to #{cookies.inspect}"
359
- end
360
- end
361
-
362
- def cookies?(response)
363
- response['set-cookie']
364
- end
365
-
366
- def cookies=(cookies)
367
- @cookies ||= {}
368
-
369
- Array(cookies).each do |cookie|
370
- cookie.match(/(\S+)=([^;]+);?/)
371
-
372
- @cookies[$1] = $2
373
- end
374
-
375
- nil
376
- end
377
-
378
- def cookies
379
- return if @cookies.nil? or @cookies.empty?
380
-
381
- @cookies.map{ |k,v| "#{k}=#{v}" }.join("; ")
382
- end
383
-
384
- def cookie(name)
385
- return if @cookies.nil? or @cookies.empty?
386
-
387
- @cookies[name]
388
- end
389
-
390
- def session=(session)
391
- self.auth_digest = session.auth_digest
392
- self.capabilities = session.capabilities
393
- self.cookies = session.cookies
394
- end
395
-
396
- def session
397
- Session.new(auth_digest, capabilities, cookies)
245
+ res = http_post(capability_url("GetMetadata"),
246
+ { "Format" => "COMPACT",
247
+ "Type" => "METADATA-#{type}",
248
+ "ID" => "0"
249
+ })
250
+ res.body
398
251
  end
399
252
 
400
-
401
253
  # The capabilies as provided by the RETS server during login.
402
254
  #
403
255
  # Currently, only the path in the endpoint URLs is used[1]. Host,
@@ -410,15 +262,21 @@ EOF
410
262
  end
411
263
 
412
264
  def capability_url(name)
413
- url = capabilities[name]
265
+ val = capabilities[name] || capabilities[name.downcase]
266
+
267
+ raise UnknownCapability.new(name) unless val
414
268
 
415
269
  begin
416
- capability_uri = URI.parse(url)
270
+ if val.downcase.match(/^https?:\/\//)
271
+ uri = URI.parse(val)
272
+ else
273
+ uri = URI.parse(login_url)
274
+ uri.path = val
275
+ end
417
276
  rescue URI::InvalidURIError
418
- raise MalformedResponse, "Unable to parse capability URL: #{url.inspect}"
277
+ raise MalformedResponse, "Unable to parse capability URL: #{name} => #{val.inspect}"
419
278
  end
420
-
421
- capability_uri
279
+ uri.to_s
422
280
  end
423
281
 
424
282
  def extract_capabilities(document)
@@ -435,59 +293,16 @@ EOF
435
293
  hash
436
294
  end
437
295
 
438
-
439
-
440
- def connection
441
- @connection ||= options[:persistent] ?
442
- persistent_connection :
443
- Net::HTTP.new(login_uri.host, login_uri.port)
444
- end
445
-
446
- def persistent_connection
447
- conn = Net::HTTP::Persistent.new
448
-
449
- def conn.idempotent?(*)
450
- true
451
- end
452
-
453
- conn
454
- end
455
-
456
-
457
- def user_agent
458
- options[:agent] || "Client/1.0"
296
+ def save_cookie_store(force=nil)
297
+ @http_client.save_cookie_store(force)
459
298
  end
460
299
 
461
- def rets_version
462
- options[:version] || "RETS/1.7.2"
300
+ def http_get(url, params=nil, extra_headers={})
301
+ @http_client.http_get(url, params, extra_headers)
463
302
  end
464
303
 
465
- def build_headers(path)
466
- headers = {
467
- "User-Agent" => user_agent,
468
- "Host" => "#{login_uri.host}:#{login_uri.port}",
469
- "RETS-Version" => rets_version
470
- }
471
-
472
- auth = authorization(path)
473
- headers.merge!("Authorization" => auth) if auth
474
- headers.merge!("Cookie" => cookies) if cookies
475
-
476
- if options[:ua_password]
477
- headers.merge!(
478
- "RETS-UA-Authorization" => build_user_agent_auth(
479
- user_agent, options[:ua_password], '', cookie('RETS-Session-ID'), rets_version))
480
- end
481
-
482
- headers
483
- end
484
-
485
- def build_key_values(data)
486
- data.map{|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join("&")
487
- end
488
-
489
- def binary?(data)
490
- data.slice(0, 1024).chars.any? {|b| b >= "\x0" && b < " " && b != '-' && b != '~' && b != "\t" && b != "\r" && b != "\n"}
304
+ def http_post(url, params, extra_headers = {})
305
+ @http_client.http_post(url, params, extra_headers)
491
306
  end
492
307
 
493
308
  def tries
@@ -496,50 +311,46 @@ EOF
496
311
  (@tries += 1) - 1
497
312
  end
498
313
 
499
- class FakeLogger
500
- def fatal(*); end
501
- def error(*); end
502
- def warn(*); end
503
- def info(*); end
504
- def debug(*); end
505
- end
506
-
507
- def format_headers(headers)
508
- out = []
509
-
510
- headers.each do |name, value|
511
- if Array === value
512
- value.each do |v|
513
- out << "#{name}: #{v}"
514
- end
515
- else
516
- out << "#{name}: #{value}"
517
- end
314
+ class FakeLogger < Logger
315
+ def initialize
316
+ super("/dev/null")
518
317
  end
519
-
520
- out.join("\n")
521
318
  end
522
319
 
523
320
  class ErrorChecker
524
321
  def self.check(response)
525
- begin
526
- if !response.body.empty?
322
+ # some RETS servers return success code in XML body but failure code 4xx in http status
323
+ # If xml body is present we ignore http status
324
+
325
+ if !response.body.empty?
326
+ begin
527
327
  xml = Nokogiri::XML.parse(response.body, nil, nil, Nokogiri::XML::ParseOptions::STRICT)
528
328
 
529
329
  rets_element = xml.xpath("/RETS")
330
+ if rets_element.empty?
331
+ return
332
+ end
530
333
  reply_text = (rets_element.attr("ReplyText") || rets_element.attr("replyText")).value
531
334
  reply_code = (rets_element.attr("ReplyCode") || rets_element.attr("replyCode")).value.to_i
532
335
 
533
336
  if reply_code.nonzero?
534
- raise InvalidRequest, "Got error code #{reply_code} (#{reply_text})."
337
+ raise InvalidRequest.new(reply_code, reply_text)
338
+ else
339
+ return
535
340
  end
341
+ rescue Nokogiri::XML::SyntaxError
342
+ #Not xml
536
343
  end
344
+ end
537
345
 
538
- rescue Nokogiri::XML::SyntaxError
539
- #Not xml
346
+ if response.respond_to?(:ok?) && ! response.ok?
347
+ if response.status_code == 401
348
+ raise AuthorizationFailure.new(response.status_code, response.body)
349
+ else
350
+ raise HttpError, "HTTP status: #{response.status_code}, body: #{response.body}"
351
+ end
540
352
  end
541
353
  end
542
354
  end
543
-
544
355
  end
545
356
  end