rets4r 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ 0.8.4
2
+ Fixed auth issue with authenticate headers with spaces after commas. (Ken Wiesner, Scott Patterson)
3
+ When an exception is raised in the authentication parsing code, complain by raising a new exception, but also report anything of interest at the same time: request, response, response's body. (Fran�ois Beausoleil)
4
+ Fixed Client#request to actually respect the specified request method instead of always using GET requests. (Scott Patterson)
5
+ Set default request method to POST since some RETS servers seem to have trouble with GET requests. (Scott Patterson)
6
+
1
7
  0.8.3
2
8
  Added example #set_pre_request_block. (Fran�ois Beausoleil)
3
9
  Allow a pre request block to be set for RETS4R::Client.
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # This is an example of how to use the RETS client to login to a server and retrieve metadata. It
4
+ # also makes use of passing blocks to client methods and demonstrates how to set the output format.
5
+ #
6
+ # You will need to set the necessary variables below.
7
+ #
8
+ #############################################################################################
9
+ # Settings
10
+
11
+ rets_url = 'http://server.com/my/rets/url'
12
+ username = 'username'
13
+ password = 'password'
14
+
15
+ #############################################################################################
16
+ $:.unshift 'lib'
17
+
18
+ require 'rets4r'
19
+
20
+ RETS4R::Client.new(rets_url) do |client|
21
+ client.login(username, password) do |login_result|
22
+ if login_result.success?
23
+ puts "Logged in successfully!"
24
+
25
+ # We want the raw metadata, so we need to set the output to raw XML.
26
+ client.set_output RETS4R::Client::OUTPUT_RAW
27
+ metadata = ''
28
+
29
+ begin
30
+ metadata = client.get_metadata
31
+ rescue
32
+ puts "Unable to get metadata: '#{$!}'"
33
+ end
34
+
35
+ File.open('metadata.xml', 'w') do |file|
36
+ file.write metadata
37
+ end
38
+ else
39
+ puts "Unable to login: '#{login_result.reply_text}'."
40
+ end
41
+ end
42
+ end
@@ -45,7 +45,7 @@ module RETS4R
45
45
 
46
46
  def Auth.parse_header(header)
47
47
  type = header[0, header.index(' ')]
48
- args = header[header.index(' '), header.length].strip.split(',')
48
+ args = header[header.index(' '), header.length].strip.split(',').map {|x| x.strip}
49
49
 
50
50
  parts = {'type' => type}
51
51
 
@@ -8,8 +8,6 @@
8
8
  # version.
9
9
  #
10
10
  # TODO
11
- # 1.0 Support (Adding this support should be fairly easy)
12
- # 2.0 Support (Adding this support will be very difficult since it is a completely different methodology)
13
11
  # Case-insensitive header
14
12
 
15
13
  require 'digest/md5'
@@ -32,9 +30,9 @@ module RETS4R
32
30
  METHOD_HEAD = 'HEAD'
33
31
 
34
32
  DEFAULT_OUTPUT = OUTPUT_RUBY
35
- DEFAULT_METHOD = METHOD_GET
33
+ DEFAULT_METHOD = METHOD_POST
36
34
  DEFAULT_RETRY = 2
37
- DEFAULT_USER_AGENT = 'RETS4R/0.8.2'
35
+ DEFAULT_USER_AGENT = 'RETS4R/0.8.4'
38
36
  DEFAULT_RETS_VERSION = '1.7'
39
37
  SUPPORTED_RETS_VERSIONS = ['1.5', '1.7']
40
38
  CAPABILITY_LIST = ['Action', 'ChangePassword', 'GetObject', 'Login', 'LoginComplete', 'Logout', 'Search', 'GetMetadata', 'Update']
@@ -183,7 +181,7 @@ module RETS4R
183
181
  @parser_class = klass
184
182
  else
185
183
  message = "The parser class '#{klass}' is not supported!"
186
- logger.debug(message) if logger
184
+ debug(message)
187
185
 
188
186
  raise Unsupported.new(message)
189
187
  end
@@ -200,7 +198,7 @@ module RETS4R
200
198
  @headers[name] = value
201
199
  end
202
200
 
