rets4r 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
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