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.
- checksums.yaml +7 -0
 - data/README.md +129 -0
 - data/lib/cottus.rb +6 -0
 - data/lib/cottus/client.rb +59 -0
 - data/lib/cottus/strategies.rb +84 -0
 - data/lib/cottus/version.rb +5 -0
 - data/spec/acceptance/cottus_acceptance_spec.rb +182 -0
 - data/spec/cottus/client_spec.rb +56 -0
 - data/spec/cottus/strategies_spec.rb +186 -0
 - data/spec/spec_helper.rb +24 -0
 - metadata +71 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -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
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,129 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # cottus
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            [](https://travis-ci.org/mthssdrbrg/cottus)
         
     | 
| 
      
 4 
     | 
    
         
            +
            [](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.
         
     | 
    
        data/lib/cottus.rb
    ADDED
    
    
| 
         @@ -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,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
         
     | 
    
        data/spec/spec_helper.rb
    ADDED
    
    | 
         @@ -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
         
     |