203
- logger.debug("Set header '#{name}' to '#{value}'") if logger
201
+ debug("Set header '#{name}' to '#{value}'")
204
202
  end
205
203
 
206
204
  def get_header(name)
@@ -284,7 +282,7 @@ module RETS4R
284
282
  @urls[capability] = base
285
283
  end
286
284
 
287
- logger.debug("Capability URL List: #{@urls.inspect}") if logger
285
+ debug("Capability URL List: #{@urls.inspect}")
288
286
  else
289
287
  raise LoginError.new(response.message + "(#{results.reply_code}: #{results.reply_text})")
290
288
  end
@@ -376,7 +374,7 @@ module RETS4R
376
374
  (raw_header, raw_data) = part.split("\r\n\r\n")
377
375
 
378
376
  next unless raw_data
379
-
377
+
380
378
  data_header = process_header(raw_header)
381
379
  data_object = DataObject.new(data_header, raw_data)
382
380
 
@@ -496,73 +494,84 @@ module RETS4R
496
494
  # for how to make use of this method.
497
495
  #++
498
496
  def request(url, data = {}, header = {}, method = @request_method, retry_auth = DEFAULT_RETRY)
499
- response = ''
500
-
501
- @semaphore.lock
497
+ headers, response = nil
498
+ begin
499
+ @semaphore.lock
502
500
 
503
- http = Net::HTTP.new(url.host, url.port)
501
+ http = Net::HTTP.new(url.host, url.port)
504
502
 
505
- if logger && logger.debug?
506
- http.set_debug_output HTTPDebugLogger.new(logger)
507
- end
503
+ if logger && logger.debug?
504
+ http.set_debug_output HTTPDebugLogger.new(logger)
505
+ end
508
506
 
509
- http.start do |http|
510
- begin
511
- uri = url.path
507
+ http.start do |http|
508
+ begin
509
+ uri = url.path
512
510
 
513
- if ! data.empty? && method == METHOD_GET
514
- uri += "?#{create_query_string(data)}"
515
- end
511
+ if ! data.empty? && method == METHOD_GET
512
+ uri += "?#{create_query_string(data)}"
513
+ end
516
514
 
517
- headers = @headers
518
- headers.merge(header) unless header.empty?
515
+ headers = @headers
516
+ headers.merge(header) unless header.empty?
519
517
 
520
- @pre_request_block.call(self, http, headers) if @pre_request_block
518
+ @pre_request_block.call(self, http, headers) if @pre_request_block
521
519
 
522
- logger.debug(headers.inspect) if logger
520
+ debug("Request headers: #{headers.inspect}")
523
521
 
524
- @semaphore.unlock
525
-
526
- response = http.get(uri, headers)
522
+ @semaphore.unlock
527
523
 
