Skip to content
This repository has been archived by the owner on Jun 23, 2020. It is now read-only.

Commit

Permalink
cleaned up and documented the request middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Stachl committed May 28, 2014
1 parent 8f70833 commit 1c52a9a
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 57 deletions.
28 changes: 21 additions & 7 deletions lib/desk_api/request/encode_json.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
module DeskApi::Request
class EncodeJson < Faraday::Middleware
dependency 'json'
module DeskApi
module Request
# {DeskApi::Request::EncodeJson} is the Faraday middleware
# that dumps a json string from whatever is specified in
# the request body. It also sets the "Content-Type" header.
#
# @author Thomas Stachl <tstachl@salesforce.com>
# @copyright Copyright (c) 2013-2014 Thomas Stachl
# @license MIT
class EncodeJson < Faraday::Middleware
dependency 'json'

def call(env)
env[:request_headers]['Content-Type'] = 'application/json'
env[:body] = ::JSON.dump(env[:body]) if env[:body] and not env[:body].to_s.empty?
@app.call env
# Changes the request before it gets sent
#
# @param env [Hash] the request hash
def call(env)
env[:request_headers]['Content-Type'] = 'application/json'
if env[:body] && !env[:body].to_s.empty?
env[:body] = ::JSON.dump(env[:body])
end
@app.call env
end
end
end
end
48 changes: 34 additions & 14 deletions lib/desk_api/request/oauth.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
module DeskApi::Request
class OAuth < Faraday::Middleware
dependency 'simple_oauth'
module DeskApi
module Request
# {DeskApi::Request::OAuth} is the Faraday middleware to
# sign requests with an OAuth header.
#
# @author Thomas Stachl <tstachl@salesforce.com>
# @copyright Copyright (c) 2013-2014 Thomas Stachl
# @license MIT
class OAuth < Faraday::Middleware
dependency 'simple_oauth'

def initialize(app, options)
super(app)
@options = options
end
# Initializies the middleware and sets options
#
# @param app [Hash] the faraday environment hash
# @param options [Hash] additional options
def initialize(app, options)
super(app)
@options = options
end

def call(env)
env[:request_headers]['Authorization'] = oauth(env).to_s
@app.call env
end
# Changes the request before it gets sent
#
# @param env [Hash] the request hash
def call(env)
env[:request_headers]['Authorization'] = oauth(env).to_s
@app.call env
end

private

private
def oauth(env)
SimpleOAuth::Header.new env[:method], env[:url].to_s, {}, @options
# Returns the OAuth header
#
# @param env [Hash] the request hash
# @return [String]
def oauth(env)
SimpleOAuth::Header.new env[:method], env[:url].to_s, {}, @options
end
end
end
end
113 changes: 79 additions & 34 deletions lib/desk_api/request/retry.rb
Original file line number Diff line number Diff line change
@@ -1,47 +1,92 @@
module DeskApi::Request
class Retry < Faraday::Middleware
def initialize(app, options = {})
@max = options[:max] || 3
@interval = options[:interval] || 10
super(app)
end
module DeskApi
module Request
# {DeskApi::Request::Retry} is a Faraday middleware that
# retries failed requests up to 3 times. It also includes
# desk.com's rate limiting which are retried only once.
#
# @author Thomas Stachl <tstachl@salesforce.com>
# @copyright Copyright (c) 2013-2014 Thomas Stachl
# @license MIT
class Retry < Faraday::Middleware
class << self
# Returns an array of errors that should be retried.
#
# @return [Array]
def errors
@exceptions ||= [
Errno::ETIMEDOUT,
'Timeout::Error',
Faraday::Error::TimeoutError,
DeskApi::Error::TooManyRequests
]
end
end

def call(env)
retries = @max
request_body = env[:body]
begin
env[:body] = request_body
@app.call(env)
rescue DeskApi::Error::TooManyRequests => e
if retries > 0
retries = 0
sleep e.rate_limit.reset_in
# Initializies the middleware and sets options
#
# @param app [Hash] the faraday environment hash
# @param options [Hash] additional options
def initialize(app, options = {})
@max = options[:max] || 3
@interval = options[:interval] || 10
super(app)
end

# Rescues exceptions and retries the request
#
# @param env [Hash] the request hash
def call(env)
retries = @max
request_body = env[:body]
begin
env[:body] = request_body
@app.call(env)
rescue exception_matcher => err
raise unless calc(err, retries) { |x| retries = x } > 0
sleep interval(err)
retry
end
raise
rescue exception_matcher
if retries > 0
retries -= 1
sleep @interval
retry
end

# Calculates the retries based on the error
#
# @param err [StandardError] the error that has been thrown
# @param retries [Integer] current retry count
# @return [Integer]
def calc(err, retries, &block)
if err.kind_of?(DeskApi::Error::TooManyRequests)
block.call(0)
else
block.call(retries - 1)
end
end

# Returns the interval for the specific error
#
# @param err [StandardError] the error that has been thrown
# @return [Integer]
def interval(err)
if err.kind_of?(DeskApi::Error::TooManyRequests)
err.rate_limit.reset_in
else
@interval
end
raise
end
end

def exception_matcher
exceptions = [Errno::ETIMEDOUT, 'Timeout::Error', Faraday::Error::TimeoutError]
matcher = Module.new
(class << matcher; self; end).class_eval do
define_method(:===) do |error|
exceptions.any? do |ex|
if ex.is_a? Module then error.is_a? ex
else error.class.to_s == ex.to_s
# Returns an exception matcher
#
# @return [Module]
def exception_matcher
matcher = Module.new
(class << matcher; self; end).class_eval do
define_method(:===) do |error|
Retry.errors.any? do |ex|
ex.is_a?(Module) ? error.is_a?(ex) : error.class.to_s == ex.to_s
end
end
end
matcher
end
matcher
end
end
end
4 changes: 2 additions & 2 deletions spec/desk_api/request/retry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
end

@conn.post('http://localhost/echo') rescue nil
expect(times_called).to eq(4)
expect(times_called).to eq(3)
end

it 'retries once if we have too many requests' do
Expand All @@ -47,6 +47,6 @@
end

@conn.post('http://localhost/echo') rescue nil
expect(times_called).to eq(2)
expect(times_called).to eq(1)
end
end

0 comments on commit 1c52a9a

Please sign in to comment.