and-son 0.6.1 → 0.7.0
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/and-son.gemspec +1 -1
- data/lib/and-son/call_runner.rb +142 -0
- data/lib/and-son/client.rb +68 -100
- data/lib/and-son/stored_responses.rb +25 -7
- data/lib/and-son/version.rb +1 -1
- data/test/helper.rb +3 -2
- data/test/support/factory.rb +6 -0
- data/test/system/making_requests_tests.rb +1 -1
- data/test/unit/call_runner_tests.rb +287 -0
- data/test/unit/client_tests.rb +219 -84
- data/test/unit/stored_responses_tests.rb +67 -56
- metadata +13 -10
- data/test/support/fake_connection.rb +0 -29
data/and-son.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_dependency("sanford-protocol", ["~> 0.
|
21
|
+
gem.add_dependency("sanford-protocol", ["~> 0.10"])
|
22
22
|
|
23
23
|
gem.add_development_dependency("assert", ["~> 2.12"])
|
24
24
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'logger'
|
3
|
+
require 'sanford-protocol'
|
4
|
+
require 'and-son/connection'
|
5
|
+
require 'and-son/response'
|
6
|
+
|
7
|
+
module AndSon
|
8
|
+
|
9
|
+
class CallRunner
|
10
|
+
|
11
|
+
DEFAULT_TIMEOUT = 60 # seconds
|
12
|
+
|
13
|
+
attr_reader :host, :port
|
14
|
+
attr_accessor :timeout_value, :params_value, :logger_value
|
15
|
+
|
16
|
+
def initialize(host, port)
|
17
|
+
@host = host
|
18
|
+
@port = port
|
19
|
+
@params_value = {}
|
20
|
+
@timeout_value = (ENV['ANDSON_TIMEOUT'] || DEFAULT_TIMEOUT).to_f
|
21
|
+
@logger_value = NullLogger.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# chain runner methods by returning itself
|
25
|
+
def call_runner; self; end
|
26
|
+
|
27
|
+
def call(name, params = nil)
|
28
|
+
params ||= {}
|
29
|
+
if !params.kind_of?(Hash)
|
30
|
+
raise ArgumentError, "expected params to be a Hash instead of a #{params.class}"
|
31
|
+
end
|
32
|
+
client_response = nil
|
33
|
+
benchmark = Benchmark.measure do
|
34
|
+
client_response = self.call!(name, params)
|
35
|
+
end
|
36
|
+
|
37
|
+
summary_line = SummaryLine.new({
|
38
|
+
'time' => RoundedTime.new(benchmark.real),
|
39
|
+
'status' => client_response.protocol_response.code,
|
40
|
+
'host' => "#{self.host}:#{self.port}",
|
41
|
+
'service' => name,
|
42
|
+
'params' => params
|
43
|
+
})
|
44
|
+
self.logger_value.info("[AndSon] #{summary_line}")
|
45
|
+
|
46
|
+
if block_given?
|
47
|
+
yield client_response.protocol_response
|
48
|
+
else
|
49
|
+
client_response.data
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def call!(name, params)
|
54
|
+
call_params = self.params_value.merge(params)
|
55
|
+
AndSon::Connection.new(host, port).open do |connection|
|
56
|
+
connection.write(Sanford::Protocol::Request.new(name, call_params).to_hash)
|
57
|
+
connection.close_write
|
58
|
+
if !connection.peek(timeout_value).empty?
|
59
|
+
AndSon::Response.parse(connection.read(timeout_value))
|
60
|
+
else
|
61
|
+
raise AndSon::ConnectionClosedError.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def hash
|
67
|
+
[ self.host,
|
68
|
+
self.port,
|
69
|
+
self.timeout_value,
|
70
|
+
self.params_value,
|
71
|
+
self.logger_value
|
72
|
+
].hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
other.kind_of?(self.class) ? self.hash == other.hash : super
|
77
|
+
end
|
78
|
+
alias :eql? :==
|
79
|
+
|
80
|
+
module InstanceMethods
|
81
|
+
|
82
|
+
# define methods here to allow configuring call runner params. be sure to
|
83
|
+
# use `tap` to return whatever instance `self.call_runner` returns so you
|
84
|
+
# can method-chain. `self.call_runner` returns a new runner instance if
|
85
|
+
# called on a client, but returns the chained instance if called on a runner
|
86
|
+
|
87
|
+
def timeout(seconds)
|
88
|
+
self.call_runner.tap{ |r| r.timeout_value = seconds.to_f }
|
89
|
+
end
|
90
|
+
|
91
|
+
def params(hash = nil)
|
92
|
+
if !hash.kind_of?(Hash)
|
93
|
+
raise ArgumentError, "expected params to be a Hash instead of a #{hash.class}"
|
94
|
+
end
|
95
|
+
self.call_runner.tap{ |r| r.params_value.merge!(stringify_keys(hash)) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def logger(passed_logger)
|
99
|
+
self.call_runner.tap{ |r| r.logger_value = passed_logger }
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def stringify_keys(hash)
|
105
|
+
hash.inject({}){|h, (k, v)| h.merge({ k.to_s => v }) }
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
include InstanceMethods
|
110
|
+
|
111
|
+
module SummaryLine
|
112
|
+
def self.new(line_attrs)
|
113
|
+
attr_keys = %w{time status host service params}
|
114
|
+
attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module RoundedTime
|
119
|
+
ROUND_PRECISION = 2
|
120
|
+
ROUND_MODIFIER = 10 ** ROUND_PRECISION
|
121
|
+
def self.new(time_in_seconds)
|
122
|
+
(time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class NullLogger
|
127
|
+
::Logger::Severity.constants.each do |name|
|
128
|
+
define_method(name.downcase){|*args| } # no-op
|
129
|
+
end
|
130
|
+
|
131
|
+
def hash; 1; end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
class ConnectionClosedError < RuntimeError
|
137
|
+
def initialize
|
138
|
+
super "The server closed the connection, no response was written."
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
data/lib/and-son/client.rb
CHANGED
@@ -1,145 +1,113 @@
|
|
1
|
-
require '
|
2
|
-
require 'logger'
|
3
|
-
require 'ostruct'
|
4
|
-
require 'sanford-protocol'
|
5
|
-
require 'and-son/connection'
|
6
|
-
require 'and-son/response'
|
1
|
+
require 'and-son/call_runner'
|
7
2
|
require 'and-son/stored_responses'
|
8
3
|
|
9
4
|
module AndSon
|
10
5
|
|
11
|
-
module
|
6
|
+
module Client
|
12
7
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
self.call_runner.tap{|r| r.timeout_value = seconds.to_f }
|
8
|
+
def self.new(host, port)
|
9
|
+
if !ENV['ANDSON_TEST_MODE']
|
10
|
+
AndSonClient.new(host, port)
|
11
|
+
else
|
12
|
+
TestClient.new(host, port)
|
13
|
+
end
|
20
14
|
end
|
21
15
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
16
|
+
def self.included(klass)
|
17
|
+
klass.class_eval do
|
18
|
+
include CallRunner::InstanceMethods
|
19
|
+
include InstanceMethods
|
25
20
|
end
|
26
|
-
self.call_runner.tap{|r| r.params_value.merge!(self.stringify_keys(hash)) }
|
27
21
|
end
|
28
22
|
|
29
|
-
|
30
|
-
self.call_runner.tap{|r| r.logger_value = passed_logger }
|
31
|
-
end
|
23
|
+
module InstanceMethods
|
32
24
|
|
33
|
-
|
25
|
+
attr_reader :host, :port
|
26
|
+
|
27
|
+
def initialize(host, port)
|
28
|
+
@host, @port = host, port
|
29
|
+
end
|
34
30
|
|
35
|
-
def stringify_keys(hash)
|
36
|
-
hash.inject({}){|h, (k, v)| h.merge({ k.to_s => v }) }
|
37
31
|
end
|
38
32
|
|
39
33
|
end
|
40
34
|
|
41
|
-
class
|
42
|
-
include
|
43
|
-
|
44
|
-
DEFAULT_TIMEOUT = 60 #seconds
|
45
|
-
|
46
|
-
attr_reader :host, :port, :responses
|
47
|
-
|
48
|
-
def initialize(host, port)
|
49
|
-
@host, @port = host, port
|
50
|
-
@responses = AndSon::StoredResponses.new
|
51
|
-
end
|
35
|
+
class AndSonClient
|
36
|
+
include Client
|
52
37
|
|
53
38
|
# proxy the call method to the call runner
|
54
39
|
def call(*args, &block); self.call_runner.call(*args, &block); end
|
55
40
|
|
56
41
|
def call_runner
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
:params_value => {},
|
63
|
-
:logger_value => NullLogger.new,
|
64
|
-
:responses => @responses,
|
65
|
-
})
|
42
|
+
AndSon::CallRunner.new(host, port)
|
43
|
+
end
|
44
|
+
|
45
|
+
def hash
|
46
|
+
self.call_runner.hash
|
66
47
|
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
other.kind_of?(self.class) ? self.hash == other.hash : super
|
51
|
+
end
|
52
|
+
alias :eql? :==
|
53
|
+
|
67
54
|
end
|
68
55
|
|
69
|
-
class
|
70
|
-
|
71
|
-
include CallRunnerMethods
|
56
|
+
class TestClient
|
57
|
+
include Client
|
72
58
|
|
73
|
-
|
74
|
-
|
59
|
+
attr_accessor :timeout_value, :params_value, :logger_value
|
60
|
+
attr_reader :calls, :responses
|
61
|
+
|
62
|
+
def initialize(host, port)
|
63
|
+
super
|
64
|
+
@params_value = {}
|
65
|
+
@calls = []
|
66
|
+
@responses = AndSon::StoredResponses.new
|
67
|
+
end
|
75
68
|
|
76
69
|
def call(name, params = nil)
|
77
70
|
params ||= {}
|
78
|
-
|
79
|
-
|
80
|
-
end
|
81
|
-
client_response = nil
|
82
|
-
benchmark = Benchmark.measure do
|
83
|
-
client_response = self.responses.find(name, params) if ENV['ANDSON_TEST_MODE']
|
84
|
-
client_response ||= self.call!(name, params)
|
85
|
-
end
|
86
|
-
|
87
|
-
summary_line = SummaryLine.new({
|
88
|
-
'time' => RoundedTime.new(benchmark.real),
|
89
|
-
'status' => client_response.protocol_response.code,
|
90
|
-
'host' => "#{self.host}:#{self.port}",
|
91
|
-
'service' => name,
|
92
|
-
'params' => params
|
93
|
-
})
|
94
|
-
self.logger_value.info("[AndSon] #{summary_line}")
|
95
|
-
|
71
|
+
response = self.responses.get(name, params)
|
72
|
+
self.calls << Call.new(name, params, response.protocol_response)
|
96
73
|
if block_given?
|
97
|
-
yield
|
74
|
+
yield response.protocol_response
|
98
75
|
else
|
99
|
-
|
76
|
+
response.data
|
100
77
|
end
|
101
78
|
end
|
102
79
|
|
103
|
-
def
|
104
|
-
call_params = self.params_value.merge(params)
|
105
|
-
AndSon::Connection.new(host, port).open do |connection|
|
106
|
-
connection.write(Sanford::Protocol::Request.new(name, call_params).to_hash)
|
107
|
-
connection.close_write
|
108
|
-
if !connection.peek(timeout_value).empty?
|
109
|
-
AndSon::Response.parse(connection.read(timeout_value))
|
110
|
-
else
|
111
|
-
raise AndSon::ConnectionClosedError.new
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
80
|
+
def call_runner; self; end
|
115
81
|
|
116
|
-
|
82
|
+
def add_response(*args, &block)
|
83
|
+
self.responses.add(*args, &block)
|
84
|
+
end
|
117
85
|
|
118
|
-
|
119
|
-
|
120
|
-
super "The server closed the connection, no response was written."
|
86
|
+
def remove_response(*args)
|
87
|
+
self.responses.remove(*args)
|
121
88
|
end
|
122
|
-
end
|
123
89
|
|
124
|
-
|
125
|
-
|
126
|
-
|
90
|
+
def reset
|
91
|
+
self.calls.clear
|
92
|
+
self.responses.remove_all
|
127
93
|
end
|
128
|
-
end
|
129
94
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
95
|
+
def hash
|
96
|
+
[ self.host,
|
97
|
+
self.port,
|
98
|
+
self.timeout_value,
|
99
|
+
self.params_value,
|
100
|
+
self.logger_value
|
101
|
+
].hash
|
134
102
|
end
|
135
|
-
end
|
136
103
|
|
137
|
-
|
138
|
-
|
139
|
-
ROUND_MODIFIER = 10 ** ROUND_PRECISION
|
140
|
-
def self.new(time_in_seconds)
|
141
|
-
(time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
|
104
|
+
def ==(other)
|
105
|
+
other.kind_of?(self.class) ? self.hash == other.hash : super
|
142
106
|
end
|
107
|
+
alias :eql? :==
|
108
|
+
|
109
|
+
Call = Struct.new(:request_name, :request_params, :response)
|
110
|
+
|
143
111
|
end
|
144
112
|
|
145
113
|
end
|
@@ -8,7 +8,7 @@ module AndSon
|
|
8
8
|
RequestData = Struct.new(:name, :params)
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@hash = {}
|
11
|
+
@hash = Hash.new{ default_response_proc }
|
12
12
|
end
|
13
13
|
|
14
14
|
def add(name, params = nil, &response_block)
|
@@ -16,13 +16,9 @@ module AndSon
|
|
16
16
|
@hash[request_data] = response_block
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def get(name, params = nil)
|
20
20
|
response_block = @hash[RequestData.new(name, params || {})]
|
21
|
-
|
22
|
-
response = response_block.call
|
23
|
-
if !response.kind_of?(Sanford::Protocol::Response)
|
24
|
-
response = Sanford::Protocol::Response.new(200, response)
|
25
|
-
end
|
21
|
+
response = handle_response_block(response_block)
|
26
22
|
AndSon::Response.new(response)
|
27
23
|
end
|
28
24
|
|
@@ -30,6 +26,28 @@ module AndSon
|
|
30
26
|
@hash.delete(RequestData.new(name, params || {}))
|
31
27
|
end
|
32
28
|
|
29
|
+
def remove_all
|
30
|
+
@hash.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def handle_response_block(response_block)
|
36
|
+
if response_block.arity == 0 || response_block.arity == -1
|
37
|
+
default_response.tap{ |r| r.data = response_block.call }
|
38
|
+
else
|
39
|
+
default_response.tap{ |r| response_block.call(r) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_response
|
44
|
+
Sanford::Protocol::Response.new(200, {})
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_response_proc
|
48
|
+
proc{ |r| r.data = Hash.new }
|
49
|
+
end
|
50
|
+
|
33
51
|
end
|
34
52
|
|
35
53
|
end
|
data/lib/and-son/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -43,7 +43,7 @@ class MakingRequestsTests < Assert::Context
|
|
43
43
|
|
44
44
|
should "return the registered response" do
|
45
45
|
client = AndSon.new('localhost', 12000)
|
46
|
-
client.
|
46
|
+
client.add_response('echo', 'message' => 'test'){ 'test' }
|
47
47
|
|
48
48
|
client.call('echo', 'message' => 'test') do |response|
|
49
49
|
assert_equal 200, response.code
|