cottus 0.1.4 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/cottus/client.rb +15 -39
- data/lib/cottus/connection.rb +21 -0
- data/lib/cottus/forward.rb +20 -0
- data/lib/cottus/strategies.rb +15 -15
- data/lib/cottus/version.rb +1 -1
- data/lib/cottus.rb +2 -0
- data/spec/cottus/client_spec.rb +1 -1
- data/spec/cottus/strategies_spec.rb +54 -38
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 362f67d0f8c8fd3245548533ae68bbcf1e012957
|
4
|
+
data.tar.gz: be8e86a230fac1d891506d1616a84745b3c6f7d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c7139181bc75a318f1ad87e5989c7d8c76d67fa44ed512a628aad0be3a3c227bd5a4bb745a7879b2b980a52170a5528f0c43e7b89217cb8ecf5454a1a7b3c93
|
7
|
+
data.tar.gz: dda18ac01482b6d1885517dbfd231d637e590e1c930ef42409d66faefd5e1b7a9a6f2daeb7bf0184d3df9316cf3686ae3ce1758948695916aef0c2268d626e1e
|
data/README.md
CHANGED
@@ -46,11 +46,11 @@ responsible for carrying out the action specified by the passed ```meth```
|
|
46
46
|
argument.
|
47
47
|
|
48
48
|
The Strategy class must however also implement an ```#initialize``` method which
|
49
|
-
takes
|
49
|
+
takes two parameters: ```connections``` and an ```options``` hash:
|
50
50
|
|
51
51
|
```ruby
|
52
52
|
class SomeStrategy
|
53
|
-
def initialize(
|
53
|
+
def initialize(connections, options={})
|
54
54
|
end
|
55
55
|
|
56
56
|
def execute(meth, path, options={}, &block)
|
@@ -72,7 +72,7 @@ end
|
|
72
72
|
|
73
73
|
If you'd like to do some initialization on your own and override
|
74
74
|
```#initialize``` make sure to call ```#super``` or set the required instance
|
75
|
-
|
75
|
+
variable (```@connections```) on your own.
|
76
76
|
|
77
77
|
It should be noted that I haven't decided on how strategies should be working to
|
78
78
|
a 100% yet, so this might change in future releases.
|
data/lib/cottus/client.rb
CHANGED
@@ -2,60 +2,36 @@
|
|
2
2
|
|
3
3
|
module Cottus
|
4
4
|
class Client
|
5
|
+
extend Forward
|
5
6
|
|
6
|
-
|
7
|
+
forward :get, :put, :post, :delete, :head, :patch, :options, :move, :to => :@strategy, :through => :execute
|
8
|
+
|
9
|
+
attr_reader :connections, :strategy
|
7
10
|
|
8
11
|
def initialize(hosts, options={})
|
9
|
-
@
|
12
|
+
@connections = create_connections(hosts)
|
10
13
|
@strategy = create_strategy(options)
|
11
14
|
end
|
12
15
|
|
13
|
-
def
|
14
|
-
@
|
15
|
-
end
|
16
|
-
|
17
|
-
def put(path, options={}, &block)
|
18
|
-
@strategy.execute(:put, path, options, &block)
|
19
|
-
end
|
20
|
-
|
21
|
-
def post(path, options={}, &block)
|
22
|
-
@strategy.execute(:post, path, options, &block)
|
23
|
-
end
|
24
|
-
|
25
|
-
def delete(path, options={}, &block)
|
26
|
-
@strategy.execute(:delete, path, options, &block)
|
27
|
-
end
|
28
|
-
|
29
|
-
def head(path, options={}, &block)
|
30
|
-
@strategy.execute(:head, path, options, &block)
|
31
|
-
end
|
32
|
-
|
33
|
-
def patch(path, options={}, &block)
|
34
|
-
@strategy.execute(:patch, path, options, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
def options(path, options={}, &block)
|
38
|
-
@strategy.execute(:options, path, options, &block)
|
39
|
-
end
|
40
|
-
|
41
|
-
def move(path, options={}, &block)
|
42
|
-
@strategy.execute(:move, path, options, &block)
|
16
|
+
def hosts
|
17
|
+
@connections.map(&:host)
|
43
18
|
end
|
44
19
|
|
45
20
|
private
|
46
21
|
|
47
|
-
def
|
48
|
-
hosts.is_a?(String) ? hosts.split(',') : hosts
|
49
|
-
|
50
|
-
|
51
|
-
def http
|
52
|
-
HTTParty
|
22
|
+
def create_connections(hosts)
|
23
|
+
hosts = hosts.is_a?(String) ? hosts.split(',') : hosts
|
24
|
+
hosts.map { |host| Connection.new(http, host) }
|
53
25
|
end
|
54
26
|
|
55
27
|
def create_strategy(options)
|
56
28
|
strategy_options = options[:strategy_options] || {}
|
57
29
|
strategy_impl = options[:strategy] || RoundRobinStrategy
|
58
|
-
strategy_impl.new(
|
30
|
+
strategy_impl.new(connections, strategy_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def http
|
34
|
+
HTTParty
|
59
35
|
end
|
60
36
|
end
|
61
37
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cottus
|
4
|
+
class Connection
|
5
|
+
extend Forward
|
6
|
+
|
7
|
+
forward :get, :put, :post, :delete, :head, :patch, :options, :move, :to => :wrapper
|
8
|
+
|
9
|
+
attr_reader :host
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
@http, @host = *args
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def wrapper(verb, path, options={}, &blk)
|
18
|
+
@http.send(verb, @host + path, options, &blk)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cottus
|
4
|
+
module Forward
|
5
|
+
def forward(*args)
|
6
|
+
options = args.pop
|
7
|
+
to, through = options.values_at(:to, :through)
|
8
|
+
|
9
|
+
args.each do |verb|
|
10
|
+
define_method(verb) do |path, opts={}, &blk|
|
11
|
+
if to && through
|
12
|
+
instance_variable_get(to).send(through, verb, path, opts, &blk)
|
13
|
+
else
|
14
|
+
self.send(to, verb, path, opts, &blk)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/cottus/strategies.rb
CHANGED
@@ -10,8 +10,8 @@ module Cottus
|
|
10
10
|
].freeze
|
11
11
|
|
12
12
|
class Strategy
|
13
|
-
def initialize(
|
14
|
-
@
|
13
|
+
def initialize(connections, options={})
|
14
|
+
@connections = connections
|
15
15
|
end
|
16
16
|
|
17
17
|
def execute(meth, path, options={}, &block)
|
@@ -20,10 +20,10 @@ module Cottus
|
|
20
20
|
end
|
21
21
|
|
22
22
|
class RoundRobinStrategy < Strategy
|
23
|
-
def initialize(
|
23
|
+
def initialize(connections, options={})
|
24
24
|
super
|
25
25
|
|
26
|
-
@
|
26
|
+
@index = 0
|
27
27
|
@mutex = Mutex.new
|
28
28
|
end
|
29
29
|
|
@@ -31,9 +31,9 @@ module Cottus
|
|
31
31
|
tries = 0
|
32
32
|
|
33
33
|
begin
|
34
|
-
|
34
|
+
next_connection.send(meth, path, options, &block)
|
35
35
|
rescue *VALID_EXCEPTIONS => e
|
36
|
-
if tries >= @
|
36
|
+
if tries >= @connections.count
|
37
37
|
raise e
|
38
38
|
else
|
39
39
|
tries += 1
|
@@ -44,17 +44,17 @@ module Cottus
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
def
|
47
|
+
def next_connection
|
48
48
|
@mutex.synchronize do
|
49
|
-
|
50
|
-
@
|
51
|
-
|
49
|
+
connection = @connections[@index]
|
50
|
+
@index = (@index + 1) % @connections.count
|
51
|
+
connection
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
class RetryableRoundRobinStrategy < RoundRobinStrategy
|
57
|
-
def initialize(
|
57
|
+
def initialize(connections, options={})
|
58
58
|
super
|
59
59
|
|
60
60
|
@timeouts = options[:timeouts] || [1, 3, 5]
|
@@ -62,18 +62,18 @@ module Cottus
|
|
62
62
|
|
63
63
|
def execute(meth, path, options={}, &block)
|
64
64
|
tries = 0
|
65
|
-
|
65
|
+
starting_connection = connection = next_connection
|
66
66
|
|
67
67
|
begin
|
68
|
-
|
68
|
+
connection.send(meth, path, options, &block)
|
69
69
|
rescue *VALID_EXCEPTIONS => e
|
70
70
|
if tries < @timeouts.size
|
71
71
|
sleep @timeouts[tries]
|
72
72
|
tries += 1
|
73
73
|
retry
|
74
74
|
else
|
75
|
-
|
76
|
-
raise e if
|
75
|
+
connection = next_connection
|
76
|
+
raise e if connection == starting_connection
|
77
77
|
|
78
78
|
tries = 0
|
79
79
|
retry
|
data/lib/cottus/version.rb
CHANGED
data/lib/cottus.rb
CHANGED
data/spec/cottus/client_spec.rb
CHANGED
@@ -47,7 +47,7 @@ module Cottus
|
|
47
47
|
context 'strategy options' do
|
48
48
|
it 'passes explicit options when creating strategy' do
|
49
49
|
client = described_class.new('http://localhost:1234', strategy: strategy, strategy_options: {timeouts: [1, 3, 5]})
|
50
|
-
expect(strategy).to have_received(:new).with(anything,
|
50
|
+
expect(strategy).to have_received(:new).with(anything, {timeouts: [1, 3, 5]})
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -5,16 +5,24 @@ require 'spec_helper'
|
|
5
5
|
module Cottus
|
6
6
|
describe Strategy do
|
7
7
|
let :strategy do
|
8
|
-
described_class.new(
|
8
|
+
described_class.new(connections)
|
9
9
|
end
|
10
10
|
|
11
11
|
let :http do
|
12
|
-
double(:http,
|
12
|
+
double(:http, get: nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
let :hosts do
|
16
|
+
['http://n1.com', 'http://n2.com']
|
17
|
+
end
|
18
|
+
|
19
|
+
let :connections do
|
20
|
+
hosts.map { |host| Connection.new(http, host) }
|
13
21
|
end
|
14
22
|
|
15
23
|
describe '#execute' do
|
16
24
|
it 'raises a NotImplementedError' do
|
17
|
-
expect { strategy.execute(:
|
25
|
+
expect { strategy.execute(:get, '/some/path') }.to raise_error(NotImplementedError, 'implement me in subclass')
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
@@ -26,15 +34,15 @@ module Cottus
|
|
26
34
|
end
|
27
35
|
|
28
36
|
it 'uses the single host for the first request' do
|
29
|
-
strategy.execute(:
|
37
|
+
strategy.execute(:get, '/some/path', query: { query: 1 })
|
30
38
|
|
31
|
-
expect(http).to have_received(:
|
39
|
+
expect(http).to have_received(:get).with('n1/some/path', query: { query: 1 }).once
|
32
40
|
end
|
33
41
|
|
34
42
|
it 'uses the single host for the second request' do
|
35
|
-
2.times { strategy.execute(:
|
43
|
+
2.times { strategy.execute(:get, '/some/path', query: { query: 1 }) }
|
36
44
|
|
37
|
-
expect(http).to have_received(:
|
45
|
+
expect(http).to have_received(:get).with('n1/some/path', query: { query: 1 }).twice
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
@@ -44,35 +52,39 @@ module Cottus
|
|
44
52
|
end
|
45
53
|
|
46
54
|
it 'uses the first host for the first request' do
|
47
|
-
strategy.execute(:
|
55
|
+
strategy.execute(:get, '/some/path', query: { query: 1 })
|
48
56
|
|
49
|
-
expect(http).to have_received(:
|
57
|
+
expect(http).to have_received(:get).with('n1/some/path', query: { query: 1 }).once
|
50
58
|
end
|
51
59
|
|
52
60
|
it 'uses the second host for the second request' do
|
53
|
-
2.times { strategy.execute(:
|
61
|
+
2.times { strategy.execute(:get, '/some/path', query: { query: 1 }) }
|
54
62
|
|
55
|
-
expect(http).to have_received(:
|
56
|
-
expect(http).to have_received(:
|
63
|
+
expect(http).to have_received(:get).with('n1/some/path', query: { query: 1 }).once
|
64
|
+
expect(http).to have_received(:get).with('n2/some/path', query: { query: 1 }).once
|
57
65
|
end
|
58
66
|
|
59
67
|
it 'uses each host in turn' do
|
60
|
-
3.times { strategy.execute(:
|
68
|
+
3.times { strategy.execute(:get, '/some/path', query: { query: 1 }) }
|
61
69
|
|
62
|
-
expect(http).to have_received(:
|
63
|
-
expect(http).to have_received(:
|
64
|
-
expect(http).to have_received(:
|
70
|
+
expect(http).to have_received(:get).with('n1/some/path', query: { query: 1 }).once
|
71
|
+
expect(http).to have_received(:get).with('n2/some/path', query: { query: 1 }).once
|
72
|
+
expect(http).to have_received(:get).with('n3/some/path', query: { query: 1 }).once
|
65
73
|
end
|
66
74
|
end
|
67
75
|
end
|
68
76
|
|
69
77
|
describe RoundRobinStrategy do
|
70
78
|
let :strategy do
|
71
|
-
described_class.new(
|
79
|
+
described_class.new(connections)
|
72
80
|
end
|
73
81
|
|
74
82
|
let :http do
|
75
|
-
double(:http,
|
83
|
+
double(:http, get: nil)
|
84
|
+
end
|
85
|
+
|
86
|
+
let :connections do
|
87
|
+
hosts.map { |host| Connection.new(http, host) }
|
76
88
|
end
|
77
89
|
|
78
90
|
describe '#execute' do
|
@@ -89,9 +101,9 @@ module Cottus
|
|
89
101
|
end
|
90
102
|
|
91
103
|
it 'gives up' do
|
92
|
-
hosts.each { |h| http.stub(:
|
104
|
+
hosts.each { |h| http.stub(:get).with("#{h}/some/path", {}).and_raise(error) }
|
93
105
|
|
94
|
-
expect { strategy.execute(:
|
106
|
+
expect { strategy.execute(:get, '/some/path') }.to raise_error(error)
|
95
107
|
end
|
96
108
|
end
|
97
109
|
|
@@ -101,16 +113,16 @@ module Cottus
|
|
101
113
|
end
|
102
114
|
|
103
115
|
it 'attempts to use each host until one succeeds' do
|
104
|
-
['n1', 'n2'].each { |h| http.stub(:
|
116
|
+
['n1', 'n2'].each { |h| http.stub(:get).with("#{h}/some/path", {}).and_raise(error) }
|
105
117
|
|
106
|
-
strategy.execute(:
|
107
|
-
expect(http).to have_received(:
|
118
|
+
strategy.execute(:get, '/some/path')
|
119
|
+
expect(http).to have_received(:get).with('n3/some/path', {})
|
108
120
|
end
|
109
121
|
|
110
122
|
it 'gives up after trying all hosts' do
|
111
|
-
hosts.each { |h| http.stub(:
|
123
|
+
hosts.each { |h| http.stub(:get).with("#{h}/some/path", {}).and_raise(error) }
|
112
124
|
|
113
|
-
expect { strategy.execute(:
|
125
|
+
expect { strategy.execute(:get, '/some/path') }.to raise_error(error)
|
114
126
|
end
|
115
127
|
end
|
116
128
|
end
|
@@ -121,11 +133,15 @@ module Cottus
|
|
121
133
|
|
122
134
|
describe RetryableRoundRobinStrategy do
|
123
135
|
let :strategy do
|
124
|
-
described_class.new(
|
136
|
+
described_class.new(connections, timeouts: [0, 0, 0])
|
125
137
|
end
|
126
138
|
|
127
139
|
let :http do
|
128
|
-
double(:http,
|
140
|
+
double(:http, get: nil)
|
141
|
+
end
|
142
|
+
|
143
|
+
let :connections do
|
144
|
+
hosts.map { |host| Connection.new(http, host) }
|
129
145
|
end
|
130
146
|
|
131
147
|
describe '#execute' do
|
@@ -142,18 +158,18 @@ module Cottus
|
|
142
158
|
end
|
143
159
|
|
144
160
|
it 'uses the single host for three consecutive exceptions' do
|
145
|
-
expect(http).to receive(:
|
146
|
-
expect(http).to receive(:
|
161
|
+
expect(http).to receive(:get).with('n1/some/path', {}).exactly(3).times.and_raise(error)
|
162
|
+
expect(http).to receive(:get).with('n1/some/path', {}).once
|
147
163
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
148
164
|
|
149
|
-
strategy.execute(:
|
165
|
+
strategy.execute(:get, '/some/path')
|
150
166
|
end
|
151
167
|
|
152
168
|
it 'gives up after three retries' do
|
153
|
-
expect(http).to receive(:
|
169
|
+
expect(http).to receive(:get).with('n1/some/path', {}).exactly(4).times.and_raise(error)
|
154
170
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
155
171
|
|
156
|
-
expect { strategy.execute(:
|
172
|
+
expect { strategy.execute(:get, '/some/path') }.to raise_error(error)
|
157
173
|
end
|
158
174
|
end
|
159
175
|
|
@@ -163,19 +179,19 @@ module Cottus
|
|
163
179
|
end
|
164
180
|
|
165
181
|
it 'uses the same host for three consecutive exceptions' do
|
166
|
-
expect(http).to receive(:
|
167
|
-
expect(http).to receive(:
|
182
|
+
expect(http).to receive(:get).with('n1/some/path', {}).exactly(3).times.and_raise(error)
|
183
|
+
expect(http).to receive(:get).with('n1/some/path', {}).once
|
168
184
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
169
185
|
|
170
|
-
strategy.execute(:
|
186
|
+
strategy.execute(:get, '/some/path')
|
171
187
|
end
|
172
188
|
|
173
189
|
it 'switches host after three retries' do
|
174
|
-
expect(http).to receive(:
|
175
|
-
expect(http).to receive(:
|
190
|
+
expect(http).to receive(:get).with('n1/some/path', {}).exactly(4).times.and_raise(error)
|
191
|
+
expect(http).to receive(:get).with('n2/some/path', {}).once
|
176
192
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
177
193
|
|
178
|
-
strategy.execute(:
|
194
|
+
strategy.execute(:get, '/some/path')
|
179
195
|
end
|
180
196
|
end
|
181
197
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cottus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mathias Söderberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -32,6 +32,8 @@ extensions: []
|
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
34
|
- lib/cottus/client.rb
|
35
|
+
- lib/cottus/connection.rb
|
36
|
+
- lib/cottus/forward.rb
|
35
37
|
- lib/cottus/strategies.rb
|
36
38
|
- lib/cottus/version.rb
|
37
39
|
- lib/cottus.rb
|