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 +6 -0
- data/examples/metadata.xml +42 -0
- data/lib/rets4r/auth.rb +1 -1
- data/lib/rets4r/client.rb +73 -59
- data/lib/rets4r/client/parser.rb +1 -1
- data/test/client/tc_auth.rb +13 -2
- data/test/client/tc_client.rb +35 -1
- metadata +48 -40
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
|
data/lib/rets4r/auth.rb
CHANGED
@@ -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
|
|
data/lib/rets4r/client.rb
CHANGED
@@ -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 =
|
33
|
+
DEFAULT_METHOD = METHOD_POST
|
36
34
|
DEFAULT_RETRY = 2
|
37
|
-
DEFAULT_USER_AGENT = 'RETS4R/0.8.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
497
|
+
headers, response = nil
|
498
|
+
begin
|
499
|
+
@semaphore.lock
|
502
500
|
|
503
|
-
|
501
|
+
http = Net::HTTP.new(url.host, url.port)
|
504
502
|
|
505
|
-
|
506
|
-
|
507
|
-
|
503
|
+
if logger && logger.debug?
|
504
|
+
http.set_debug_output HTTPDebugLogger.new(logger)
|
505
|
+
end
|
508
506
|
|
509
|
-
|
510
|
-
|
511
|
-
|
507
|
+
http.start do |http|
|
508
|
+
begin
|
509
|
+
uri = url.path
|
512
510
|
|
513
|
-
|
514
|
-
|
515
|
-
|
511
|
+
if ! data.empty? && method == METHOD_GET
|
512
|
+
uri += "?#{create_query_string(data)}"
|
513
|
+
end
|
516
514
|
|
517
|
-
|
518
|
-
|
515
|
+
headers = @headers
|
516
|
+
headers.merge(header) unless header.empty?
|
519
517
|
|
520
|
-
|
518
|
+
@pre_request_block.call(self, http, headers) if @pre_request_block
|
521
519
|
|
522
|
-
|
520
|
+
debug("Request headers: #{headers.inspect}")
|
523
521
|
|
524
|
-
|
525
|
-
|
526
|
-
response = http.get(uri, headers)
|
522
|
+
@semaphore.unlock
|
527
523
|
|
528
|
-
|
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
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
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
|
-
|
545
|
-
|
546
|
-
end
|
547
|
-
rescue AuthRequired
|
548
|
-
@nc += 1
|
549
|
+
rescue AuthRequired
|
550
|
+
@nc += 1
|
549
551
|
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
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
|
-
|
561
|
-
|
562
|
+
debug(response.body)
|
563
|
+
end
|
564
|
+
|
565
|
+
@semaphore.unlock if @semaphore.locked?
|
562
566
|
|
563
|
-
|
567
|
+
return response
|
564
568
|
|
565
|
-
|
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)
|
data/lib/rets4r/client/parser.rb
CHANGED
data/test/client/tc_auth.rb
CHANGED
@@ -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
|
-
|
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'])
|
data/test/client/tc_client.rb
CHANGED
@@ -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.
|
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
|
-
|
77
|
-
|
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
|
-
|
82
|
-
-
|
83
|
-
|
84
|
-
|
85
|
-
-
|
86
|
-
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
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
|