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 +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
|