528
- @semaphore.lock
524
+ post_data = data.map {|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&') if method == METHOD_POST
525
+ response = method == METHOD_POST ? http.post(uri, post_data, headers) :
526
+ http.get(uri, headers)
527
+
528
+ debug("Response headers: #{response.to_hash.inspect}")
529
+
530
+ @semaphore.lock
529
531
 
530
- if response.code == '401'
531
- # Authentication is required
532
- raise AuthRequired
533
- elsif response.code.to_i >= 300
534
- # We have a non-successful response that we cannot handle
535
- @semaphore.unlock if @semaphore.locked?
536
- raise HTTPError.new(response)
537
- else
538
- cookies = []
539
- if set_cookies = response.get_fields('set-cookie') then
540
- set_cookies.each do |cookie|
541
- cookies << cookie.split(";").first
532
+ if response.code == '401'
533
+ # Authentication is required
534
+ raise AuthRequired
535
+ elsif response.code.to_i >= 300
536
+ # We have a non-successful response that we cannot handle
537
+ @semaphore.unlock if @semaphore.locked?
538
+ raise HTTPError.new(response)
539
+ else
540
+ cookies = []
541
+ if set_cookies = response.get_fields('set-cookie') then
542
+ set_cookies.each do |cookie|
543
+ cookies << cookie.split(";").first
544
+ end
542
545
  end
546
+ set_header('Cookie', cookies.join("; ")) unless cookies.empty?
547
+ set_header('RETS-Session-ID', response['RETS-Session-ID']) if response['RETS-Session-ID']
543
548
  end
544
- set_header('Cookie', cookies.join("; ")) unless cookies.empty?
545
- set_header('RETS-Session-ID', response['RETS-Session-ID']) if response['RETS-Session-ID']
546
- end
547
- rescue AuthRequired
548
- @nc += 1
549
+ rescue AuthRequired
550
+ @nc += 1
549
551
 
550
- if retry_auth > 0
551
- retry_auth -= 1
552
- set_header('Authorization', Auth.authenticate(response, @username, @password, url.path, method, @headers['RETS-Request-ID'], get_user_agent, @nc))
553
- retry
554
- else
555
- @semaphore.unlock if @semaphore.locked?
556
- raise LoginError.new(response.message)
557
- end
558
- end
552
+ if retry_auth > 0
553
+ retry_auth -= 1
554
+ set_header('Authorization', Auth.authenticate(response, @username, @password, url.path, method, @headers['RETS-Request-ID'], get_user_agent, @nc))
555
+ retry
556
+ else
557
+ @semaphore.unlock if @semaphore.locked?
558
+ raise LoginError.new(response.message)
559
+ end
560
+ end
559
561
 
560
- logger.debug(response.body) if logger
561
- end
562
+ debug(response.body)
563
+ end
564
+
565
+ @semaphore.unlock if @semaphore.locked?
562
566
 
563
- @semaphore.unlock if @semaphore.locked?
567
+ return response
564
568
 
565
- return response
569
+ #rescue
570
+ #data = {"request" => headers, "body" => response.body}
571
+ #data["response"] = response.respond_to?(:headers) ? response.headers : response
572
+ #data = data.respond_to?(:to_yaml) ? data.to_yaml : data.inspect
573
+ #raise RETSException, "#{$!.message}\nRequest/Response Details:\n#{data}"
574
+ end
566
575
  end
567
576
 
568
577
  # If an action URL is present in the URL capability list, it calls that action URL and returns the
@@ -577,6 +586,11 @@ module RETS4R
577
586
  end
578
587
  end
579
588
 
589
+ # Shorthand for sending debug messages to the logger if a logger is provided
590
+ def debug(message)
591
+ logger.debug(message) if logger
592
+ end
593
+
580
594
  # Provides a proxy class to allow for net/http to log its debug to the logger.
581
595
  class HTTPDebugLogger
582
596
  def initialize(logger)
@@ -102,7 +102,7 @@ module RETS4R
102
102
 
103
103
  def parse_compact_line(data, delim = "\t")
104
104
  begin
105
- return data.to_s.strip.split(delim)
105
+ return data.to_s.split(delim)
106
106
  rescue
107
107
  raise "Error while parsing compact line: #{$!} with data: #{data}"
108
108
  end
@@ -21,9 +21,20 @@ module RETS4R
21
21
  Auth.authenticate(response, @username, @password, '/my/rets/url', 'GET', Auth.request_id, @useragent)
22
22
  end
23
23
 
24
- def test_parse_auth_header
24
+ # test without spacing
25
+ def test_parse_auth_header_without_spacing
25
26
  header = 'Digest qop="auth",realm="'+ @realm +'",nonce="'+ @nonce +'",opaque="",stale="false",domain="\my\test\domain"'
26
-
27
+ check_header(header)
28
+ end
29
+
30
+ # test with spacing between each item
31
+ def test_parse_auth_header_with_spacing
32
+ header = 'Digest qop="auth", realm="'+ @realm +'", nonce="'+ @nonce +'", opaque="", stale="false", domain="\my\test\domain"'
33
+ check_header(header)
34
+ end
35
+
36
+ # used to check the that the header was processed properly.
37
+ def check_header(header)
27
38
  results = Auth.parse_header(header)
28
39
 
29
40
  assert_equal('auth', results['qop'])
@@ -279,7 +279,41 @@ module RETS4R
279
279
  assert_equal('multipart/parallel', results['content-type'])
280
280
  assert_equal('utf-8', results['charset'])
281
281
  end
282
-
282
+
283
+ def test_performs_get_request
284
+ assert_nothing_raised() {@rets.request_method = 'GET'}
285
+ assert_equal('GET', @rets.request_method)
286
+
287
+ http = mock('http')
288
+ response = mock('response')
289
+ response.stubs(:to_hash).returns({})
290
+ response.stubs(:code).returns('500')
291
+ response.stubs(:message).returns('Move along, nothing to see here.')
292
+
293
+ http.expects(:get).with('', {'RETS-Session-ID' => '0', 'User-Agent' => 'RETS4R/0.8.2', 'RETS-Version' => 'RETS/1.7', 'Accept' => '*/*'}).at_least_once.returns(response)
294
+ http.expects(:post).never
295
+ Net::HTTP.any_instance.expects(:start).at_least_once.yields(http)
296
+
297
+ assert_raises(RETS4R::Client::HTTPError) {@rets.login('user', 'pass')}
298
+ end
299
+
300
+ def test_performs_post_request
301
+ assert_nothing_raised() {@rets.request_method = 'POST'}
302
+ assert_equal('POST', @rets.request_method)
303
+
304
+ http = mock('http')
305
+ response = mock('response')
306
+ response.stubs(:to_hash).returns({})
307
+ response.stubs(:code).returns('500')
308
+ response.stubs(:message).returns('Move along, nothing to see here.')
309
+
310
+ http.expects(:post).with('', '', {'RETS-Session-ID' => '0', 'User-Agent' => 'RETS4R/0.8.2', 'RETS-Version' => 'RETS/1.7', 'Accept' => '*/*'}).at_least_once.returns(response)
311
+ http.expects(:get).never
312
+ Net::HTTP.any_instance.expects(:start).at_least_once.yields(http)
313
+
314
+ assert_raises(RETS4R::Client::HTTPError) {@rets.login('user', 'pass')}
315
+ end
316
+
283
317
  class MockParser
284
318
  end
285
319
  end
metadata CHANGED
@@ -1,38 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: rets4r
5
3
  version: !ruby/object:Gem::Version
6
- version: 0.8.3
7
- date: 2008-01-03 00:00:00 -08:00
8
- summary: A native Ruby implementation of RETS (Real Estate Transaction Standard).
9
- require_paths:
10
- - lib
11
- email: scott.patterson@digitalaun.com
12
- homepage: http://rets4r.rubyforge.org/
13
- rubyforge_project: rets4r
14
- description:
15
- autorequire:
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 0.8.4
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
7
  - Scott Patterson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-05 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: scott.patterson@digitalaun.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - CONTRIBUTORS
24
+ - README
25
+ - LICENSE
26
+ - RUBYS
27
+ - GPL
28
+ - CHANGELOG
29
+ - TODO
31
30
  files:
32
31
  - examples/client_get_object.rb
33
32
  - examples/client_login.rb
34
33
  - examples/client_metadata.rb
35
34
  - examples/client_search.rb
35
+ - examples/metadata.xml
36
36
  - lib/rets4r
37
37
  - lib/rets4r/auth.rb
38
38
  - lib/rets4r/client
@@ -73,24 +73,32 @@ files:
73
73
  - GPL
74
74
  - CHANGELOG
75
75
  - TODO
76
- test_files:
77
- - test/ts_all.rb
76
+ has_rdoc: true
77
+ homepage: http://rets4r.rubyforge.org/
78
+ post_install_message:
78
79
  rdoc_options:
79
80
  - --main
80
81
  - README
81
- extra_rdoc_files:
82
- - CONTRIBUTORS
83
- - README
84
- - LICENSE
85
- - RUBYS
86
- - GPL
87
- - CHANGELOG
88
- - TODO
89
- executables: []
90
-
91
- extensions: []
92
-
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
93
96
  requirements: []
94
97
 
95
- dependencies: []
96
-
98
+ rubyforge_project: rets4r
99
+ rubygems_version: 1.3.0
100
+ signing_key:
101
+ specification_version: 2
102
+ summary: A native Ruby implementation of RETS (Real Estate Transaction Standard).
103
+ test_files:
104
+ - test/ts_all.rb