rest-core 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +31 -0
- data/README.md +1 -0
- data/lib/rest-core.rb +2 -1
- data/lib/rest-core/builder.rb +1 -2
- data/lib/rest-core/client.rb +35 -8
- data/lib/rest-core/client/universal.rb +4 -6
- data/lib/rest-core/event.rb +9 -3
- data/lib/rest-core/event_source.rb +1 -1
- data/lib/rest-core/middleware.rb +9 -2
- data/lib/rest-core/middleware/cache.rb +7 -6
- data/lib/rest-core/middleware/error_handler.rb +4 -30
- data/lib/rest-core/middleware/follow_redirect.rb +9 -13
- data/lib/rest-core/middleware/json_response.rb +1 -1
- data/lib/rest-core/middleware/retry.rb +33 -0
- data/lib/rest-core/middleware/timeout.rb +5 -12
- data/lib/rest-core/promise.rb +17 -7
- data/lib/rest-core/test.rb +1 -1
- data/lib/rest-core/timer.rb +1 -1
- data/lib/rest-core/util/parse_link.rb +2 -2
- data/lib/rest-core/util/parse_query.rb +1 -1
- data/lib/rest-core/version.rb +1 -1
- data/rest-core.gemspec +7 -4
- data/test/test_client.rb +12 -2
- data/test/test_error_handler.rb +11 -1
- data/test/test_event_source.rb +2 -10
- data/test/test_follow_redirect.rb +1 -0
- data/test/test_httpclient.rb +2 -4
- data/test/test_promise.rb +24 -0
- data/test/test_retry.rb +63 -0
- data/test/test_timeout.rb +6 -10
- data/test/test_universal.rb +24 -6
- metadata +6 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b4cd61e7344e03258e5ef9482bf9270e29115ee2
         | 
| 4 | 
            +
              data.tar.gz: fbc1f00dcb47c15daec112ea4cf6a7397331bc74
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ffc882cb23b1e1b9017d910c4fb34c77258f4c4afd93eceb6cfb96ef1068fb8afd356c330fdb36ea5bfc8345d985cacfedb72eb374422368d8b573c85220b93c
         | 
| 7 | 
            +
              data.tar.gz: aeb92a57d45711993ed90d561b9f4d39e780d5715c3d97acf5310d8089445a071ec0593958ecb7d024c147b3cb8e326153bbc64885e2b0fa89de10ff3c67b736
         | 
    
        data/CHANGES.md
    CHANGED
    
    | @@ -1,5 +1,36 @@ | |
| 1 1 | 
             
            # CHANGES
         | 
| 2 2 |  | 
| 3 | 
            +
            ## rest-core 3.5.0 -- 2014-12-09
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ### Incompatible changes
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * `RC::Builder` would now only deep copy arrays and hashes.
         | 
| 8 | 
            +
            * `RC::ErrorHandler`'s only responsibility is now creating the exception.
         | 
| 9 | 
            +
              Raising the exceptions or passing it to the callback is now handled by
         | 
| 10 | 
            +
              `RC::Client` instead. (Thanks Andrew Clunis, #6)
         | 
| 11 | 
            +
            * Since exceptions are raised by `RC::Client` now, `RC::Timeout` would not
         | 
| 12 | 
            +
              raise any exception, but just hand over to `RC::Client`.
         | 
| 13 | 
            +
            * Support for Ruby version < 1.9.2 is dropped.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ### Bugs fixed
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            * Reverted #10 because it caused the other encoding issue. (#12)
         | 
| 18 | 
            +
            * `RC::Client#wait` and `RC::Client.wait` would now properly wait for
         | 
| 19 | 
            +
              `RC::FollowRedirect`
         | 
| 20 | 
            +
            * `RC::Event::CacheHit` is properly logged again.
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            ### Enhancements
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            * Introduced `RC::Client#error_callback` which would get called for each
         | 
| 25 | 
            +
              exceptions raised. This is useful for monitoring and logging errors.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            * Introduced `RC::Retry` which could retry the request upon certain errors.
         | 
| 28 | 
            +
              Specify `max_retries` for maximum times for retrying, and `retry_exceptions`
         | 
| 29 | 
            +
              for which exceptions should be trying.
         | 
| 30 | 
            +
              Default is `[IOError, SystemCallError]`
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            * Eliminated a few warnings when `-w` is used.
         | 
| 33 | 
            +
             | 
| 3 34 | 
             
            ## rest-core 3.4.1 -- 2014-11-29
         | 
| 4 35 |  | 
| 5 36 | 
             
            ### Bugs fixed
         | 
    
        data/README.md
    CHANGED
    
    | @@ -531,6 +531,7 @@ simply ignore `:expires_in`. | |
| 531 531 | 
             
            [RC::Oauth2Header]: lib/rest-core/middleware/oauth2_header.rb
         | 
| 532 532 | 
             
            [RC::Oauth2Query]: lib/rest-core/middleware/oauth2_query.rb
         | 
| 533 533 | 
             
            [RC::SmashResponse]: lib/rest-core/middleware/smash_response.rb
         | 
| 534 | 
            +
            [RC::Retry]: lib/rest-core/middleware/retry.rb
         | 
| 534 535 | 
             
            [RC::Timeout]: lib/rest-core/middleware/timeout.rb
         | 
| 535 536 | 
             
            [moneta]: https://github.com/minad/moneta#expiration
         | 
| 536 537 |  | 
    
        data/lib/rest-core.rb
    CHANGED
    
    | @@ -68,6 +68,7 @@ module RestCore | |
| 68 68 | 
             
              autoload :Oauth1Header  , 'rest-core/middleware/oauth1_header'
         | 
| 69 69 | 
             
              autoload :Oauth2Header  , 'rest-core/middleware/oauth2_header'
         | 
| 70 70 | 
             
              autoload :Oauth2Query   , 'rest-core/middleware/oauth2_query'
         | 
| 71 | 
            +
              autoload :Retry         , 'rest-core/middleware/retry'
         | 
| 71 72 | 
             
              autoload :Timeout       , 'rest-core/middleware/timeout'
         | 
| 72 73 |  | 
| 73 74 | 
             
              # engines
         | 
| @@ -88,7 +89,7 @@ module RestCore | |
| 88 89 | 
             
                    c = const.const_get(n)
         | 
| 89 90 | 
             
                  rescue LoadError, NameError => e
         | 
| 90 91 | 
             
                    warn "RestCore: WARN: #{e} for #{const}\n" \
         | 
| 91 | 
            -
                         "  from #{e.backtrace.grep(/top.+required/ | 
| 92 | 
            +
                         "  from #{e.backtrace.grep(/top.+required/).first}"
         | 
| 92 93 | 
             
                  end
         | 
| 93 94 | 
             
                  eagerload(c, loaded) if c.respond_to?(:constants) && !loaded[n]
         | 
| 94 95 | 
             
                }
         | 
    
        data/lib/rest-core/builder.rb
    CHANGED
    
    | @@ -76,8 +76,7 @@ class RestCore::Builder | |
| 76 76 | 
             
                case obj
         | 
| 77 77 | 
             
                  when Array; obj.map{ |o| partial_deep_copy(o) }
         | 
| 78 78 | 
             
                  when Hash ; obj.inject({}){ |r, (k, v)| r[k] = partial_deep_copy(v); r }
         | 
