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