cottus 0.1.3

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