| 79 | 
            -
                   | 
| 80 | 
            -
                  else begin obj.dup; rescue TypeError; obj; end
         | 
| 79 | 
            +
                  else      ; obj
         | 
| 81 80 | 
             
                end
         | 
| 82 81 | 
             
              end
         | 
| 83 82 |  | 
    
        data/lib/rest-core/client.rb
    CHANGED
    
    | @@ -41,12 +41,14 @@ module RestCore::Client | |
| 41 41 | 
             
              end
         | 
| 42 42 |  | 
| 43 43 | 
             
              attr_reader :app, :dry, :promises
         | 
| 44 | 
            +
              attr_accessor :error_callback
         | 
| 44 45 | 
             
              def initialize o={}
         | 
| 45 46 | 
             
                @app ||= self.class.builder.to_app # lighten! would reinitialize anyway
         | 
| 46 47 | 
             
                @dry ||= self.class.builder.to_app(Dry)
         | 
| 47 48 | 
             
                @promises = []  # don't record any promises in lighten!
         | 
| 48 49 | 
             
                @mutex    = nil # for locking promises, lazily initialized
         | 
| 49 50 | 
             
                                # for serialization
         | 
| 51 | 
            +
                @error_callback = nil
         | 
| 50 52 | 
             
                o.each{ |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
         | 
| 51 53 | 
             
              end
         | 
| 52 54 |  | 
| @@ -57,7 +59,7 @@ module RestCore::Client | |
| 57 59 | 
             
              def inspect
         | 
| 58 60 | 
             
                fields = if size > 0
         | 
| 59 61 | 
             
                           ' ' + attributes.map{ |k, v|
         | 
| 60 | 
            -
                             "#{k}=#{v.inspect.sub(/(?<=.{12}).{4,} | 
| 62 | 
            +
                             "#{k}=#{v.inspect.sub(/(?<=.{12}).{4,}/, '...')}"
         | 
| 61 63 | 
             
                           }.join(', ')
         | 
| 62 64 | 
             
                         else
         | 
| 63 65 | 
             
                           ''
         | 
| @@ -161,9 +163,20 @@ module RestCore::Client | |
| 161 163 | 
             
              end
         | 
| 162 164 |  | 
| 163 165 | 
             
              def request_full env, app=app, &k
         | 
| 164 | 
            -
                response = app.call(build_env({ASYNC => !!k}.merge(env)) | 
| 165 | 
            -
             | 
| 166 | 
            +
                response = app.call(build_env({ASYNC => !!k}.merge(env))) do |res|
         | 
| 167 | 
            +
                  (k || RC.id).call(request_complete(res))
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                give_promise(response)
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                if block_given?
         | 
| 173 | 
            +
                  self
         | 
| 174 | 
            +
                else
         | 
| 175 | 
            +
                  response
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
              end
         | 
| 166 178 |  | 
| 179 | 
            +
              def give_promise response
         | 
| 167 180 | 
             
                # under ASYNC callback, response might not be a response hash
         | 
| 168 181 | 
             
                # in that case (maybe in a user created engine), Client#wait
         | 
| 169 182 | 
             
                # won't work because we have no way to track the promise.
         | 
| @@ -173,11 +186,7 @@ module RestCore::Client | |
| 173 186 | 
             
                  self.class.give_promise(weak_promise, promises, mutex)
         | 
| 174 187 | 
             
                end
         | 
| 175 188 |  | 
| 176 | 
            -
                 | 
| 177 | 
            -
                  self
         | 
| 178 | 
            -
                else
         | 
| 179 | 
            -
                  response
         | 
| 180 | 
            -
                end
         | 
| 189 | 
            +
                response
         | 
| 181 190 | 
             
              end
         | 
| 182 191 |  | 
| 183 192 | 
             
              def build_env env={}
         | 
| @@ -200,6 +209,24 @@ module RestCore::Client | |
| 200 209 |  | 
| 201 210 |  | 
| 202 211 | 
             
              private
         | 
| 212 | 
            +
              def request_complete res
         | 
| 213 | 
            +
                if err = res[FAIL].find{ |f| f.kind_of?(Exception) }
         | 
| 214 | 
            +
                  RC::Promise.set_backtrace(err) unless err.backtrace
         | 
| 215 | 
            +
                  error_callback.call(err) if error_callback
         | 
| 216 | 
            +
                  if res[ASYNC]
         | 
| 217 | 
            +
                    if res[HIJACK]
         | 
| 218 | 
            +
                      res.merge(RESPONSE_SOCKET => err)
         | 
| 219 | 
            +
                    else
         | 
| 220 | 
            +
                      res.merge(RESPONSE_BODY => err)
         | 
| 221 | 
            +
                    end
         | 
| 222 | 
            +
                  else
         | 
| 223 | 
            +
                    raise err
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
                else
         | 
| 226 | 
            +
                  res
         | 
| 227 | 
            +
                end
         | 
| 228 | 
            +
              end
         | 
| 229 | 
            +
             | 
| 203 230 | 
             
              def mutex
         | 
| 204 231 | 
             
                @mutex ||= Mutex.new
         | 
| 205 232 | 
             
              end
         | 
| @@ -1,15 +1,14 @@ | |
| 1 1 |  | 
| 2 2 | 
             
            module RestCore
         | 
| 3 3 | 
             
              Universal = Builder.client do
         | 
| 4 | 
            -
                use Timeout       , 0
         | 
| 5 | 
            -
             | 
| 6 4 | 
             
                use DefaultSite   , nil
         | 
| 7 5 | 
             
                use DefaultHeaders, {}
         | 
| 8 6 | 
             
                use DefaultQuery  , {}
         | 
| 9 7 | 
             
                use DefaultPayload, {}
         | 
| 10 8 | 
             
                use JsonRequest   , false
         | 
| 11 9 | 
             
                use AuthBasic     , nil, nil
         | 
| 12 | 
            -
                use  | 
| 10 | 
            +
                use Retry         , 0, Retry::DefaultRetryExceptions
         | 
| 11 | 
            +
                use Timeout       , 0
         | 
| 13 12 | 
             
                use ErrorHandler  , nil
         | 
| 14 13 | 
             
                use ErrorDetectorHttp
         | 
| 15 14 |  | 
| @@ -17,10 +16,9 @@ module RestCore | |
| 17 16 | 
             
                use ClashResponse , false
         | 
| 18 17 | 
             
                use  JsonResponse , false
         | 
| 19 18 | 
             
                use QueryResponse , false
         | 
| 20 | 
            -
             | 
| 19 | 
            +
                use FollowRedirect, 10
         | 
| 20 | 
            +
                use CommonLogger  , method(:puts)
         | 
| 21 21 | 
             
                use Cache         , {}, 600 # default :expires_in 600 but the default
         | 
| 22 22 | 
             
                                            # cache {} didn't support it
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                use FollowRedirect, 10
         | 
| 25 23 | 
             
              end
         | 
| 26 24 | 
             
            end
         | 
    
        data/lib/rest-core/event.rb
    CHANGED
    
    | @@ -4,9 +4,14 @@ module RestCore | |
| 4 4 | 
             
                RestCore.const_defined?(:EventStruct)
         | 
| 5 5 |  | 
| 6 6 | 
             
              class Event < EventStruct
         | 
| 7 | 
            -
                 | 
| 8 | 
            -
                def  | 
| 9 | 
            -
             | 
| 7 | 
            +
                def name; self.class.name[/(?<=::)\w+$/]; end
         | 
| 8 | 
            +
                def to_s
         | 
| 9 | 
            +
                  if duration
         | 
| 10 | 
            +
                    "spent #{duration} #{name} #{message}"
         | 
| 11 | 
            +
                  else
         | 
| 12 | 
            +
                    "#{name} #{message}"
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 10 15 | 
             
              end
         | 
| 11 16 | 
             
              class Event::MultiDone    < Event; end
         | 
| 12 17 | 
             
              class Event::Requested    < Event; end
         | 
| @@ -14,4 +19,5 @@ module RestCore | |
| 14 19 | 
             
              class Event::CacheCleared < Event; end
         | 
| 15 20 | 
             
              class Event::Failed       < Event; end
         | 
| 16 21 | 
             
              class Event::WithHeader   < Event; end
         | 
| 22 | 
            +
              class Event::Retrying     < Event; end
         | 
| 17 23 | 
             
            end
         | 
| @@ -67,7 +67,7 @@ class RestCore::EventSource < Struct.new(:client, :path, :query, :opts, | |
| 67 67 | 
             
                  begin
         | 
| 68 68 | 
             
                    @onerror.call(error, sock) if @onerror
         | 
| 69 69 | 
             
                    onreconnect(error, sock)
         | 
| 70 | 
            -
                  rescue Exception | 
| 70 | 
            +
                  rescue Exception
         | 
| 71 71 | 
             
                    mutex.synchronize do
         | 
| 72 72 | 
             
                      @closed = true
         | 
| 73 73 | 
             
                      condv.signal # so we can't be reconnecting, need to try to unblock
         | 
    
        data/lib/rest-core/middleware.rb
    CHANGED
    
    | @@ -57,6 +57,13 @@ module RestCore::Middleware | |
| 57 57 | 
             
                  app
         | 
| 58 58 | 
             
                end
         | 
| 59 59 | 
             
              end
         | 
| 60 | 
            +
              def error_callback res, err
         | 
| 61 | 
            +
                res[CLIENT].error_callback.call(err) if
         | 
| 62 | 
            +
                  res[CLIENT] && res[CLIENT].error_callback
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
              def give_promise res
         | 
| 65 | 
            +
                res[CLIENT].give_promise(res) if res[CLIENT]
         | 
| 66 | 
            +
              end
         | 
| 60 67 |  | 
| 61 68 | 
             
              module_function
         | 
| 62 69 | 
             
              def request_uri env
         | 
| @@ -64,7 +71,7 @@ module RestCore::Middleware | |
| 64 71 | 
             
                if (query = (env[REQUEST_QUERY] || {}).select{ |k, v| v }).empty?
         | 
| 65 72 | 
             
                  env[REQUEST_PATH].to_s
         | 
| 66 73 | 
             
                else
         | 
| 67 | 
            -
                  q = if env[REQUEST_PATH] =~ /\?/ | 
| 74 | 
            +
                  q = if env[REQUEST_PATH] =~ /\?/ then '&' else '?' end
         | 
| 68 75 | 
             
                  "#{env[REQUEST_PATH]}#{q}#{percent_encode(query)}"
         | 
| 69 76 | 
             
                end
         | 
| 70 77 | 
             
              end
         | 
| @@ -81,7 +88,7 @@ module RestCore::Middleware | |
| 81 88 | 
             
              end
         | 
| 82 89 | 
             
              public :percent_encode
         | 
| 83 90 |  | 
| 84 | 
            -
              UNRESERVED = /[^a-zA-Z0-9\-\.\_\~]+/ | 
| 91 | 
            +
              UNRESERVED = /[^a-zA-Z0-9\-\.\_\~]+/
         | 
| 85 92 | 
             
              def escape string
         | 
| 86 93 | 
             
                string.gsub(UNRESERVED) do |s|
         | 
| 87 94 | 
             
                  "%#{s.unpack('H2' * s.bytesize).join('%')}".upcase
         | 
| @@ -48,8 +48,9 @@ class RestCore::Cache | |
| 48 48 | 
             
                uri = request_uri(env)
         | 
| 49 49 | 
             
                start_time = Time.now
         | 
| 50 50 | 
             
                return unless data = cache(env)[cache_key(env)]
         | 
| 51 | 
            -
                log(env | 
| 52 | 
            -
             | 
| 51 | 
            +
                res = log(env.merge(REQUEST_URI => uri),
         | 
| 52 | 
            +
                          Event::CacheHit.new(Time.now - start_time, uri))
         | 
| 53 | 
            +
                data_extract(data, res, k)
         | 
| 53 54 | 
             
              end
         | 
| 54 55 |  | 
| 55 56 | 
             
              private
         | 
| @@ -97,12 +98,12 @@ class RestCore::Cache | |
| 97 98 | 
             
                "#{ res[RESPONSE_BODY]}"
         | 
| 98 99 | 
             
              end
         | 
| 99 100 |  | 
| 100 | 
            -
              def data_extract data,  | 
| 101 | 
            +
              def data_extract data, res, k
         | 
| 101 102 | 
             
                _, status, headers, body =
         | 
| 102 | 
            -
                  data.match(/\A(\d+)\n((?:[^\n]+\n)*)\n\n(.*)\Z/ | 
| 103 | 
            +
                  data.match(/\A(\d+)\n((?:[^\n]+\n)*)\n\n(.*)\Z/m).to_a
         | 
| 103 104 |  | 
| 104 | 
            -
                Promise.claim( | 
| 105 | 
            -
                  Hash[(headers||'').scan(/([^:]+): ([^\n]+)\n/ | 
| 105 | 
            +
                Promise.claim(res, k, body, status.to_i,
         | 
| 106 | 
            +
                  Hash[(headers||'').scan(/([^:]+): ([^\n]+)\n/)]).future_response
         | 
| 106 107 | 
             
              end
         | 
| 107 108 |  | 
| 108 109 | 
             
              def cache_for? env
         | 
| @@ -7,38 +7,12 @@ class RestCore::ErrorHandler | |
| 7 7 |  | 
| 8 8 | 
             
              def call env
         | 
| 9 9 | 
             
                app.call(env){ |res|
         | 
| 10 | 
            -
                   | 
| 10 | 
            +
                  h = error_handler(res)
         | 
| 11 | 
            +
                  f = res[FAIL] || []
         | 
| 12 | 
            +
                  yield(if f.empty? || f.find{ |ff| ff.kind_of?(Exception) } || !h
         | 
| 11 13 | 
             
                          res
         | 
| 12 14 | 
             
                        else
         | 
| 13 | 
            -
                           | 
| 14 | 
            -
                          if err = res[FAIL].find{ |e| e.kind_of?(Exception) }
         | 
| 15 | 
            -
                            process(res, err)
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                          elsif h = error_handler(res)
         | 
| 18 | 
            -
                            # if the user provides an exception, hand it over
         | 
| 19 | 
            -
                            if (err = h.call(res)).kind_of?(Exception)
         | 
| 20 | 
            -
                              process(res, err)
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                            else # otherwise we report all of them
         | 
| 23 | 
            -
                              res.merge(FAIL => [res[FAIL], err].flatten.compact)
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                            end
         | 
| 26 | 
            -
                          else # no exceptions at all, then do nothing
         | 
| 27 | 
            -
                            res
         | 
| 28 | 
            -
                          end
         | 
| 15 | 
            +
                          fail(res, h.call(res))
         | 
| 29 16 | 
             
                        end)}
         | 
| 30 17 | 
             
              end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
              def process res, err
         | 
| 33 | 
            -
                RC::Promise.set_backtrace(err)
         | 
| 34 | 
            -
                if res[ASYNC]
         | 
| 35 | 
            -
                  if res[HIJACK]
         | 
| 36 | 
            -
                    res.merge(RESPONSE_SOCKET => err)
         | 
| 37 | 
            -
                  else
         | 
| 38 | 
            -
                    res.merge(RESPONSE_BODY => err)
         | 
| 39 | 
            -
                  end
         | 
| 40 | 
            -
                else
         | 
| 41 | 
            -
                  raise err
         | 
| 42 | 
            -
                end
         | 
| 43 | 
            -
              end
         | 
| 44 18 | 
             
            end
         | 
| @@ -6,19 +6,15 @@ class RestCore::FollowRedirect | |
| 6 6 | 
             
              include RestCore::Middleware
         | 
| 7 7 |  | 
| 8 8 | 
             
              def call env, &k
         | 
| 9 | 
            -
                 | 
| 10 | 
            -
             | 
| 11 | 
            -
                                max_redirects(env))
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                if e[DRY]
         | 
| 14 | 
            -
                  app.call(e, &k)
         | 
| 9 | 
            +
                if env[DRY]
         | 
| 10 | 
            +
                  app.call(env, &k)
         | 
| 15 11 | 
             
                else
         | 
| 16 | 
            -
                  app.call( | 
| 12 | 
            +
                  app.call(env){ |res| process(res, k) }
         | 
| 17 13 | 
             
                end
         | 
| 18 14 | 
             
              end
         | 
| 19 15 |  | 
| 20 16 | 
             
              def process res, k
         | 
| 21 | 
            -
                return k.call(res) if res | 
| 17 | 
            +
                return k.call(res) if max_redirects(res) <= 0
         | 
| 22 18 | 
             
                return k.call(res) if ![301,302,303,307].include?(res[RESPONSE_STATUS])
         | 
| 23 19 | 
             
                return k.call(res) if  [301,302    ,307].include?(res[RESPONSE_STATUS]) &&
         | 
| 24 20 | 
             
                                      ![:get, :head    ].include?(res[REQUEST_METHOD])
         | 
| @@ -30,10 +26,10 @@ class RestCore::FollowRedirect | |
| 30 26 | 
             
                             res[REQUEST_METHOD]
         | 
| 31 27 | 
             
                           end
         | 
| 32 28 |  | 
| 33 | 
            -
                call(res.merge( | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 29 | 
            +
                give_promise(call(res.merge(
         | 
| 30 | 
            +
                  REQUEST_METHOD => meth    ,
         | 
| 31 | 
            +
                  REQUEST_PATH   => location,
         | 
| 32 | 
            +
                  REQUEST_QUERY  => {}      ,
         | 
| 33 | 
            +
                  'max_redirects' => max_redirects(res) - 1), &k))
         | 
| 38 34 | 
             
              end
         | 
| 39 35 | 
             
            end
         | 
| @@ -30,7 +30,7 @@ class RestCore::JsonResponse | |
| 30 30 | 
             
              def process response
         | 
| 31 31 | 
             
                # StackExchange returns the problematic BOM! in UTF-8, so we need to
         | 
| 32 32 | 
             
                # strip it or it would break JSON parsers (i.e. yajl-ruby and json)
         | 
| 33 | 
            -
                body = response[RESPONSE_BODY].to_s.sub(/\A\xEF\xBB\xBF | 
| 33 | 
            +
                body = response[RESPONSE_BODY].to_s.sub(/\A\xEF\xBB\xBF/, '')
         | 
| 34 34 | 
             
                response.merge(RESPONSE_BODY => Json.decode("[#{body}]").first)
         | 
| 35 35 | 
             
                # [this].first is not needed for yajl-ruby
         | 
| 36 36 | 
             
              rescue Json.const_get(:ParseError) => error
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            require 'rest-core/middleware'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class RestCore::Retry
         | 
| 5 | 
            +
              def self.members; [:max_retries, :retry_exceptions]; end
         | 
| 6 | 
            +
              include RestCore::Middleware
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              DefaultRetryExceptions = [IOError, SystemCallError]
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def call env, &k
         | 
| 11 | 
            +
                if env[DRY]
         | 
| 12 | 
            +
                  app.call(env, &k)
         | 
| 13 | 
            +
                else
         | 
| 14 | 
            +
                  app.call(env){ |res| process(res, k) }
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def process res, k
         | 
| 19 | 
            +
                times = max_retries(res)
         | 
| 20 | 
            +
                return k.call(res) if times <= 0
         | 
| 21 | 
            +
                errors = retry_exceptions(res) || DefaultRetryExceptions
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                if idx = res[FAIL].index{ |f| errors.find{ |e| f.kind_of?(e) } }
         | 
| 24 | 
            +
                  err = res[FAIL].delete_at(idx)
         | 
| 25 | 
            +
                  error_callback(res, err)
         | 
| 26 | 
            +
                  env = res.merge('max_retries' => times - 1)
         | 
| 27 | 
            +
                  give_promise(call(log(
         | 
| 28 | 
            +
                    env, Event::Retrying.new(nil, "(#{times}) for: #{err.inspect}")), &k))
         | 
| 29 | 
            +
                else
         | 
| 30 | 
            +
                  k.call(res)
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -1,4 +1,6 @@ | |
| 1 1 |  | 
| 2 | 
            +
            require 'timeout'
         | 
| 3 | 
            +
             | 
| 2 4 | 
             
            require 'rest-core/middleware'
         | 
| 3 5 | 
             
            require 'rest-core/timer'
         | 
| 4 6 |  | 
| @@ -8,21 +10,12 @@ class RestCore::Timeout | |
| 8 10 |  | 
| 9 11 | 
             
              def call env, &k
         | 
| 10 12 | 
             
                return app.call(env, &k) if env[DRY] || timeout(env) == 0
         | 
| 11 | 
            -
                 | 
| 12 | 
            -
                  app.call(e){ |r|
         | 
| 13 | 
            -
                    if r[ASYNC] ||
         | 
| 14 | 
            -
                       !(exp = (r[FAIL]||[]).find{ |f| f.kind_of?(::Timeout::Error) })
         | 
| 15 | 
            -
                      # we do nothing special for callback and rest-client
         | 
| 16 | 
            -
                      k.call(r)
         | 
| 17 | 
            -
                    else
         | 
| 18 | 
            -
                      # it would go to this branch only under response future
         | 
| 19 | 
            -
                      raise exp
         | 
| 20 | 
            -
                    end}}
         | 
| 13 | 
            +
                process(env, &k)
         | 
| 21 14 | 
             
              end
         | 
| 22 15 |  | 
| 23 | 
            -
              def  | 
| 16 | 
            +
              def process env, &k
         | 
| 24 17 | 
             
                timer = Timer.new(timeout(env), timeout_error)
         | 
| 25 | 
            -
                 | 
| 18 | 
            +
                app.call(env.merge(TIMER => timer), &k)
         | 
| 26 19 | 
             
              rescue Exception
         | 
| 27 20 | 
             
                timer.cancel
         | 
| 28 21 | 
             
                raise
         | 
    
        data/lib/rest-core/promise.rb
    CHANGED
    
    | @@ -159,15 +159,15 @@ class RestCore::Promise | |
| 159 159 | 
             
                  never_raise_yield do
         | 
| 160 160 | 
             
                    env[TIMER].cancel if env[TIMER]
         | 
| 161 161 | 
             
                    self.class.set_backtrace(e)
         | 
| 162 | 
            -
                    # TODO: add error_log_method
         | 
| 163 | 
            -
                    warn "RestCore: ERROR: #{e}\n  from #{e.backtrace.inspect}"
         | 
| 164 162 | 
             
                  end
         | 
| 165 163 |  | 
| 166 | 
            -
                   | 
| 167 | 
            -
                     | 
| 168 | 
            -
                   | 
| 169 | 
            -
                     | 
| 170 | 
            -
                       | 
| 164 | 
            +
                  if done?
         | 
| 165 | 
            +
                    callback_error(e)
         | 
| 166 | 
            +
                  else
         | 
| 167 | 
            +
                    begin
         | 
| 168 | 
            +
                      rejecting(e) # i/o error
         | 
| 169 | 
            +
                    rescue Exception => e
         | 
| 170 | 
            +
                      callback_error(e)
         | 
| 171 171 | 
             
                    end
         | 
| 172 172 | 
             
                  end
         | 
| 173 173 | 
             
                end
         | 
| @@ -195,6 +195,16 @@ class RestCore::Promise | |
| 195 195 | 
             
                self.called = true
         | 
| 196 196 | 
             
              end
         | 
| 197 197 |  | 
| 198 | 
            +
              def callback_error e
         | 
| 199 | 
            +
                never_raise_yield do
         | 
| 200 | 
            +
                  if env[CLIENT].error_callback
         | 
| 201 | 
            +
                    env[CLIENT].error_callback.call(e)
         | 
| 202 | 
            +
                  else
         | 
| 203 | 
            +
                    warn "RestCore: ERROR: #{e}\n  from #{e.backtrace.inspect}"
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
              end
         | 
| 207 | 
            +
             | 
| 198 208 | 
             
              def cancel_task
         | 
| 199 209 | 
             
                mutex.synchronize do
         | 
| 200 210 | 
             
                  next if done?
         | 
    
        data/lib/rest-core/test.rb
    CHANGED
    
    | @@ -11,7 +11,7 @@ require 'yaml' | |
| 11 11 | 
             
            WebMock.disable_net_connect!(:allow_localhost => true)
         | 
| 12 12 | 
             
            Pork::Executor.__send__(:include, Muack::API, WebMock::API)
         | 
| 13 13 |  | 
| 14 | 
            -
             | 
| 14 | 
            +
            class Pork::Executor
         | 
| 15 15 | 
             
              def with_img
         | 
| 16 16 | 
             
                f = Tempfile.new(['img', '.jpg'])
         | 
| 17 17 | 
             
                n = File.basename(f.path)
         | 
    
        data/lib/rest-core/timer.rb
    CHANGED
    
    
| @@ -4,12 +4,12 @@ module RestCore::ParseLink | |
| 4 4 | 
             
              module_function
         | 
| 5 5 | 
             
              # http://tools.ietf.org/html/rfc5988
         | 
| 6 6 | 
             
              parname = '"?([^"]+)"?'
         | 
| 7 | 
            -
              LINKPARAM = /#{parname}=#{parname}/ | 
| 7 | 
            +
              LINKPARAM = /#{parname}=#{parname}/
         | 
| 8 8 | 
             
              def parse_link link
         | 
| 9 9 | 
             
                link.split(',').inject({}) do |r, value|
         | 
| 10 10 | 
             
                  uri, *pairs = value.split(';')
         | 
| 11 11 | 
             
                  params = Hash[pairs.map{ |p| p.strip.match(LINKPARAM)[1..2] }]
         | 
| 12 | 
            -
                  r[params['rel']] = params.merge('uri' => uri[/<([^>]+) | 
| 12 | 
            +
                  r[params['rel']] = params.merge('uri' => uri[/<([^>]+)>/, 1])
         | 
| 13 13 | 
             
                  r
         | 
| 14 14 | 
             
                end
         | 
| 15 15 | 
             
              end
         | 
| @@ -12,7 +12,7 @@ module RestCore::ParseQuery | |
| 12 12 | 
             
                def parse_query(qs, d = nil)
         | 
| 13 13 | 
             
                  params = {}
         | 
| 14 14 |  | 
| 15 | 
            -
                  (qs || '').split(d ? /[#{d}] */ | 
| 15 | 
            +
                  (qs || '').split(d ? /[#{d}] */n : /[&;] */n).each do |p|
         | 
| 16 16 | 
             
                    k, v = p.split('=', 2).map { |x| URI.decode_www_form_component(x) }
         | 
| 17 17 | 
             
                    if cur = params[k]
         | 
| 18 18 | 
             
                      if cur.class == Array
         | 
    
        data/lib/rest-core/version.rb
    CHANGED
    
    
    
        data/rest-core.gemspec
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            -
            # stub: rest-core 3. | 
| 2 | 
            +
            # stub: rest-core 3.5.0 ruby lib
         | 
| 3 3 |  | 
| 4 4 | 
             
            Gem::Specification.new do |s|
         | 
| 5 5 | 
             
              s.name = "rest-core"
         | 
| 6 | 
            -
              s.version = "3. | 
| 6 | 
            +
              s.version = "3.5.0"
         | 
| 7 7 |  | 
| 8 8 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 9 9 | 
             
              s.require_paths = ["lib"]
         | 
| 10 10 | 
             
              s.authors = ["Lin Jen-Shin (godfat)"]
         | 
| 11 | 
            -
              s.date = "2014- | 
| 11 | 
            +
              s.date = "2014-12-09"
         | 
| 12 12 | 
             
              s.description = "Modular Ruby clients interface for REST APIs.\n\nThere has been an explosion in the number of REST APIs available today.\nTo address the need for a way to access these APIs easily and elegantly,\nwe have developed rest-core, which consists of composable middleware\nthat allows you to build a REST client for any REST API. Or in the case of\ncommon APIs such as Facebook, Github, and Twitter, you can simply use the\ndedicated clients provided by [rest-more][].\n\n[rest-more]: https://github.com/godfat/rest-more"
         | 
| 13 13 | 
             
              s.email = ["godfat (XD) godfat.org"]
         | 
| 14 14 | 
             
              s.files = [
         | 
| @@ -57,6 +57,7 @@ Gem::Specification.new do |s| | |
| 57 57 | 
             
              "lib/rest-core/middleware/oauth2_header.rb",
         | 
| 58 58 | 
             
              "lib/rest-core/middleware/oauth2_query.rb",
         | 
| 59 59 | 
             
              "lib/rest-core/middleware/query_response.rb",
         | 
| 60 | 
            +
              "lib/rest-core/middleware/retry.rb",
         | 
| 60 61 | 
             
              "lib/rest-core/middleware/smash_response.rb",
         | 
| 61 62 | 
             
              "lib/rest-core/middleware/timeout.rb",
         | 
| 62 63 | 
             
              "lib/rest-core/promise.rb",
         | 
| @@ -103,6 +104,7 @@ Gem::Specification.new do |s| | |
| 103 104 | 
             
              "test/test_payload.rb",
         | 
| 104 105 | 
             
              "test/test_promise.rb",
         | 
| 105 106 | 
             
              "test/test_query_response.rb",
         | 
| 107 | 
            +
              "test/test_retry.rb",
         | 
| 106 108 | 
             
              "test/test_simple.rb",
         | 
| 107 109 | 
             
              "test/test_smash.rb",
         | 
| 108 110 | 
             
              "test/test_smash_response.rb",
         | 
| @@ -111,7 +113,7 @@ Gem::Specification.new do |s| | |
| 111 113 | 
             
              "test/test_universal.rb"]
         | 
| 112 114 | 
             
              s.homepage = "https://github.com/godfat/rest-core"
         | 
| 113 115 | 
             
              s.licenses = ["Apache License 2.0"]
         | 
| 114 | 
            -
              s.rubygems_version = "2.4. | 
| 116 | 
            +
              s.rubygems_version = "2.4.5"
         | 
| 115 117 | 
             
              s.summary = "Modular Ruby clients interface for REST APIs."
         | 
| 116 118 | 
             
              s.test_files = [
         | 
| 117 119 | 
             
              "test/test_auth_basic.rb",
         | 
| @@ -141,6 +143,7 @@ Gem::Specification.new do |s| | |
| 141 143 | 
             
              "test/test_payload.rb",
         | 
| 142 144 | 
             
              "test/test_promise.rb",
         | 
| 143 145 | 
             
              "test/test_query_response.rb",
         | 
| 146 | 
            +
              "test/test_retry.rb",
         | 
| 144 147 | 
             
              "test/test_simple.rb",
         | 
| 145 148 | 
             
              "test/test_smash.rb",
         | 
| 146 149 | 
             
              "test/test_smash_response.rb",
         | 
    
        data/test/test_client.rb
    CHANGED
    
    | @@ -7,7 +7,7 @@ describe RC::Simple do | |
| 7 7 | 
             
                Muack.verify
         | 
| 8 8 | 
             
              end
         | 
| 9 9 |  | 
| 10 | 
            -
              url = 'http:// | 
| 10 | 
            +
              url = 'http://example.com/'
         | 
| 11 11 |  | 
| 12 12 | 
             
              would 'do simple request' do
         | 
| 13 13 | 
             
                c = RC::Simple.new
         | 
| @@ -80,7 +80,7 @@ describe RC::Simple do | |
| 80 80 | 
             
              end
         | 
| 81 81 |  | 
| 82 82 | 
             
              would 'cleanup promises' do
         | 
| 83 | 
            -
                stub_request(:get, url)
         | 
| 83 | 
            +
                stub_request(:get, url).to_return(:body => 'nnf')
         | 
| 84 84 | 
             
                client = RC::Builder.client
         | 
| 85 85 | 
             
                5.times{ client.new.get(url) }
         | 
| 86 86 | 
             
                Thread.pass
         | 
| @@ -164,4 +164,14 @@ describe RC::Simple do | |
| 164 164 | 
             
                client.wait
         | 
| 165 165 | 
             
                client.should.nil? # to make sure the inner most block did run
         | 
| 166 166 | 
             
              end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
              would 'call error_callback' do
         | 
| 169 | 
            +
                error = nil
         | 
| 170 | 
            +
                error_callback = lambda{ |e| error = e }
         | 
| 171 | 
            +
                should.raise(Errno::ECONNREFUSED) do
         | 
| 172 | 
            +
                  RC::Simple.new(:error_callback => error_callback).
         | 
| 173 | 
            +
                    get('http://localhost/').tap{}
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
                error.should.kind_of?(Errno::ECONNREFUSED)
         | 
| 176 | 
            +
              end
         | 
| 167 177 | 
             
            end
         | 
    
        data/test/test_error_handler.rb
    CHANGED
    
    | @@ -12,7 +12,7 @@ describe RC::ErrorHandler do | |
| 12 12 | 
             
              describe 'there is an exception' do
         | 
| 13 13 | 
             
                would 'raise an error with future' do
         | 
| 14 14 | 
             
                  lambda{
         | 
| 15 | 
            -
             | 
| 15 | 
            +
                    client.new.get('/', {}, RC::FAIL => [exp.new('fail')])
         | 
| 16 16 | 
             
                  }.should.raise(exp)
         | 
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| @@ -56,4 +56,14 @@ describe RC::ErrorHandler do | |
| 56 56 | 
             
                end
         | 
| 57 57 | 
             
                client.wait
         | 
| 58 58 | 
             
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              would 'not call error_handler if there is already an exception' do
         | 
| 61 | 
            +
                called = false
         | 
| 62 | 
            +
                RC::Builder.client do
         | 
| 63 | 
            +
                  use RC::ErrorHandler, lambda{ |_| called = true }
         | 
| 64 | 
            +
                end.new.get('http://localhost') do |error|
         | 
| 65 | 
            +
                  error.should.kind_of?(SystemCallError)
         | 
| 66 | 
            +
                end.wait
         | 
| 67 | 
            +
                called.should.eq false
         | 
| 68 | 
            +
              end
         | 
| 59 69 | 
             
            end
         | 
    
        data/test/test_event_source.rb
    CHANGED
    
    | @@ -133,21 +133,13 @@ SSE | |
| 133 133 | 
             
                m.should.empty?
         | 
| 134 134 | 
             
              end
         | 
| 135 135 |  | 
| 136 | 
            -
              def mock_warning
         | 
| 137 | 
            -
                mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
         | 
| 138 | 
            -
                  msg.should.include?(Errno::ECONNREFUSED.new.message)
         | 
| 139 | 
            -
                end
         | 
| 140 | 
            -
              end
         | 
| 141 | 
            -
             | 
| 142 136 | 
             
              would 'not deadlock without ErrorHandler' do
         | 
| 143 | 
            -
                mock_warning
         | 
| 144 137 | 
             
                c = RC::Simple.new.event_source('http://localhost:1')
         | 
| 145 | 
            -
                c.onerror{ |e| e.should. | 
| 138 | 
            +
                c.onerror{ |e| e.should.kind_of?(Errno::ECONNREFUSED) }
         | 
| 146 139 | 
             
                c.start.wait
         | 
| 147 140 | 
             
              end
         | 
| 148 141 |  | 
| 149 142 | 
             
              would 'not deadlock with ErrorHandler' do
         | 
| 150 | 
            -
                mock_warning
         | 
| 151 143 | 
             
                c = RC::Universal.new(:log_method => false).
         | 
| 152 144 | 
             
                           event_source('http://localhost:1')
         | 
| 153 145 | 
             
                c.onerror{ |e| e.should.kind_of?(Errno::ECONNREFUSED) }
         | 
| @@ -156,7 +148,7 @@ SSE | |
| 156 148 |  | 
| 157 149 | 
             
              would 'not deadlock if errors in onmessage' do
         | 
| 158 150 | 
             
                err = nil
         | 
| 159 | 
            -
                es,  | 
| 151 | 
            +
                es, _, _ = server.call
         | 
| 160 152 | 
             
                es.onmessage do |event, data|
         | 
| 161 153 | 
             
                  raise err = "error"
         | 
| 162 154 | 
             
                end.start.wait
         | 
    
        data/test/test_httpclient.rb
    CHANGED
    
    | @@ -39,11 +39,9 @@ describe RC::HttpClient do | |
| 39 39 | 
             
                end
         | 
| 40 40 |  | 
| 41 41 | 
             
                would 'not kill the thread if error was coming from the task' do
         | 
| 42 | 
            -
                  mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
         | 
| 43 | 
            -
                    msg.should.include?('boom')
         | 
| 44 | 
            -
                  end
         | 
| 45 42 | 
             
                  mock(HTTPClient).new{ raise 'boom' }.with_any_args
         | 
| 46 | 
            -
                  c.request(RC::RESPONSE_KEY => RC::FAIL | 
| 43 | 
            +
                  c.request(RC::RESPONSE_KEY => RC::FAIL,
         | 
| 44 | 
            +
                            RC::ASYNC => true).first.message.should.eq 'boom'
         | 
| 47 45 | 
             
                  Muack.verify
         | 
| 48 46 | 
             
                end
         | 
| 49 47 | 
             
              end
         | 
    
        data/test/test_promise.rb
    CHANGED
    
    | @@ -45,6 +45,27 @@ describe RC::Promise do | |
| 45 45 | 
             
                @promise.send(:headers).should.eq('K' => 'V')
         | 
| 46 46 | 
             
              end
         | 
| 47 47 |  | 
| 48 | 
            +
              would 'warn on callback error' do
         | 
| 49 | 
            +
                mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
         | 
| 50 | 
            +
                  msg.should.eq 'boom'
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                @client.new.get('http://localhost/') do |err|
         | 
| 54 | 
            +
                  err.should.kind_of?(Errno::ECONNREFUSED)
         | 
| 55 | 
            +
                  raise 'boom'
         | 
| 56 | 
            +
                end.wait
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              would 'call error_callback on errors' do
         | 
| 60 | 
            +
                errors = []
         | 
| 61 | 
            +
                @client.new(:error_callback => lambda{ |e| errors << e }).
         | 
| 62 | 
            +
                  get('http://localhost/') do |err|
         | 
| 63 | 
            +
                    err.should.kind_of?(Errno::ECONNREFUSED)
         | 
| 64 | 
            +
                    raise 'boom'
         | 
| 65 | 
            +
                  end.wait
         | 
| 66 | 
            +
                errors.map(&:class).should.eq [Errno::ECONNREFUSED, RuntimeError]
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 48 69 | 
             
              would 'then then then' do
         | 
| 49 70 | 
             
                plusone = lambda do |r|
         | 
| 50 71 | 
             
                  r.merge(RC::RESPONSE_BODY => r[RC::RESPONSE_BODY] + 1)
         | 
| @@ -65,10 +86,13 @@ describe RC::Promise do | |
| 65 86 | 
             
              would 'call in a new thread if pool_size == 0' do
         | 
| 66 87 | 
             
                @client.pool_size = 0
         | 
| 67 88 | 
             
                thread = nil
         | 
| 89 | 
            +
                rd, wr = IO.pipe
         | 
| 68 90 | 
             
                mock(Thread).new.with_any_args.peek_return do |t|
         | 
| 69 91 | 
             
                  thread = t
         | 
| 92 | 
            +
                  wr.puts
         | 
| 70 93 | 
             
                end
         | 
| 71 94 | 
             
                @promise.defer do
         | 
| 95 | 
            +
                  rd.gets
         | 
| 72 96 | 
             
                  Thread.current.should.eq thread
         | 
| 73 97 | 
             
                  @promise.reject(nil)
         | 
| 74 98 | 
             
                end
         | 
    
        data/test/test_retry.rb
    ADDED
    
    | @@ -0,0 +1,63 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            require 'rest-core/test'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe RC::Retry do
         | 
| 5 | 
            +
              before do
         | 
| 6 | 
            +
                @called = called = []
         | 
| 7 | 
            +
                @errors = errors = []
         | 
| 8 | 
            +
                engine = Class.new do
         | 
| 9 | 
            +
                  define_method :call do |env, &block|
         | 
| 10 | 
            +
                    called << true
         | 
| 11 | 
            +
                    env[RC::FAIL].should.eq [true]
         | 
| 12 | 
            +
                    block.call(env.merge(RC::FAIL => [true, errors.shift]))
         | 
| 13 | 
            +
                    {}
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end.new
         | 
| 16 | 
            +
                @app = RC::Retry.new(engine, 5)
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              after do
         | 
| 20 | 
            +
                @errors.size.should.eq 0
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def call env={}
         | 
| 24 | 
            +
                @app.call({RC::FAIL => [true]}.merge(env)){}
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              def max_retries
         | 
| 28 | 
            +
                @app.max_retries({})
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              would 'retry max_retries times' do
         | 
| 32 | 
            +
                @errors.replace([IOError.new] * max_retries)
         | 
| 33 | 
            +
                call
         | 
| 34 | 
            +
                @called.size.should.eq max_retries + 1
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              would 'retry several times' do
         | 
| 38 | 
            +
                @errors.replace([IOError.new] * 2)
         | 
| 39 | 
            +
                call
         | 
| 40 | 
            +
                @called.size.should.eq 3
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              would 'not retry RuntimeError by default' do
         | 
| 44 | 
            +
                @errors.replace([RuntimeError.new])
         | 
| 45 | 
            +
                call
         | 
| 46 | 
            +
                @called.size.should.eq 1
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              would 'retry RuntimeError when setup' do
         | 
| 50 | 
            +
                @errors.replace([RuntimeError.new] * max_retries)
         | 
| 51 | 
            +
                @app.retry_exceptions = [RuntimeError]
         | 
| 52 | 
            +
                call
         | 
| 53 | 
            +
                @called.size.should.eq max_retries + 1
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              would 'call error_callback upon retrying' do
         | 
| 57 | 
            +
                @errors.replace([IOError.new] * 2)
         | 
| 58 | 
            +
                errors = []
         | 
| 59 | 
            +
                call(RC::CLIENT => stub.error_callback{errors.method(:<<)}.object)
         | 
| 60 | 
            +
                @called.size.should.eq 3
         | 
| 61 | 
            +
                errors.size.should.eq 2
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
    
        data/test/test_timeout.rb
    CHANGED
    
    | @@ -11,13 +11,13 @@ describe RC::Timeout do | |
| 11 11 | 
             
              end
         | 
| 12 12 |  | 
| 13 13 | 
             
              would 'bypass timeout if timeout is 0' do
         | 
| 14 | 
            -
                mock(app). | 
| 14 | 
            +
                mock(app).process.times(0)
         | 
| 15 15 | 
             
                app.call({}){ |e| e.should.eq({}) }
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 | 
            -
              would 'run the  | 
| 18 | 
            +
              would 'run the process to setup timeout' do
         | 
| 19 19 | 
             
                env = {'timeout' => 2}
         | 
| 20 | 
            -
                mock(app). | 
| 20 | 
            +
                mock(app).process(env)
         | 
| 21 21 | 
             
                app.call(env){|e| e[RC::TIMER].should.kind_of?(RC::Timer)}
         | 
| 22 22 | 
             
              end
         | 
| 23 23 |  | 
| @@ -43,9 +43,9 @@ describe RC::Timeout do | |
| 43 43 | 
             
                  }
         | 
| 44 44 | 
             
                end
         | 
| 45 45 | 
             
                app.pool_size = 1
         | 
| 46 | 
            -
                app.new.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer | 
| 46 | 
            +
                app.new.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer,
         | 
| 47 | 
            +
                                RC::ASYNC => true).
         | 
| 47 48 | 
             
                  first.message.should.eq 'boom'
         | 
| 48 | 
            -
                Muack.verify
         | 
| 49 49 | 
             
              end
         | 
| 50 50 |  | 
| 51 51 | 
             
              would 'interrupt the task if timing out' do
         | 
| @@ -70,14 +70,10 @@ describe RC::Timeout do | |
| 70 70 | 
             
                  }
         | 
| 71 71 | 
             
                end
         | 
| 72 72 | 
             
                (-1..1).each do |size|
         | 
| 73 | 
            -
                  mock(any_instance_of(RC::Promise)).warn(is_a(String)) do |msg|
         | 
| 74 | 
            -
                    msg.should.include?('boom')
         | 
| 75 | 
            -
                  end
         | 
| 76 73 | 
             
                  app.pool_size = size
         | 
| 77 74 | 
             
                  app.new.request(RC::RESPONSE_KEY => RC::FAIL, RC::TIMER => timer,
         | 
| 78 | 
            -
                                  'pipe' => wr).
         | 
| 75 | 
            +
                                  RC::ASYNC => true, 'pipe' => wr).
         | 
| 79 76 | 
             
                    first.message.should.eq 'boom'
         | 
| 80 | 
            -
                  Muack.verify
         | 
| 81 77 | 
             
                end
         | 
| 82 78 | 
             
              end
         | 
| 83 79 | 
             
            end
         | 
    
        data/test/test_universal.rb
    CHANGED
    
    | @@ -2,6 +2,12 @@ | |
| 2 2 | 
             
            require 'rest-core/test'
         | 
| 3 3 |  | 
| 4 4 | 
             
            describe RC::Universal do
         | 
| 5 | 
            +
              url = 'http://localhost/'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              after do
         | 
| 8 | 
            +
                WebMock.reset!
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 5 11 | 
             
              would 'send Authorization header' do
         | 
| 6 12 | 
             
                u = RC::Universal.new(:log_method => false)
         | 
| 7 13 | 
             
                u.username = 'Aladdin'
         | 
| @@ -18,7 +24,6 @@ describe RC::Universal do | |
| 18 24 | 
             
              end
         | 
| 19 25 |  | 
| 20 26 | 
             
              would 'clash' do
         | 
| 21 | 
            -
                url = 'http://localhost/'
         | 
| 22 27 | 
             
                stub_request(:get, url).to_return(:body => '{"a":{"b":"c"}}')
         | 
| 23 28 | 
             
                res = RC::Universal.new(:json_response => true,
         | 
| 24 29 | 
             
                                        :clash_response => true,
         | 
| @@ -27,12 +32,25 @@ describe RC::Universal do | |
| 27 32 | 
             
              end
         | 
| 28 33 |  | 
| 29 34 | 
             
              would 'follow redirect regardless response body' do
         | 
| 30 | 
            -
                 | 
| 35 | 
            +
                called = []
         | 
| 31 36 | 
             
                stub_request(:get, url).to_return(:body => 'bad json!',
         | 
| 32 37 | 
             
                  :status => 302, :headers => {'Location' => "#{url}a"})
         | 
| 33 | 
            -
                stub_request(:get, "#{url}a").to_return | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 38 | 
            +
                stub_request(:get, "#{url}a").to_return do
         | 
| 39 | 
            +
                  Thread.pass
         | 
| 40 | 
            +
                  {:body => '{"good":"json!"}'}
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                RC::Universal.new(:json_response => true, :log_method => false).
         | 
| 43 | 
            +
                  get(url, &called.method(:<<)).wait
         | 
| 44 | 
            +
                called.should.eq([{'good' => 'json!'}])
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              would 'retry and call error_callback' do
         | 
| 48 | 
            +
                errors = []
         | 
| 49 | 
            +
                called = []
         | 
| 50 | 
            +
                RC::Universal.new(:error_callback => errors.method(:<<),
         | 
| 51 | 
            +
                                  :max_retries => 1, :log_method => false).
         | 
| 52 | 
            +
                  get(url, &called.method(:<<)).wait
         | 
| 53 | 
            +
                errors.map(&:class).should.eq [Errno::ECONNREFUSED]*2
         | 
| 54 | 
            +
                called.map(&:class).should.eq [Errno::ECONNREFUSED]
         | 
| 37 55 | 
             
              end
         | 
| 38 56 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rest-core
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3. | 
| 4 | 
            +
              version: 3.5.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Lin Jen-Shin (godfat)
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2014- | 
| 11 | 
            +
            date: 2014-12-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: httpclient
         | 
| @@ -114,6 +114,7 @@ files: | |
| 114 114 | 
             
            - lib/rest-core/middleware/oauth2_header.rb
         | 
| 115 115 | 
             
            - lib/rest-core/middleware/oauth2_query.rb
         | 
| 116 116 | 
             
            - lib/rest-core/middleware/query_response.rb
         | 
| 117 | 
            +
            - lib/rest-core/middleware/retry.rb
         | 
| 117 118 | 
             
            - lib/rest-core/middleware/smash_response.rb
         | 
| 118 119 | 
             
            - lib/rest-core/middleware/timeout.rb
         | 
| 119 120 | 
             
            - lib/rest-core/promise.rb
         | 
| @@ -160,6 +161,7 @@ files: | |
| 160 161 | 
             
            - test/test_payload.rb
         | 
| 161 162 | 
             
            - test/test_promise.rb
         | 
| 162 163 | 
             
            - test/test_query_response.rb
         | 
| 164 | 
            +
            - test/test_retry.rb
         | 
| 163 165 | 
             
            - test/test_simple.rb
         | 
| 164 166 | 
             
            - test/test_smash.rb
         | 
| 165 167 | 
             
            - test/test_smash_response.rb
         | 
| @@ -186,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 186 188 | 
             
                  version: '0'
         | 
| 187 189 | 
             
            requirements: []
         | 
| 188 190 | 
             
            rubyforge_project: 
         | 
| 189 | 
            -
            rubygems_version: 2.4. | 
| 191 | 
            +
            rubygems_version: 2.4.5
         | 
| 190 192 | 
             
            signing_key: 
         | 
| 191 193 | 
             
            specification_version: 4
         | 
| 192 194 | 
             
            summary: Modular Ruby clients interface for REST APIs.
         | 
| @@ -218,6 +220,7 @@ test_files: | |
| 218 220 | 
             
            - test/test_payload.rb
         | 
| 219 221 | 
             
            - test/test_promise.rb
         | 
| 220 222 | 
             
            - test/test_query_response.rb
         | 
| 223 | 
            +
            - test/test_retry.rb
         | 
| 221 224 | 
             
            - test/test_simple.rb
         | 
| 222 225 | 
             
            - test/test_smash.rb
         | 
| 223 226 | 
             
            - test/test_smash_response.rb
         |