cottus 0.1.3

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 98294e799b9473ff9c55f0b3673ac133661a62e0
4
+ data.tar.gz: 222dbb234ad98069ce4991591d4e9d9d3d6e9803
5
+ SHA512:
6
+ metadata.gz: b4600796f17181e67de09adf662cf784306e121c110b4decc78a8853d1c136ca0e7dd4aadd92f581c5cbf5ddc27adf20d0c4061571ddb5560da7ed2d6ddc4754
7
+ data.tar.gz: 444b03bf8efb611f7e0c9570d6bb917da52f037b25fa57a76db38fe5c579737044d0d677e0f2e0a3d8aa111de96d5fe7e852131d36dda78934cf585c4898245f
@@ -0,0 +1,129 @@
1
+ # cottus
2
+
3
+ [![Build Status](https://travis-ci.org/mthssdrbrg/cottus.png?branch=master)](https://travis-ci.org/mthssdrbrg/cottus)
4
+ [![Coverage Status](https://coveralls.io/repos/mthssdrbrg/cottus/badge.png?branch=master)](https://coveralls.io/r/mthssdrbrg/cottus?branch=master)
5
+
6
+ Cottus, a multi limp HTTP client with an aim of making the use of multiple hosts
7
+ providing the same service easier with regards to timeouts and automatic fail-over.
8
+
9
+ Sure enough, if you don't mind using an ELB in EC2 and making your service public,
10
+ or setting up something like HAProxy, then this is not a client library for you.
11
+
12
+ Initialize a client with a list of hosts and it will happily round-robin load-balance
13
+ requests among them.
14
+ You could very well define your own strategy if you feel like it and inject it
15
+ into Cottus, more on that further down.
16
+
17
+ ## Installation
18
+
19
+ ```
20
+ gem install cottus
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require 'cottus'
27
+
28
+ client = Cottus::Client.new(['http://n1.com', 'http://n2.com', 'http://n3.com'])
29
+
30
+ # This request will be made against http://n1.com
31
+ response = client.get('/any/path', query: {id: 1337})
32
+ puts response.body, response.code, response.message, response.headers.inspect
33
+
34
+ # This request will be made against http://n2.com
35
+ response = client.post('/any/path', query: {id: 1337}, body: { attribute: 'cool'})
36
+ puts response.body, response.code, response.message, response.headers.inspect
37
+ ```
38
+
39
+ That's about it! Cottus exposes almost all of the same methods with the same semantics as
40
+ HTTParty does, with the exception of ```HTTParty#copy```.
41
+
42
+ ## Strategy
43
+
44
+ A "Strategy" is merely a class implementing an ```execute``` method that is
45
+ responsible for carrying out the action specified by the passed ```meth```
46
+ argument.
47
+
48
+ The Strategy class must however also implement an ```#initialize``` method which
49
+ takes three parameters: ```hosts```, ```client``` and an ```options``` hash:
50
+
51
+ ```ruby
52
+ class SomeStrategy
53
+ def initialize(hosts, client, options={})
54
+ end
55
+
56
+ def execute(meth, path, options={}, &block)
57
+ # do something funky here
58
+ end
59
+ end
60
+ ```
61
+
62
+ If you don't mind inheritance there's a base class (```Cottus::Strategy```) that
63
+ you can inherit from and the above class would instead become:
64
+
65
+ ```ruby
66
+ class SomeStrategy < Strategy
67
+ def execute(meth, path, options={}, &block)
68
+ # do something funky here
69
+ end
70
+ end
71
+ ```
72
+
73
+ If you'd like to do some initialization on your own and override
74
+ ```#initialize``` make sure to call ```#super``` or set the required instance
75
+ variables (```@hosts```, ```@client```) on your own.
76
+
77
+ It should be noted that I haven't decided on how strategies should be working to
78
+ a 100% yet, so this might change in future releases.
79
+
80
+ See ```lib/cottus/strategies.rb``` for further examples.
81
+
82
+ ### Using your own strategy
83
+
84
+ In order to use your own Strategy class, supply the name of the class in the
85
+ options hash as you create your instance, as such:
86
+
87
+ ```ruby
88
+ require 'cottus'
89
+
90
+ client = Cottus::Client.new(['http://n1.com', 'http://n2.com'], strategy: MyStrategy)
91
+ ```
92
+
93
+ Want some additional options passed when your strategy is initialized?
94
+
95
+ No problem! Pass them into the ```strategy_options``` sub-hash of the options
96
+ hash to the client.
97
+
98
+ ```ruby
99
+ require 'cottus'
100
+
101
+ client = Cottus::Client.new(['http://n1.com', 'http://n2.com'], strategy: MyStrategy,
102
+ strategy_options: { an_option: 'cool stuff!'})
103
+ ```
104
+
105
+ The options will be passed as an options hash to the strategy, as explained
106
+ above.
107
+
108
+ Boom! That's all there is, for the moment.
109
+
110
+ ## Cottus?
111
+
112
+ Cottus was one of the Hecatonchires of Greek mythology.
113
+ The Hecatonchires, "Hundred-Handed Ones" (also with 50 heads) were figures in an
114
+ archaic stage of Greek mythology.
115
+ Three giants of incredible strength and ferocity that surpassed that of all Titans whom they helped overthrow.
116
+
117
+ ## Copyright
118
+ Copyright 2013 Mathias Söderberg
119
+
120
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
121
+ this file except in compliance with the License. You may obtain a copy of the
122
+ License at
123
+
124
+ http://www.apache.org/licenses/LICENSE-2.0
125
+
126
+ Unless required by applicable law or agreed to in writing, software distributed
127
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
128
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
129
+ specific language governing permissions and limitations under the License.
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ require 'httparty'
4
+
5
+ require 'cottus/client'
6
+ require 'cottus/strategies'
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ module Cottus
4
+ class Client
5
+
6
+ attr_reader :hosts, :strategy
7
+
8
+ def initialize(hosts, options={})
9
+ @hosts = parse_hosts(hosts)
10
+ @strategy = create_strategy(options)
11
+ end
12
+
13
+ def get(path, options={}, &block)
14
+ @strategy.execute(:get, path, options, &block)
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)
43
+ end
44
+
45
+ private
46
+
47
+ def parse_hosts(hosts)
48
+ hosts.is_a?(String) ? hosts.split(',') : hosts
49
+ end
50
+
51
+ def http
52
+ HTTParty
53
+ end
54
+
55
+ def create_strategy(options)
56
+ strategy = (options[:strategy] || RoundRobinStrategy).new(hosts, http, options[:strategy_options])
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ module Cottus
4
+
5
+ VALID_EXCEPTIONS = [
6
+ Timeout::Error,
7
+ Errno::ECONNREFUSED,
8
+ Errno::ETIMEDOUT,
9
+ Errno::ECONNRESET
10
+ ].freeze
11
+
12
+ class Strategy
13
+ def initialize(hosts, client, options={})
14
+ @hosts, @client = hosts, client
15
+ end
16
+
17
+ def execute(meth, path, options={}, &block)
18
+ raise NotImplementedError, 'implement me in subclass'
19
+ end
20
+ end
21
+
22
+ class RoundRobinStrategy < Strategy
23
+ def initialize(hosts, client, options={})
24
+ super
25
+
26
+ @current = 0
27
+ @mutex = Mutex.new
28
+ end
29
+
30
+ def execute(meth, path, options={}, &block)
31
+ tries = 0
32
+
33
+ begin
34
+ @client.send(meth, next_host + path, options, &block)
35
+ rescue *VALID_EXCEPTIONS => e
36
+ if tries >= @hosts.count
37
+ raise e
38
+ else
39
+ tries += 1
40
+ retry
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def next_host
48
+ @mutex.synchronize do
49
+ h = @hosts[@current]
50
+ @current = (@current + 1) % @hosts.count
51
+ h
52
+ end
53
+ end
54
+ end
55
+
56
+ class RetryableRoundRobinStrategy < RoundRobinStrategy
57
+ def initialize(hosts, client, options={})
58
+ super
59
+
60
+ @timeouts = options[:timeouts] || [1, 3, 5]
61
+ end
62
+
63
+ def execute(meth, path, options={}, &block)
64
+ tries = 0
65
+ starting_host = host = next_host
66
+
67
+ begin
68
+ @client.send(meth, host + path, options, &block)
69
+ rescue *VALID_EXCEPTIONS => e
70
+ if tries < @timeouts.size
71
+ sleep @timeouts[tries]
72
+ tries += 1
73
+ retry
74
+ else
75
+ host = next_host
76
+ raise e if host == starting_host
77
+
78
+ tries = 0
79
+ retry
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Cottus
4
+ VERSION = '0.1.3'.freeze
5
+ end
@@ -0,0 +1,182 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Cottus
6
+ describe 'Client acceptance spec' do
7
+ shared_examples 'exception handling' do
8
+ context 'exceptions' do
9
+ context 'Timeout::Error' do
10
+ it 'attempts to use each host until one succeeds' do
11
+ stub_request(verb, 'http://localhost:1234/some/path').to_timeout
12
+ stub_request(verb, 'http://localhost:12345/some/path').to_timeout
13
+ request = stub_request(verb, 'http://localhost:12343/some/path')
14
+
15
+ client.send(verb, '/some/path')
16
+ expect(request).to have_been_requested
17
+ end
18
+
19
+ it 'gives up after trying all hosts' do
20
+ stub_request(verb, 'http://localhost:1234/some/path').to_timeout
21
+ stub_request(verb, 'http://localhost:12345/some/path').to_timeout
22
+ stub_request(verb, 'http://localhost:12343/some/path').to_timeout
23
+
24
+ expect { client.send(verb, '/some/path') }.to raise_error(Timeout::Error)
25
+ end
26
+ end
27
+
28
+ [Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET].each do |error|
29
+ context "#{error}" do
30
+ it 'attempts to use each host until one succeeds' do
31
+ stub_request(verb, 'http://localhost:1234/some/path').to_raise(error)
32
+ stub_request(verb, 'http://localhost:12345/some/path').to_raise(error)
33
+ request = stub_request(verb, 'http://localhost:12343/some/path')
34
+
35
+ client.send(verb, '/some/path')
36
+ expect(request).to have_been_requested
37
+ end
38
+
39
+ it 'gives up after trying all hosts' do
40
+ stub_request(verb, 'http://localhost:1234/some/path').to_raise(error)
41
+ stub_request(verb, 'http://localhost:12345/some/path').to_raise(error)
42
+ stub_request(verb, 'http://localhost:12343/some/path').to_raise(error)
43
+
44
+ expect { client.send(verb, '/some/path') }.to raise_error(error)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ shared_examples 'load balancing' do
52
+ context 'with several hosts' do
53
+ it 'uses the first host for the first request' do
54
+ request = stub_request(verb, 'http://localhost:1234/some/path')
55
+ client.send(verb, '/some/path')
56
+ expect(request).to have_been_requested
57
+ end
58
+
59
+ it 'uses the second host for the second request' do
60
+ stub_request(verb, 'http://localhost:1234/some/path')
61
+ request = stub_request(verb, 'http://localhost:12345/some/path')
62
+ 2.times { client.send(verb, '/some/path') }
63
+ expect(request).to have_been_requested
64
+ end
65
+ end
66
+
67
+ context 'with a single host' do
68
+ let :client do
69
+ Client.new('http://localhost:1234')
70
+ end
71
+
72
+ it 'uses the single host for the first request' do
73
+ request = stub_request(verb, 'http://localhost:1234/some/path')
74
+ client.send(verb, '/some/path')
75
+ expect(request).to have_been_requested
76
+ end
77
+
78
+ it 'uses the single host for the second request' do
79
+ request = stub_request(verb, 'http://localhost:1234/some/path')
80
+ 2.times { client.send(verb, '/some/path') }
81
+ expect(request).to have_been_requested.twice
82
+ end
83
+ end
84
+ end
85
+
86
+ let :client do
87
+ Client.new('http://localhost:1234,http://localhost:12345,http://localhost:12343')
88
+ end
89
+
90
+ describe '#get' do
91
+ include_examples 'load balancing' do
92
+ let(:verb) { :get }
93
+ end
94
+
95
+ include_examples 'exception handling' do
96
+ let(:verb) { :get }
97
+ end
98
+ end
99
+
100
+ describe '#post' do
101
+ include_examples 'load balancing' do
102
+ let(:verb) { :post }
103
+ end
104
+
105
+ include_examples 'exception handling' do
106
+ let(:verb) { :post }
107
+ end
108
+ end
109
+
110
+ describe '#put' do
111
+ include_examples 'load balancing' do
112
+ let(:verb) { :put }
113
+ end
114
+
115
+ include_examples 'exception handling' do
116
+ let(:verb) { :put }
117
+ end
118
+ end
119
+
120
+ describe '#head' do
121
+ include_examples 'load balancing' do
122
+ let(:verb) { :head }
123
+ end
124
+
125
+ include_examples 'exception handling' do
126
+ let(:verb) { :head }
127
+ end
128
+ end
129
+
130
+ describe '#patch' do
131
+ include_examples 'load balancing' do
132
+ let(:verb) { :patch }
133
+ end
134
+
135
+ include_examples 'exception handling' do
136
+ let(:verb) { :patch }
137
+ end
138
+ end
139
+
140
+ describe '#delete' do
141
+ include_examples 'load balancing' do
142
+ let(:verb) { :delete }
143
+ end
144
+
145
+ include_examples 'exception handling' do
146
+ let(:verb) { :delete }
147
+ end
148
+ end
149
+
150
+ describe '#move' do
151
+ include_examples 'load balancing' do
152
+ let(:verb) { :move }
153
+ end
154
+
155
+ include_examples 'exception handling' do
156
+ let(:verb) { :move }
157
+ end
158
+ end
159
+
160
+ describe '#options' do
161
+ include_examples 'load balancing' do
162
+ let(:verb) { :options }
163
+ end
164
+
165
+ include_examples 'exception handling' do
166
+ let(:verb) { :options }
167
+ end
168
+ end
169
+
170
+ describe '#copy' do
171
+ pending do
172
+ include_examples 'load balancing' do
173
+ let(:verb) { :options }
174
+ end
175
+
176
+ include_examples 'exception handling' do
177
+ let(:verb) { :options }
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Cottus
6
+ describe Client do
7
+ describe '#initialize' do
8
+ it 'accepts an array of hosts w/ ports' do
9
+ client = described_class.new(['host1:123', 'host2:125'])
10
+ expect(client.hosts).to eq ['host1:123', 'host2:125']
11
+ end
12
+
13
+ it 'accepts a connection string w/ ports' do
14
+ client = described_class.new('host1:1255,host2:1255,host3:1255')
15
+ expect(client.hosts).to eq ['host1:1255', 'host2:1255', 'host3:1255']
16
+ end
17
+ end
18
+
19
+ context 'retry strategy' do
20
+ context 'by default' do
21
+ let :client do
22
+ described_class.new('http://host1.com/')
23
+ end
24
+
25
+ it 'uses a simple round-robin strategy' do
26
+ expect(client.strategy).to be_a RoundRobinStrategy
27
+ end
28
+ end
29
+
30
+ context 'when given an explicit strategy' do
31
+ let :client do
32
+ described_class.new('http://localhost:1234', strategy: strategy)
33
+ end
34
+
35
+ let :strategy do
36
+ double(:strategy, new: strategy_impl)
37
+ end
38
+
39
+ let :strategy_impl do
40
+ double(:strategy_impl)
41
+ end
42
+
43
+ it 'uses given strategy' do
44
+ expect(client.strategy).to eq(strategy_impl)
45
+ end
46
+
47
+ context 'strategy options' do
48
+ it 'passes explicit options when creating strategy' do
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, anything, {timeouts: [1, 3, 5]})
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,186 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Cottus
6
+ describe Strategy do
7
+ let :strategy do
8
+ described_class.new(['http://n1.com', 'http://n2.com'], http)
9
+ end
10
+
11
+ let :http do
12
+ double(:http, meth: nil)
13
+ end
14
+
15
+ describe '#execute' do
16
+ it 'raises a NotImplementedError' do
17
+ expect { strategy.execute(:meth, '/some/path') }.to raise_error(NotImplementedError, 'implement me in subclass')
18
+ end
19
+ end
20
+ end
21
+
22
+ shared_examples 'a round-robin strategy' do
23
+ context 'with a single host' do
24
+ let :hosts do
25
+ ['n1']
26
+ end
27
+
28
+ it 'uses the single host for the first request' do
29
+ strategy.execute(:meth, '/some/path', query: { query: 1 })
30
+
31
+ expect(http).to have_received(:meth).with('n1/some/path', query: { query: 1 }).once
32
+ end
33
+
34
+ it 'uses the single host for the second request' do
35
+ 2.times { strategy.execute(:meth, '/some/path', query: { query: 1 }) }
36
+
37
+ expect(http).to have_received(:meth).with('n1/some/path', query: { query: 1 }).twice
38
+ end
39
+ end
40
+
41
+ context 'with several hosts' do
42
+ let :hosts do
43
+ ['n1', 'n2', 'n3']
44
+ end
45
+
46
+ it 'uses the first host for the first request' do
47
+ strategy.execute(:meth, '/some/path', query: { query: 1 })
48
+
49
+ expect(http).to have_received(:meth).with('n1/some/path', query: { query: 1 }).once
50
+ end
51
+
52
+ it 'uses the second host for the second request' do
53
+ 2.times { strategy.execute(:meth, '/some/path', query: { query: 1 }) }
54
+
55
+ expect(http).to have_received(:meth).with('n1/some/path', query: { query: 1 }).once
56
+ expect(http).to have_received(:meth).with('n2/some/path', query: { query: 1 }).once
57
+ end
58
+
59
+ it 'uses each host in turn' do
60
+ 3.times { strategy.execute(:meth, '/some/path', query: { query: 1 }) }
61
+
62
+ expect(http).to have_received(:meth).with('n1/some/path', query: { query: 1 }).once
63
+ expect(http).to have_received(:meth).with('n2/some/path', query: { query: 1 }).once
64
+ expect(http).to have_received(:meth).with('n3/some/path', query: { query: 1 }).once
65
+ end
66
+ end
67
+ end
68
+
69
+ describe RoundRobinStrategy do
70
+ let :strategy do
71
+ described_class.new(hosts, http)
72
+ end
73
+
74
+ let :http do
75
+ double(:http, meth: nil)
76
+ end
77
+
78
+ describe '#execute' do
79
+ context 'without exceptions' do
80
+ it_behaves_like 'a round-robin strategy'
81
+ end
82
+
83
+ context 'with exceptions' do
84
+ [Timeout::Error, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET].each do |error|
85
+ context "when #{error} is raised" do
86
+ context 'with a single host' do
87
+ let :hosts do
88
+ ['n1']
89
+ end
90
+
91
+ it 'gives up' do
92
+ hosts.each { |h| http.stub(:meth).with("#{h}/some/path", {}).and_raise(error) }
93
+
94
+ expect { strategy.execute(:meth, '/some/path') }.to raise_error(error)
95
+ end
96
+ end
97
+
98
+ context 'with several hosts' do
99
+ let :hosts do
100
+ ['n1', 'n2', 'n3']
101
+ end
102
+
103
+ it 'attempts to use each host until one succeeds' do
104
+ ['n1', 'n2'].each { |h| http.stub(:meth).with("#{h}/some/path", {}).and_raise(error) }
105
+
106
+ strategy.execute(:meth, '/some/path')
107
+ expect(http).to have_received(:meth).with('n3/some/path', {})
108
+ end
109
+
110
+ it 'gives up after trying all hosts' do
111
+ hosts.each { |h| http.stub(:meth).with("#{h}/some/path", {}).and_raise(error) }
112
+
113
+ expect { strategy.execute(:meth, '/some/path') }.to raise_error(error)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ describe RetryableRoundRobinStrategy do
123
+ let :strategy do
124
+ described_class.new(hosts, http, timeouts: [0, 0, 0])
125
+ end
126
+
127
+ let :http do
128
+ double(:http, meth: nil)
129
+ end
130
+
131
+ describe '#execute' do
132
+ context 'without any exceptions' do
133
+ it_behaves_like 'a round-robin strategy'
134
+ end
135
+
136
+ context 'with exceptions' do
137
+ [Timeout::Error, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET].each do |error|
138
+ context "when #{error} is raised" do
139
+ context 'with a single host' do
140
+ let :hosts do
141
+ ['n1']
142
+ end
143
+
144
+ it 'uses the single host for three consecutive exceptions' do
145
+ expect(http).to receive(:meth).with('n1/some/path', {}).exactly(3).times.and_raise(error)
146
+ expect(http).to receive(:meth).with('n1/some/path', {}).once
147
+ expect(strategy).to receive(:sleep).with(0).exactly(3).times
148
+
149
+ strategy.execute(:meth, '/some/path')
150
+ end
151
+
152
+ it 'gives up after three retries' do
153
+ expect(http).to receive(:meth).with('n1/some/path', {}).exactly(4).times.and_raise(error)
154
+ expect(strategy).to receive(:sleep).with(0).exactly(3).times
155
+
156
+ expect { strategy.execute(:meth, '/some/path') }.to raise_error(error)
157
+ end
158
+ end
159
+
160
+ context 'with several hosts' do
161
+ let :hosts do
162
+ ['n1', 'n2', 'n3']
163
+ end
164
+
165
+ it 'uses the same host for three consecutive exceptions' do
166
+ expect(http).to receive(:meth).with('n1/some/path', {}).exactly(3).times.and_raise(error)
167
+ expect(http).to receive(:meth).with('n1/some/path', {}).once
168
+ expect(strategy).to receive(:sleep).with(0).exactly(3).times
169
+
170
+ strategy.execute(:meth, '/some/path')
171
+ end
172
+
173
+ it 'switches host after three retries' do
174
+ expect(http).to receive(:meth).with('n1/some/path', {}).exactly(4).times.and_raise(error)
175
+ expect(http).to receive(:meth).with('n2/some/path', {}).once
176
+ expect(strategy).to receive(:sleep).with(0).exactly(3).times
177
+
178
+ strategy.execute(:meth, '/some/path')
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'webmock/rspec'
4
+
5
+ RSpec.configure do |config|
6
+ config.treat_symbols_as_metadata_keys_with_true_values = true
7
+ config.order = 'random'
8
+ end
9
+
10
+ require 'coveralls'
11
+ require 'simplecov'
12
+
13
+ if ENV.include?('TRAVIS')
14
+ Coveralls.wear!
15
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
16
+ end
17
+
18
+ SimpleCov.start do
19
+ add_group 'Source', 'lib'
20
+ add_group 'Unit tests', 'spec/cottus'
21
+ add_group 'Acceptance tests', 'spec/acceptance'
22
+ end
23
+
24
+ require 'cottus'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cottus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Mathias Söderberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: HTTP client for making requests against a set of hosts
28
+ email:
29
+ - mths@sdrbrg.se
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/cottus/client.rb
35
+ - lib/cottus/strategies.rb
36
+ - lib/cottus/version.rb
37
+ - lib/cottus.rb
38
+ - README.md
39
+ - spec/acceptance/cottus_acceptance_spec.rb
40
+ - spec/cottus/client_spec.rb
41
+ - spec/cottus/strategies_spec.rb
42
+ - spec/spec_helper.rb
43
+ homepage: https://github.com/mthssdrbrg/cottus
44
+ licenses:
45
+ - Apache License 2.0
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: 1.9.2
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.0.6
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Multi limp HTTP client
67
+ test_files:
68
+ - spec/acceptance/cottus_acceptance_spec.rb
69
+ - spec/cottus/client_spec.rb
70
+ - spec/cottus/strategies_spec.rb
71
+ - spec/spec_helper.rb