mirror of
https://github.com/lostisland/faraday.git
synced 2025-08-15 00:04:38 -04:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
47b3979850 | ||
|
ef0040fe4c | ||
|
0ee48490b0 | ||
|
3f127bb10f | ||
|
554930c6c5 | ||
|
c65d3be5fb | ||
|
aa17e5b3df | ||
|
537f5d7f2d | ||
|
4a8a504fe2 | ||
|
1d7c8ad2fc | ||
|
80407137f1 | ||
|
3f0acb63b1 | ||
|
d0efa09282 | ||
|
8cd1bf1190 | ||
|
2a5702a616 | ||
|
d881f163c0 | ||
|
18ba248d14 | ||
|
a84e6b4dba | ||
|
8f32d7d59d |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -2,9 +2,8 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches: [main, 0.1x]
|
branches: [main, 0.1x, 1.x]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GIT_COMMIT_SHA: ${{ github.sha }}
|
GIT_COMMIT_SHA: ${{ github.sha }}
|
||||||
@ -43,7 +42,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ruby: ['2.4', '2.5', '2.6', '2.7', '3.0']
|
ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '3.1']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
@ -53,6 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
sudo apt-get install libcurl4-openssl-dev
|
sudo apt-get install libcurl4-openssl-dev
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
|
@ -14,7 +14,7 @@ Metrics/AbcSize:
|
|||||||
# Offense count: 5
|
# Offense count: 5
|
||||||
# Configuration parameters: CountComments, CountAsOne.
|
# Configuration parameters: CountComments, CountAsOne.
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 256
|
Max: 260
|
||||||
|
|
||||||
# Offense count: 15
|
# Offense count: 15
|
||||||
# Configuration parameters: IgnoredMethods.
|
# Configuration parameters: IgnoredMethods.
|
||||||
|
1
Gemfile
1
Gemfile
@ -7,6 +7,7 @@ ruby RUBY_VERSION
|
|||||||
gem 'jruby-openssl', '~> 0.10.7', platforms: :jruby
|
gem 'jruby-openssl', '~> 0.10.7', platforms: :jruby
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
|
gem 'irb'
|
||||||
gem 'pry'
|
gem 'pry'
|
||||||
gem 'rake'
|
gem 'rake'
|
||||||
end
|
end
|
||||||
|
@ -23,6 +23,16 @@ Faraday.new(...) do |conn|
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### With a proc
|
||||||
|
|
||||||
|
You can also provide a proc, which will be evaluated on each request:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Faraday.new(...) do |conn|
|
||||||
|
conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token }
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
### Basic Authentication
|
### Basic Authentication
|
||||||
|
|
||||||
`BasicAuthentication` adds a 'Basic' type Authorization header to a Faraday request.
|
`BasicAuthentication` adds a 'Basic' type Authorization header to a Faraday request.
|
||||||
|
@ -81,4 +81,17 @@ RSpec.describe Client do
|
|||||||
stubs.verify_stubbed_calls
|
stubs.verify_stubbed_calls
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'When the Faraday connection is configured with FlatParamsEncoder' do
|
||||||
|
let(:conn) { Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) { |b| b.adapter(:test, stubs) } }
|
||||||
|
|
||||||
|
it 'handles the same multiple URL parameters' do
|
||||||
|
stubs.get('/ebi?a=x&a=y&a=z') { [200, { 'Content-Type' => 'application/json' }, '{"name": "shrimp"}'] }
|
||||||
|
|
||||||
|
# uncomment to raise Stubs::NotFound
|
||||||
|
# expect(client.sushi('ebi', params: { a: %w[x y] })).to eq('shrimp')
|
||||||
|
expect(client.sushi('ebi', params: { a: %w[x y z] })).to eq('shrimp')
|
||||||
|
stubs.verify_stubbed_calls
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -13,8 +13,8 @@ class Client
|
|||||||
@conn = conn
|
@conn = conn
|
||||||
end
|
end
|
||||||
|
|
||||||
def sushi(jname)
|
def sushi(jname, params: {})
|
||||||
res = @conn.get("/#{jname}")
|
res = @conn.get("/#{jname}", params)
|
||||||
data = JSON.parse(res.body)
|
data = JSON.parse(res.body)
|
||||||
data['name']
|
data['name']
|
||||||
end
|
end
|
||||||
@ -70,6 +70,45 @@ class ClientTest < Test::Unit::TestCase
|
|||||||
stubs.verify_stubbed_calls
|
stubs.verify_stubbed_calls
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strict_mode
|
||||||
|
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
|
||||||
|
stubs.get('/ebi?abc=123') do
|
||||||
|
[
|
||||||
|
200,
|
||||||
|
{ 'Content-Type': 'application/javascript' },
|
||||||
|
'{"name": "shrimp"}'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
cli = client(stubs)
|
||||||
|
assert_equal 'shrimp', cli.sushi('ebi', params: { abc: 123 })
|
||||||
|
|
||||||
|
# uncomment to raise Stubs::NotFound
|
||||||
|
# assert_equal 'shrimp', cli.sushi('ebi', params: { abc: 123, foo: 'Kappa' })
|
||||||
|
stubs.verify_stubbed_calls
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_non_default_params_encoder
|
||||||
|
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
|
||||||
|
stubs.get('/ebi?a=x&a=y&a=z') do
|
||||||
|
[
|
||||||
|
200,
|
||||||
|
{ 'Content-Type': 'application/javascript' },
|
||||||
|
'{"name": "shrimp"}'
|
||||||
|
]
|
||||||
|
end
|
||||||
|
conn = Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
|
||||||
|
builder.adapter :test, stubs
|
||||||
|
end
|
||||||
|
|
||||||
|
cli = Client.new(conn)
|
||||||
|
assert_equal 'shrimp', cli.sushi('ebi', params: { a: %w[x y z] })
|
||||||
|
|
||||||
|
# uncomment to raise Stubs::NotFound
|
||||||
|
# assert_equal 'shrimp', cli.sushi('ebi', params: { a: %w[x y] })
|
||||||
|
stubs.verify_stubbed_calls
|
||||||
|
end
|
||||||
|
|
||||||
def client(stubs)
|
def client(stubs)
|
||||||
conn = Faraday.new do |builder|
|
conn = Faraday.new do |builder|
|
||||||
builder.adapter :test, stubs
|
builder.adapter :test, stubs
|
||||||
|
@ -18,12 +18,13 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
|
|||||||
spec.add_dependency 'faraday-em_http', '~> 1.0'
|
spec.add_dependency 'faraday-em_http', '~> 1.0'
|
||||||
spec.add_dependency 'faraday-em_synchrony', '~> 1.0'
|
spec.add_dependency 'faraday-em_synchrony', '~> 1.0'
|
||||||
spec.add_dependency 'faraday-excon', '~> 1.1'
|
spec.add_dependency 'faraday-excon', '~> 1.1'
|
||||||
spec.add_dependency 'faraday-httpclient', '~> 1.0.1'
|
spec.add_dependency 'faraday-httpclient', '~> 1.0'
|
||||||
|
spec.add_dependency 'faraday-multipart', '~> 1.0'
|
||||||
spec.add_dependency 'faraday-net_http', '~> 1.0'
|
spec.add_dependency 'faraday-net_http', '~> 1.0'
|
||||||
spec.add_dependency 'faraday-net_http_persistent', '~> 1.1'
|
spec.add_dependency 'faraday-net_http_persistent', '~> 1.0'
|
||||||
spec.add_dependency 'faraday-patron', '~> 1.0'
|
spec.add_dependency 'faraday-patron', '~> 1.0'
|
||||||
spec.add_dependency 'faraday-rack', '~> 1.0'
|
spec.add_dependency 'faraday-rack', '~> 1.0'
|
||||||
spec.add_dependency 'multipart-post', '>= 1.2', '< 3'
|
spec.add_dependency 'faraday-retry', '~> 1.0'
|
||||||
spec.add_dependency 'ruby2_keywords', '>= 0.0.4'
|
spec.add_dependency 'ruby2_keywords', '>= 0.0.4'
|
||||||
|
|
||||||
# Includes `examples` and `spec` to allow external adapter gems to run Faraday unit and integration tests
|
# Includes `examples` and `spec` to allow external adapter gems to run Faraday unit and integration tests
|
||||||
|
@ -24,9 +24,15 @@ require 'faraday/adapter'
|
|||||||
require 'faraday/request'
|
require 'faraday/request'
|
||||||
require 'faraday/response'
|
require 'faraday/response'
|
||||||
require 'faraday/error'
|
require 'faraday/error'
|
||||||
require 'faraday/file_part'
|
require 'faraday/request/url_encoded' # needed by multipart
|
||||||
require 'faraday/param_part'
|
|
||||||
|
|
||||||
|
# External Middleware gems and their aliases
|
||||||
|
require 'faraday/multipart'
|
||||||
|
require 'faraday/retry'
|
||||||
|
Faraday::Request::Multipart = Faraday::Multipart::Middleware
|
||||||
|
Faraday::Request::Retry = Faraday::Retry::Middleware
|
||||||
|
|
||||||
|
# External Adapters gems
|
||||||
unless defined?(JRUBY_VERSION)
|
unless defined?(JRUBY_VERSION)
|
||||||
require 'faraday/em_http'
|
require 'faraday/em_http'
|
||||||
require 'faraday/em_synchrony'
|
require 'faraday/em_synchrony'
|
||||||
|
@ -62,18 +62,20 @@ module Faraday
|
|||||||
@stack.empty?
|
@stack.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def match(request_method, host, path, headers, body)
|
# @param env [Faraday::Env]
|
||||||
|
def match(env)
|
||||||
|
request_method = env[:method]
|
||||||
return false unless @stack.key?(request_method)
|
return false unless @stack.key?(request_method)
|
||||||
|
|
||||||
stack = @stack[request_method]
|
stack = @stack[request_method]
|
||||||
consumed = (@consumed[request_method] ||= [])
|
consumed = (@consumed[request_method] ||= [])
|
||||||
|
|
||||||
stub, meta = matches?(stack, host, path, headers, body)
|
stub, meta = matches?(stack, env)
|
||||||
if stub
|
if stub
|
||||||
consumed << stack.delete(stub)
|
consumed << stack.delete(stub)
|
||||||
return stub, meta
|
return stub, meta
|
||||||
end
|
end
|
||||||
matches?(consumed, host, path, headers, body)
|
matches?(consumed, env)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(path, headers = {}, &block)
|
def get(path, headers = {}, &block)
|
||||||
@ -142,15 +144,18 @@ module Faraday
|
|||||||
Faraday::Utils.URI(path).host
|
Faraday::Utils.URI(path).host
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
|
||||||
headers = Utils::Headers.new(headers)
|
headers = Utils::Headers.new(headers)
|
||||||
stub = Stub.new(host, normalized_path, headers, body, @strict_mode, block)
|
|
||||||
|
stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
|
||||||
(@stack[request_method] ||= []) << stub
|
(@stack[request_method] ||= []) << stub
|
||||||
end
|
end
|
||||||
|
|
||||||
def matches?(stack, host, path, headers, body)
|
# @param stack [Hash]
|
||||||
|
# @param env [Faraday::Env]
|
||||||
|
def matches?(stack, env)
|
||||||
stack.each do |stub|
|
stack.each do |stub|
|
||||||
match_result, meta = stub.matches?(host, path, headers, body)
|
match_result, meta = stub.matches?(env)
|
||||||
return stub, meta if match_result
|
return stub, meta if match_result
|
||||||
end
|
end
|
||||||
nil
|
nil
|
||||||
@ -158,35 +163,20 @@ module Faraday
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Stub request
|
# Stub request
|
||||||
# rubocop:disable Style/StructInheritance
|
class Stub < Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) # rubocop:disable Style/StructInheritance
|
||||||
class Stub < Struct.new(:host, :path, :params, :headers, :body, :strict_mode, :block)
|
# @param env [Faraday::Env]
|
||||||
# rubocop:enable Style/StructInheritance
|
def matches?(env)
|
||||||
def initialize(host, full, headers, body, strict_mode, block) # rubocop:disable Metrics/ParameterLists
|
request_host = env[:url].host
|
||||||
path, query = full.respond_to?(:split) ? full.split('?') : full
|
request_path = Faraday::Utils.normalize_path(env[:url].path)
|
||||||
params =
|
request_headers = env.request_headers
|
||||||
if query
|
request_body = env[:body]
|
||||||
Faraday::Utils.parse_nested_query(query)
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
super(host, path, params, headers, body, strict_mode, block)
|
|
||||||
end
|
|
||||||
|
|
||||||
def matches?(request_host, request_uri, request_headers, request_body)
|
|
||||||
request_path, request_query = request_uri.split('?')
|
|
||||||
request_params =
|
|
||||||
if request_query
|
|
||||||
Faraday::Utils.parse_nested_query(request_query)
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
# meta is a hash used as carrier
|
# meta is a hash used as carrier
|
||||||
# that will be yielded to consumer block
|
# that will be yielded to consumer block
|
||||||
meta = {}
|
meta = {}
|
||||||
[(host.nil? || host == request_host) &&
|
[(host.nil? || host == request_host) &&
|
||||||
path_match?(request_path, meta) &&
|
path_match?(request_path, meta) &&
|
||||||
params_match?(request_params) &&
|
params_match?(env) &&
|
||||||
(body.to_s.size.zero? || request_body == body) &&
|
(body.to_s.size.zero? || request_body == body) &&
|
||||||
headers_match?(request_headers), meta]
|
headers_match?(request_headers), meta]
|
||||||
end
|
end
|
||||||
@ -199,7 +189,11 @@ module Faraday
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def params_match?(request_params)
|
# @param env [Faraday::Env]
|
||||||
|
def params_match?(env)
|
||||||
|
request_params = env[:params]
|
||||||
|
params = env.params_encoder.decode(query) || {}
|
||||||
|
|
||||||
if strict_mode
|
if strict_mode
|
||||||
return Set.new(params) == Set.new(request_params)
|
return Set.new(params) == Set.new(request_params)
|
||||||
end
|
end
|
||||||
@ -239,26 +233,19 @@ module Faraday
|
|||||||
yield(stubs)
|
yield(stubs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param env [Faraday::Env]
|
||||||
def call(env)
|
def call(env)
|
||||||
super
|
super
|
||||||
host = env[:url].host
|
|
||||||
normalized_path = Faraday::Utils.normalize_path(env[:url])
|
|
||||||
params_encoder = env.request.params_encoder ||
|
|
||||||
Faraday::Utils.default_params_encoder
|
|
||||||
|
|
||||||
stub, meta = stubs.match(env[:method], host, normalized_path,
|
env.request.params_encoder ||= Faraday::Utils.default_params_encoder
|
||||||
env.request_headers, env[:body])
|
env[:params] = env.params_encoder.decode(env[:url].query) || {}
|
||||||
|
stub, meta = stubs.match(env)
|
||||||
|
|
||||||
unless stub
|
unless stub
|
||||||
raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
|
raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
|
||||||
"#{normalized_path} #{env[:body]}"
|
"#{env[:url]} #{env[:body]}"
|
||||||
end
|
end
|
||||||
|
|
||||||
env[:params] = if (query = env[:url].query)
|
|
||||||
params_encoder.decode(query)
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
block_arity = stub.block.arity
|
block_arity = stub.block.arity
|
||||||
status, headers, body =
|
status, headers, body =
|
||||||
if block_arity >= 0
|
if block_arity >= 0
|
||||||
|
@ -433,13 +433,18 @@ module Faraday
|
|||||||
uri.query = nil
|
uri.query = nil
|
||||||
|
|
||||||
with_uri_credentials(uri) do |user, password|
|
with_uri_credentials(uri) do |user, password|
|
||||||
basic_auth user, password
|
set_basic_auth(user, password)
|
||||||
uri.user = uri.password = nil
|
uri.user = uri.password = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@proxy = proxy_from_env(url) unless @manual_proxy
|
@proxy = proxy_from_env(url) unless @manual_proxy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_basic_auth(user, password)
|
||||||
|
header = Faraday::Request::BasicAuthentication.header(user, password)
|
||||||
|
headers[Faraday::Request::Authorization::KEY] = header
|
||||||
|
end
|
||||||
|
|
||||||
# Sets the path prefix and ensures that it always has a leading
|
# Sets the path prefix and ensures that it always has a leading
|
||||||
# slash.
|
# slash.
|
||||||
#
|
#
|
||||||
|
@ -143,10 +143,4 @@ module Faraday
|
|||||||
# Raised by FaradayMiddleware::ResponseMiddleware
|
# Raised by FaradayMiddleware::ResponseMiddleware
|
||||||
class ParsingError < Error
|
class ParsingError < Error
|
||||||
end
|
end
|
||||||
|
|
||||||
# Exception used to control the Retry middleware.
|
|
||||||
#
|
|
||||||
# @see Faraday::Request::Retry
|
|
||||||
class RetriableResponse < Error
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,128 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'stringio'
|
|
||||||
|
|
||||||
# multipart-post gem
|
|
||||||
require 'composite_io'
|
|
||||||
require 'parts'
|
|
||||||
|
|
||||||
module Faraday
|
|
||||||
# Multipart value used to POST a binary data from a file or
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") }
|
|
||||||
# http.post("/upload", payload)
|
|
||||||
#
|
|
||||||
|
|
||||||
# @!method initialize(filename_or_io, content_type, filename = nil, opts = {})
|
|
||||||
#
|
|
||||||
# @param filename_or_io [String, IO] Either a String filename to a local
|
|
||||||
# file or an open IO object.
|
|
||||||
# @param content_type [String] String content type of the file data.
|
|
||||||
# @param filename [String] Optional String filename, usually to add context
|
|
||||||
# to a given IO object.
|
|
||||||
# @param opts [Hash] Optional Hash of String key/value pairs to describethis
|
|
||||||
# this uploaded file. Expected Header keys include:
|
|
||||||
# * Content-Transfer-Encoding - Defaults to "binary"
|
|
||||||
# * Content-Disposition - Defaults to "form-data"
|
|
||||||
# * Content-Type - Defaults to the content_type argument.
|
|
||||||
# * Content-ID - Optional.
|
|
||||||
#
|
|
||||||
# @return [Faraday::FilePart]
|
|
||||||
#
|
|
||||||
# @!attribute [r] content_type
|
|
||||||
# The uploaded binary data's content type.
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
#
|
|
||||||
# @!attribute [r] original_filename
|
|
||||||
# The base filename, taken either from the filename_or_io or filename
|
|
||||||
# arguments in #initialize.
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
#
|
|
||||||
# @!attribute [r] opts
|
|
||||||
# Extra String key/value pairs to make up the header for this uploaded file.
|
|
||||||
#
|
|
||||||
# @return [Hash]
|
|
||||||
#
|
|
||||||
# @!attribute [r] io
|
|
||||||
# The open IO object for the uploaded file.
|
|
||||||
#
|
|
||||||
# @return [IO]
|
|
||||||
FilePart = ::UploadIO
|
|
||||||
|
|
||||||
# Multipart value used to POST a file.
|
|
||||||
#
|
|
||||||
# @deprecated Use FilePart instead of this class. It behaves identically, with
|
|
||||||
# a matching name to ParamPart.
|
|
||||||
UploadIO = ::UploadIO
|
|
||||||
|
|
||||||
Parts = ::Parts
|
|
||||||
|
|
||||||
# Similar to, but not compatible with CompositeReadIO provided by the
|
|
||||||
# multipart-post gem.
|
|
||||||
# https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
|
|
||||||
class CompositeReadIO
|
|
||||||
def initialize(*parts)
|
|
||||||
@parts = parts.flatten
|
|
||||||
@ios = @parts.map(&:to_io)
|
|
||||||
@index = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Integer] sum of the lengths of all the parts
|
|
||||||
def length
|
|
||||||
@parts.inject(0) { |sum, part| sum + part.length }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Rewind each of the IOs and reset the index to 0.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
def rewind
|
|
||||||
@ios.each(&:rewind)
|
|
||||||
@index = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# Read from IOs in order until `length` bytes have been received.
|
|
||||||
#
|
|
||||||
# @param length [Integer, nil]
|
|
||||||
# @param outbuf [String, nil]
|
|
||||||
def read(length = nil, outbuf = nil)
|
|
||||||
got_result = false
|
|
||||||
outbuf = outbuf ? (+outbuf).replace('') : +''
|
|
||||||
|
|
||||||
while (io = current_io)
|
|
||||||
if (result = io.read(length))
|
|
||||||
got_result ||= !result.nil?
|
|
||||||
result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
|
|
||||||
outbuf << result
|
|
||||||
length -= result.length if length
|
|
||||||
break if length&.zero?
|
|
||||||
end
|
|
||||||
advance_io
|
|
||||||
end
|
|
||||||
!got_result && length ? nil : outbuf
|
|
||||||
end
|
|
||||||
|
|
||||||
# Close each of the IOs.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
def close
|
|
||||||
@ios.each(&:close)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_open_and_readable
|
|
||||||
# Rubinius compatibility
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def current_io
|
|
||||||
@ios[@index]
|
|
||||||
end
|
|
||||||
|
|
||||||
def advance_io
|
|
||||||
@index += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,53 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Faraday
|
|
||||||
# Multipart value used to POST data with a content type.
|
|
||||||
class ParamPart
|
|
||||||
# @param value [String] Uploaded content as a String.
|
|
||||||
# @param content_type [String] String content type of the value.
|
|
||||||
# @param content_id [String] Optional String of this value's Content-ID.
|
|
||||||
#
|
|
||||||
# @return [Faraday::ParamPart]
|
|
||||||
def initialize(value, content_type, content_id = nil)
|
|
||||||
@value = value
|
|
||||||
@content_type = content_type
|
|
||||||
@content_id = content_id
|
|
||||||
end
|
|
||||||
|
|
||||||
# Converts this value to a form part.
|
|
||||||
#
|
|
||||||
# @param boundary [String] String multipart boundary that must not exist in
|
|
||||||
# the content exactly.
|
|
||||||
# @param key [String] String key name for this value.
|
|
||||||
#
|
|
||||||
# @return [Faraday::Parts::Part]
|
|
||||||
def to_part(boundary, key)
|
|
||||||
Faraday::Parts::Part.new(boundary, key, value, headers)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a Hash of String key/value pairs.
|
|
||||||
#
|
|
||||||
# @return [Hash]
|
|
||||||
def headers
|
|
||||||
{
|
|
||||||
'Content-Type' => content_type,
|
|
||||||
'Content-ID' => content_id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# The content to upload.
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
attr_reader :value
|
|
||||||
|
|
||||||
# The value's content type.
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
attr_reader :content_type
|
|
||||||
|
|
||||||
# The value's content ID, if given.
|
|
||||||
#
|
|
||||||
# @return [String, nil]
|
|
||||||
attr_reader :content_id
|
|
||||||
end
|
|
||||||
end
|
|
@ -35,8 +35,6 @@ module Faraday
|
|||||||
|
|
||||||
register_middleware File.expand_path('request', __dir__),
|
register_middleware File.expand_path('request', __dir__),
|
||||||
url_encoded: [:UrlEncoded, 'url_encoded'],
|
url_encoded: [:UrlEncoded, 'url_encoded'],
|
||||||
multipart: [:Multipart, 'multipart'],
|
|
||||||
retry: [:Retry, 'retry'],
|
|
||||||
authorization: [:Authorization, 'authorization'],
|
authorization: [:Authorization, 'authorization'],
|
||||||
basic_auth: [
|
basic_auth: [
|
||||||
:BasicAuthentication,
|
:BasicAuthentication,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'base64'
|
||||||
|
|
||||||
module Faraday
|
module Faraday
|
||||||
class Request
|
class Request
|
||||||
# Request middleware for the Authorization HTTP header
|
# Request middleware for the Authorization HTTP header
|
||||||
@ -13,7 +15,8 @@ module Faraday
|
|||||||
# @return [String] a header value
|
# @return [String] a header value
|
||||||
def self.header(type, token)
|
def self.header(type, token)
|
||||||
case token
|
case token
|
||||||
when String, Symbol
|
when String, Symbol, Proc
|
||||||
|
token = token.call if token.is_a?(Proc)
|
||||||
"#{type} #{token}"
|
"#{type} #{token}"
|
||||||
when Hash
|
when Hash
|
||||||
build_hash(type.to_s, token)
|
build_hash(type.to_s, token)
|
||||||
@ -32,6 +35,7 @@ module Faraday
|
|||||||
comma = ', '
|
comma = ', '
|
||||||
values = []
|
values = []
|
||||||
hash.each do |key, value|
|
hash.each do |key, value|
|
||||||
|
value = value.call if value.is_a?(Proc)
|
||||||
values << "#{key}=#{value.to_s.inspect}"
|
values << "#{key}=#{value.to_s.inspect}"
|
||||||
end
|
end
|
||||||
"#{type} #{values * comma}"
|
"#{type} #{values * comma}"
|
||||||
@ -39,16 +43,19 @@ module Faraday
|
|||||||
|
|
||||||
# @param app [#call]
|
# @param app [#call]
|
||||||
# @param type [String, Symbol] Type of Authorization
|
# @param type [String, Symbol] Type of Authorization
|
||||||
# @param token [String, Symbol, Hash] Token value for the Authorization
|
# @param param [String, Symbol, Hash, Proc] parameter to build the Authorization header.
|
||||||
def initialize(app, type, token)
|
# This value can be a proc, in which case it will be invoked on each request.
|
||||||
@header_value = self.class.header(type, token)
|
def initialize(app, type, param)
|
||||||
|
@type = type
|
||||||
|
@param = param
|
||||||
super(app)
|
super(app)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param env [Faraday::Env]
|
# @param env [Faraday::Env]
|
||||||
def call(env)
|
def on_request(env)
|
||||||
env.request_headers[KEY] = @header_value unless env.request_headers[KEY]
|
return if env.request_headers[KEY]
|
||||||
@app.call(env)
|
|
||||||
|
env.request_headers[KEY] = self.class.header(@type, @param)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require File.expand_path('url_encoded', __dir__)
|
|
||||||
require 'securerandom'
|
|
||||||
|
|
||||||
module Faraday
|
|
||||||
class Request
|
|
||||||
# Middleware for supporting multi-part requests.
|
|
||||||
class Multipart < UrlEncoded
|
|
||||||
self.mime_type = 'multipart/form-data'
|
|
||||||
unless defined?(::Faraday::Request::Multipart::DEFAULT_BOUNDARY_PREFIX)
|
|
||||||
DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(app = nil, options = {})
|
|
||||||
super(app)
|
|
||||||
@options = options
|
|
||||||
end
|
|
||||||
|
|
||||||
# Checks for files in the payload, otherwise leaves everything untouched.
|
|
||||||
#
|
|
||||||
# @param env [Faraday::Env]
|
|
||||||
def call(env)
|
|
||||||
match_content_type(env) do |params|
|
|
||||||
env.request.boundary ||= unique_boundary
|
|
||||||
env.request_headers[CONTENT_TYPE] +=
|
|
||||||
"; boundary=#{env.request.boundary}"
|
|
||||||
env.body = create_multipart(env, params)
|
|
||||||
end
|
|
||||||
@app.call env
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param env [Faraday::Env]
|
|
||||||
def process_request?(env)
|
|
||||||
type = request_type(env)
|
|
||||||
env.body.respond_to?(:each_key) && !env.body.empty? && (
|
|
||||||
(type.empty? && has_multipart?(env.body)) ||
|
|
||||||
(type == self.class.mime_type)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if obj is an enumerable with values that are multipart.
|
|
||||||
#
|
|
||||||
# @param obj [Object]
|
|
||||||
# @return [Boolean]
|
|
||||||
def has_multipart?(obj) # rubocop:disable Naming/PredicateName
|
|
||||||
if obj.respond_to?(:each)
|
|
||||||
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
|
|
||||||
return true if val.respond_to?(:content_type) || has_multipart?(val)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param env [Faraday::Env]
|
|
||||||
# @param params [Hash]
|
|
||||||
def create_multipart(env, params)
|
|
||||||
boundary = env.request.boundary
|
|
||||||
parts = process_params(params) do |key, value|
|
|
||||||
part(boundary, key, value)
|
|
||||||
end
|
|
||||||
parts << Faraday::Parts::EpiloguePart.new(boundary)
|
|
||||||
|
|
||||||
body = Faraday::CompositeReadIO.new(parts)
|
|
||||||
env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
|
|
||||||
body
|
|
||||||
end
|
|
||||||
|
|
||||||
def part(boundary, key, value)
|
|
||||||
if value.respond_to?(:to_part)
|
|
||||||
value.to_part(boundary, key)
|
|
||||||
else
|
|
||||||
Faraday::Parts::Part.new(boundary, key, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String]
|
|
||||||
def unique_boundary
|
|
||||||
"#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param params [Hash]
|
|
||||||
# @param prefix [String]
|
|
||||||
# @param pieces [Array]
|
|
||||||
def process_params(params, prefix = nil, pieces = nil, &block)
|
|
||||||
params.inject(pieces || []) do |all, (key, value)|
|
|
||||||
if prefix
|
|
||||||
key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]"
|
|
||||||
end
|
|
||||||
|
|
||||||
case value
|
|
||||||
when Array
|
|
||||||
values = value.inject([]) { |a, v| a << [nil, v] }
|
|
||||||
process_params(values, key, all, &block)
|
|
||||||
when Hash
|
|
||||||
process_params(value, key, all, &block)
|
|
||||||
else
|
|
||||||
# rubocop:disable Performance/RedundantBlockCall
|
|
||||||
all << block.call(key, value)
|
|
||||||
# rubocop:enable Performance/RedundantBlockCall
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,239 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Faraday
|
|
||||||
class Request
|
|
||||||
# Catches exceptions and retries each request a limited number of times.
|
|
||||||
#
|
|
||||||
# By default, it retries 2 times and handles only timeout exceptions. It can
|
|
||||||
# be configured with an arbitrary number of retries, a list of exceptions to
|
|
||||||
# handle, a retry interval, a percentage of randomness to add to the retry
|
|
||||||
# interval, and a backoff factor.
|
|
||||||
#
|
|
||||||
# @example Configure Retry middleware using intervals
|
|
||||||
# Faraday.new do |conn|
|
|
||||||
# conn.request(:retry, max: 2,
|
|
||||||
# interval: 0.05,
|
|
||||||
# interval_randomness: 0.5,
|
|
||||||
# backoff_factor: 2,
|
|
||||||
# exceptions: [CustomException, 'Timeout::Error'])
|
|
||||||
#
|
|
||||||
# conn.adapter(:net_http) # NB: Last middleware must be the adapter
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# This example will result in a first interval that is random between 0.05
|
|
||||||
# and 0.075 and a second interval that is random between 0.1 and 0.125.
|
|
||||||
class Retry < Faraday::Middleware
|
|
||||||
DEFAULT_EXCEPTIONS = [
|
|
||||||
Errno::ETIMEDOUT, 'Timeout::Error',
|
|
||||||
Faraday::TimeoutError, Faraday::RetriableResponse
|
|
||||||
].freeze
|
|
||||||
IDEMPOTENT_METHODS = %i[delete get head options put].freeze
|
|
||||||
|
|
||||||
# Options contains the configurable parameters for the Retry middleware.
|
|
||||||
class Options < Faraday::Options.new(:max, :interval, :max_interval,
|
|
||||||
:interval_randomness,
|
|
||||||
:backoff_factor, :exceptions,
|
|
||||||
:methods, :retry_if, :retry_block,
|
|
||||||
:retry_statuses)
|
|
||||||
|
|
||||||
DEFAULT_CHECK = ->(_env, _exception) { false }
|
|
||||||
|
|
||||||
def self.from(value)
|
|
||||||
if value.is_a?(Integer)
|
|
||||||
new(value)
|
|
||||||
else
|
|
||||||
super(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def max
|
|
||||||
(self[:max] ||= 2).to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def interval
|
|
||||||
(self[:interval] ||= 0).to_f
|
|
||||||
end
|
|
||||||
|
|
||||||
def max_interval
|
|
||||||
(self[:max_interval] ||= Float::MAX).to_f
|
|
||||||
end
|
|
||||||
|
|
||||||
def interval_randomness
|
|
||||||
(self[:interval_randomness] ||= 0).to_f
|
|
||||||
end
|
|
||||||
|
|
||||||
def backoff_factor
|
|
||||||
(self[:backoff_factor] ||= 1).to_f
|
|
||||||
end
|
|
||||||
|
|
||||||
def exceptions
|
|
||||||
Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
|
|
||||||
end
|
|
||||||
|
|
||||||
def methods
|
|
||||||
Array(self[:methods] ||= IDEMPOTENT_METHODS)
|
|
||||||
end
|
|
||||||
|
|
||||||
def retry_if
|
|
||||||
self[:retry_if] ||= DEFAULT_CHECK
|
|
||||||
end
|
|
||||||
|
|
||||||
def retry_block
|
|
||||||
self[:retry_block] ||= proc {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def retry_statuses
|
|
||||||
Array(self[:retry_statuses] ||= [])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param app [#call]
|
|
||||||
# @param options [Hash]
|
|
||||||
# @option options [Integer] :max (2) Maximum number of retries
|
|
||||||
# @option options [Integer] :interval (0) Pause in seconds between retries
|
|
||||||
# @option options [Integer] :interval_randomness (0) The maximum random
|
|
||||||
# interval amount expressed as a float between
|
|
||||||
# 0 and 1 to use in addition to the interval.
|
|
||||||
# @option options [Integer] :max_interval (Float::MAX) An upper limit
|
|
||||||
# for the interval
|
|
||||||
# @option options [Integer] :backoff_factor (1) The amount to multiply
|
|
||||||
# each successive retry's interval amount by in order to provide backoff
|
|
||||||
# @option options [Array] :exceptions ([ Errno::ETIMEDOUT,
|
|
||||||
# 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
|
|
||||||
# The list of exceptions to handle. Exceptions can be given as
|
|
||||||
# Class, Module, or String.
|
|
||||||
# @option options [Array] :methods (the idempotent HTTP methods
|
|
||||||
# in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
|
|
||||||
# calling retry_if. Pass an empty Array to call retry_if
|
|
||||||
# for all exceptions.
|
|
||||||
# @option options [Block] :retry_if (false) block that will receive
|
|
||||||
# the env object and the exception raised
|
|
||||||
# and should decide if the code should retry still the action or
|
|
||||||
# not independent of the retry count. This would be useful
|
|
||||||
# if the exception produced is non-recoverable or if the
|
|
||||||
# the HTTP method called is not idempotent.
|
|
||||||
# @option options [Block] :retry_block block that is executed before
|
|
||||||
# every retry. Request environment, middleware options, current number
|
|
||||||
# of retries and the exception is passed to the block as parameters.
|
|
||||||
# @option options [Array] :retry_statuses Array of Integer HTTP status
|
|
||||||
# codes or a single Integer value that determines whether to raise
|
|
||||||
# a Faraday::RetriableResponse exception based on the HTTP status code
|
|
||||||
# of an HTTP response.
|
|
||||||
def initialize(app, options = nil)
|
|
||||||
super(app)
|
|
||||||
@options = Options.from(options)
|
|
||||||
@errmatch = build_exception_matcher(@options.exceptions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_sleep_amount(retries, env)
|
|
||||||
retry_after = calculate_retry_after(env)
|
|
||||||
retry_interval = calculate_retry_interval(retries)
|
|
||||||
|
|
||||||
return if retry_after && retry_after > @options.max_interval
|
|
||||||
|
|
||||||
if retry_after && retry_after >= retry_interval
|
|
||||||
retry_after
|
|
||||||
else
|
|
||||||
retry_interval
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param env [Faraday::Env]
|
|
||||||
def call(env)
|
|
||||||
retries = @options.max
|
|
||||||
request_body = env[:body]
|
|
||||||
begin
|
|
||||||
# after failure env[:body] is set to the response body
|
|
||||||
env[:body] = request_body
|
|
||||||
@app.call(env).tap do |resp|
|
|
||||||
if @options.retry_statuses.include?(resp.status)
|
|
||||||
raise Faraday::RetriableResponse.new(nil, resp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue @errmatch => e
|
|
||||||
if retries.positive? && retry_request?(env, e)
|
|
||||||
retries -= 1
|
|
||||||
rewind_files(request_body)
|
|
||||||
@options.retry_block.call(env, @options, retries, e)
|
|
||||||
if (sleep_amount = calculate_sleep_amount(retries + 1, env))
|
|
||||||
sleep sleep_amount
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
raise unless e.is_a?(Faraday::RetriableResponse)
|
|
||||||
|
|
||||||
e.response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# An exception matcher for the rescue clause can usually be any object
|
|
||||||
# that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
|
|
||||||
#
|
|
||||||
# @param exceptions [Array]
|
|
||||||
# @api private
|
|
||||||
# @return [Module] an exception matcher
|
|
||||||
def build_exception_matcher(exceptions)
|
|
||||||
matcher = Module.new
|
|
||||||
(
|
|
||||||
class << matcher
|
|
||||||
self
|
|
||||||
end).class_eval do
|
|
||||||
define_method(:===) do |error|
|
|
||||||
exceptions.any? do |ex|
|
|
||||||
if ex.is_a? Module
|
|
||||||
error.is_a? ex
|
|
||||||
else
|
|
||||||
error.class.to_s == ex.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
matcher
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def retry_request?(env, exception)
|
|
||||||
@options.methods.include?(env[:method]) ||
|
|
||||||
@options.retry_if.call(env, exception)
|
|
||||||
end
|
|
||||||
|
|
||||||
def rewind_files(body)
|
|
||||||
return unless body.is_a?(Hash)
|
|
||||||
|
|
||||||
body.each do |_, value|
|
|
||||||
value.rewind if value.is_a?(UploadIO)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# MDN spec for Retry-After header:
|
|
||||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
|
||||||
def calculate_retry_after(env)
|
|
||||||
response_headers = env[:response_headers]
|
|
||||||
return unless response_headers
|
|
||||||
|
|
||||||
retry_after_value = env[:response_headers]['Retry-After']
|
|
||||||
|
|
||||||
# Try to parse date from the header value
|
|
||||||
begin
|
|
||||||
datetime = DateTime.rfc2822(retry_after_value)
|
|
||||||
datetime.to_time - Time.now.utc
|
|
||||||
rescue ArgumentError
|
|
||||||
retry_after_value.to_f
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_retry_interval(retries)
|
|
||||||
retry_index = @options.max - retries
|
|
||||||
current_interval = @options.interval *
|
|
||||||
(@options.backoff_factor**retry_index)
|
|
||||||
current_interval = [current_interval, @options.max_interval].min
|
|
||||||
random_interval = rand * @options.interval_randomness.to_f *
|
|
||||||
@options.interval
|
|
||||||
|
|
||||||
current_interval + random_interval
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,5 +1,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Faraday
|
module Faraday
|
||||||
VERSION = '1.7.0'
|
VERSION = '1.9.3'
|
||||||
end
|
end
|
||||||
|
@ -258,6 +258,38 @@ RSpec.describe Faraday::Adapter::Test do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'for request with non default params encoder' do
|
||||||
|
let(:connection) do
|
||||||
|
Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
|
||||||
|
builder.adapter :test, stubs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
let(:stubs) do
|
||||||
|
described_class::Stubs.new do |stubs|
|
||||||
|
stubs.get('/path?a=x&a=y&a=z') { [200, {}, 'a'] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when all flat param values are correctly set' do
|
||||||
|
subject(:request) { connection.get('/path?a=x&a=y&a=z') }
|
||||||
|
|
||||||
|
it { expect(request.status).to eq 200 }
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'raise NotFound when params do not satisfy the flat param values' do |params|
|
||||||
|
subject(:request) { connection.get('/path', params) }
|
||||||
|
|
||||||
|
context "with #{params.inspect}" do
|
||||||
|
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x] }
|
||||||
|
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x y] }
|
||||||
|
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x z y] } # NOTE: The order of the value is also compared.
|
||||||
|
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { b: %w[x y z] }
|
||||||
|
end
|
||||||
|
|
||||||
describe 'strict_mode' do
|
describe 'strict_mode' do
|
||||||
let(:stubs) do
|
let(:stubs) do
|
||||||
described_class::Stubs.new(strict_mode: true) do |stubs|
|
described_class::Stubs.new(strict_mode: true) do |stubs|
|
||||||
|
@ -84,5 +84,13 @@ RSpec.describe Faraday::Request::Authorization do
|
|||||||
|
|
||||||
include_examples 'does not interfere with existing authentication'
|
include_examples 'does not interfere with existing authentication'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when passed a string and a proc' do
|
||||||
|
let(:auth_config) { ['Bearer', -> { 'custom_from_proc' }] }
|
||||||
|
|
||||||
|
it { expect(response.body).to eq('Bearer custom_from_proc') }
|
||||||
|
|
||||||
|
include_examples 'does not interfere with existing authentication'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,302 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
RSpec.describe Faraday::Request::Multipart do
|
|
||||||
let(:options) { {} }
|
|
||||||
let(:conn) do
|
|
||||||
Faraday.new do |b|
|
|
||||||
b.request :multipart, options
|
|
||||||
b.request :url_encoded
|
|
||||||
b.adapter :test do |stub|
|
|
||||||
stub.post('/echo') do |env|
|
|
||||||
posted_as = env[:request_headers]['Content-Type']
|
|
||||||
expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
|
|
||||||
[200, { 'Content-Type' => posted_as }, env[:body].read]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'a multipart request' do
|
|
||||||
it 'generates a unique boundary for each request' do
|
|
||||||
response1 = conn.post('/echo', payload)
|
|
||||||
response2 = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
b1 = parse_multipart_boundary(response1.headers['Content-Type'])
|
|
||||||
b2 = parse_multipart_boundary(response2.headers['Content-Type'])
|
|
||||||
expect(b1).to_not eq(b2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'FilePart: when multipart objects in param' do
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
a: 1,
|
|
||||||
b: {
|
|
||||||
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
|
|
||||||
'Content-Disposition' => 'form-data; foo=1'),
|
|
||||||
d: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'forms a multipart request' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
|
||||||
result = parse_multipart(boundary, response.body)
|
|
||||||
expect(result[:errors]).to be_empty
|
|
||||||
|
|
||||||
part_a, body_a = result.part('a')
|
|
||||||
expect(part_a).to_not be_nil
|
|
||||||
expect(part_a.filename).to be_nil
|
|
||||||
expect(body_a).to eq('1')
|
|
||||||
|
|
||||||
part_bc, body_bc = result.part('b[c]')
|
|
||||||
expect(part_bc).to_not be_nil
|
|
||||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
|
||||||
expect(part_bc.headers['content-disposition'])
|
|
||||||
.to eq(
|
|
||||||
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
|
||||||
)
|
|
||||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
|
||||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
|
||||||
expect(body_bc).to eq(File.read(__FILE__))
|
|
||||||
|
|
||||||
part_bd, body_bd = result.part('b[d]')
|
|
||||||
expect(part_bd).to_not be_nil
|
|
||||||
expect(part_bd.filename).to be_nil
|
|
||||||
expect(body_bd).to eq('2')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'FilePart: when providing json and IO content in the same payload' do
|
|
||||||
let(:io) { StringIO.new('io-content') }
|
|
||||||
let(:json) do
|
|
||||||
{
|
|
||||||
b: 1,
|
|
||||||
c: 2
|
|
||||||
}.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
json: Faraday::ParamPart.new(json, 'application/json'),
|
|
||||||
io: Faraday::FilePart.new(io, 'application/pdf')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'forms a multipart request' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
|
||||||
result = parse_multipart(boundary, response.body)
|
|
||||||
expect(result[:errors]).to be_empty
|
|
||||||
|
|
||||||
part_json, body_json = result.part('json')
|
|
||||||
expect(part_json).to_not be_nil
|
|
||||||
expect(part_json.mime).to eq('application/json')
|
|
||||||
expect(part_json.filename).to be_nil
|
|
||||||
expect(body_json).to eq(json)
|
|
||||||
|
|
||||||
part_io, body_io = result.part('io')
|
|
||||||
expect(part_io).to_not be_nil
|
|
||||||
expect(part_io.mime).to eq('application/pdf')
|
|
||||||
expect(part_io.filename).to eq('local.path')
|
|
||||||
expect(body_io).to eq(io.string)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'FilePart: when multipart objects in array param' do
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
a: 1,
|
|
||||||
b: [{
|
|
||||||
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
|
|
||||||
d: 2
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'forms a multipart request' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
|
||||||
result = parse_multipart(boundary, response.body)
|
|
||||||
expect(result[:errors]).to be_empty
|
|
||||||
|
|
||||||
part_a, body_a = result.part('a')
|
|
||||||
expect(part_a).to_not be_nil
|
|
||||||
expect(part_a.filename).to be_nil
|
|
||||||
expect(body_a).to eq('1')
|
|
||||||
|
|
||||||
part_bc, body_bc = result.part('b[][c]')
|
|
||||||
expect(part_bc).to_not be_nil
|
|
||||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
|
||||||
expect(part_bc.headers['content-disposition'])
|
|
||||||
.to eq(
|
|
||||||
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
|
||||||
)
|
|
||||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
|
||||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
|
||||||
expect(body_bc).to eq(File.read(__FILE__))
|
|
||||||
|
|
||||||
part_bd, body_bd = result.part('b[][d]')
|
|
||||||
expect(part_bd).to_not be_nil
|
|
||||||
expect(part_bd.filename).to be_nil
|
|
||||||
expect(body_bd).to eq('2')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'UploadIO: when multipart objects in param' do
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
a: 1,
|
|
||||||
b: {
|
|
||||||
c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil,
|
|
||||||
'Content-Disposition' => 'form-data; foo=1'),
|
|
||||||
d: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'forms a multipart request' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
|
||||||
result = parse_multipart(boundary, response.body)
|
|
||||||
expect(result[:errors]).to be_empty
|
|
||||||
|
|
||||||
part_a, body_a = result.part('a')
|
|
||||||
expect(part_a).to_not be_nil
|
|
||||||
expect(part_a.filename).to be_nil
|
|
||||||
expect(body_a).to eq('1')
|
|
||||||
|
|
||||||
part_bc, body_bc = result.part('b[c]')
|
|
||||||
expect(part_bc).to_not be_nil
|
|
||||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
|
||||||
expect(part_bc.headers['content-disposition'])
|
|
||||||
.to eq(
|
|
||||||
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
|
||||||
)
|
|
||||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
|
||||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
|
||||||
expect(body_bc).to eq(File.read(__FILE__))
|
|
||||||
|
|
||||||
part_bd, body_bd = result.part('b[d]')
|
|
||||||
expect(part_bd).to_not be_nil
|
|
||||||
expect(part_bd.filename).to be_nil
|
|
||||||
expect(body_bd).to eq('2')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'UploadIO: when providing json and IO content in the same payload' do
|
|
||||||
let(:io) { StringIO.new('io-content') }
|
|
||||||
let(:json) do
|
|
||||||
{
|
|
||||||
b: 1,
|
|
||||||
c: 2
|
|
||||||
}.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
json: Faraday::ParamPart.new(json, 'application/json'),
|
|
||||||
io: Faraday::UploadIO.new(io, 'application/pdf')
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'forms a multipart request' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
|
||||||
result = parse_multipart(boundary, response.body)
|
|
||||||
expect(result[:errors]).to be_empty
|
|
||||||
|
|
||||||
part_json, body_json = result.part('json')
|
|
||||||
expect(part_json).to_not be_nil
|
|
||||||
expect(part_json.mime).to eq('application/json')
|
|
||||||
expect(part_json.filename).to be_nil
|
|
||||||
expect(body_json).to eq(json)
|
|
||||||
|
|
||||||
part_io, body_io = result.part('io')
|
|
||||||
expect(part_io).to_not be_nil
|
|
||||||
expect(part_io.mime).to eq('application/pdf')
|
|
||||||
expect(part_io.filename).to eq('local.path')
|
|
||||||
expect(body_io).to eq(io.string)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'UploadIO: when multipart objects in array param' do
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
a: 1,
|
|
||||||
b: [{
|
|
||||||
c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
|
|
||||||
d: 2
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'forms a multipart request' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
|
||||||
result = parse_multipart(boundary, response.body)
|
|
||||||
expect(result[:errors]).to be_empty
|
|
||||||
|
|
||||||
part_a, body_a = result.part('a')
|
|
||||||
expect(part_a).to_not be_nil
|
|
||||||
expect(part_a.filename).to be_nil
|
|
||||||
expect(body_a).to eq('1')
|
|
||||||
|
|
||||||
part_bc, body_bc = result.part('b[][c]')
|
|
||||||
expect(part_bc).to_not be_nil
|
|
||||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
|
||||||
expect(part_bc.headers['content-disposition'])
|
|
||||||
.to eq(
|
|
||||||
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
|
||||||
)
|
|
||||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
|
||||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
|
||||||
expect(body_bc).to eq(File.read(__FILE__))
|
|
||||||
|
|
||||||
part_bd, body_bd = result.part('b[][d]')
|
|
||||||
expect(part_bd).to_not be_nil
|
|
||||||
expect(part_bd.filename).to be_nil
|
|
||||||
expect(body_bd).to eq('2')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when passing flat_encode=true option' do
|
|
||||||
let(:options) { { flat_encode: true } }
|
|
||||||
let(:io) { StringIO.new('io-content') }
|
|
||||||
let(:payload) do
|
|
||||||
{
|
|
||||||
a: 1,
|
|
||||||
b: [
|
|
||||||
Faraday::UploadIO.new(io, 'application/pdf'),
|
|
||||||
Faraday::UploadIO.new(io, 'application/pdf')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'a multipart request'
|
|
||||||
|
|
||||||
it 'encode params using flat encoder' do
|
|
||||||
response = conn.post('/echo', payload)
|
|
||||||
|
|
||||||
expect(response.body).to include('name="b"')
|
|
||||||
expect(response.body).not_to include('name="b[]"')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,242 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
RSpec.describe Faraday::Request::Retry do
|
|
||||||
let(:calls) { [] }
|
|
||||||
let(:times_called) { calls.size }
|
|
||||||
let(:options) { [] }
|
|
||||||
let(:conn) do
|
|
||||||
Faraday.new do |b|
|
|
||||||
b.request :retry, *options
|
|
||||||
|
|
||||||
b.adapter :test do |stub|
|
|
||||||
%w[get post].each do |method|
|
|
||||||
stub.send(method, '/unstable') do |env|
|
|
||||||
calls << env.dup
|
|
||||||
env[:body] = nil # simulate blanking out response body
|
|
||||||
callback.call
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when an unexpected error happens' do
|
|
||||||
let(:callback) { -> { raise 'boom!' } }
|
|
||||||
|
|
||||||
before { expect { conn.get('/unstable') }.to raise_error(RuntimeError) }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(1) }
|
|
||||||
|
|
||||||
context 'and this is passed as a custom exception' do
|
|
||||||
let(:options) { [{ exceptions: StandardError }] }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(3) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when an expected error happens' do
|
|
||||||
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
|
||||||
|
|
||||||
before do
|
|
||||||
@started = Time.now
|
|
||||||
expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
end
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(3) }
|
|
||||||
|
|
||||||
context 'and legacy max_retry set to 1' do
|
|
||||||
let(:options) { [1] }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(2) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and legacy max_retry set to -9' do
|
|
||||||
let(:options) { [-9] }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(1) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and new max_retry set to 3' do
|
|
||||||
let(:options) { [{ max: 3 }] }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(4) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and new max_retry set to -9' do
|
|
||||||
let(:options) { [{ max: -9 }] }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(1) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and both max_retry and interval are set' do
|
|
||||||
let(:options) { [{ max: 2, interval: 0.1 }] }
|
|
||||||
|
|
||||||
it { expect(Time.now - @started).to be_within(0.04).of(0.2) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when no exception raised' do
|
|
||||||
let(:options) { [{ max: 1, retry_statuses: 429 }] }
|
|
||||||
|
|
||||||
before { conn.get('/unstable') }
|
|
||||||
|
|
||||||
context 'and response code is in retry_statuses' do
|
|
||||||
let(:callback) { -> { [429, {}, ''] } }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(2) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'and response code is not in retry_statuses' do
|
|
||||||
let(:callback) { -> { [503, {}, ''] } }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(1) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#calculate_retry_interval' do
|
|
||||||
context 'with exponential backoff' do
|
|
||||||
let(:options) { { max: 5, interval: 0.1, backoff_factor: 2 } }
|
|
||||||
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
|
||||||
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.4) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with exponential backoff and max_interval' do
|
|
||||||
let(:options) { { max: 5, interval: 0.1, backoff_factor: 2, max_interval: 0.3 } }
|
|
||||||
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
|
||||||
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.3) }
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 2)).to eq(0.3) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with exponential backoff and interval_randomness' do
|
|
||||||
let(:options) { { max: 2, interval: 0.1, interval_randomness: 0.05 } }
|
|
||||||
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
|
||||||
|
|
||||||
it { expect(middleware.send(:calculate_retry_interval, 2)).to be_between(0.1, 0.105) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when method is not idempotent' do
|
|
||||||
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
|
||||||
|
|
||||||
before { expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT) }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(1) }
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'retry_if option' do
|
|
||||||
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
|
||||||
let(:options) { [{ retry_if: @check }] }
|
|
||||||
|
|
||||||
it 'retries if retry_if block always returns true' do
|
|
||||||
body = { foo: :bar }
|
|
||||||
@check = ->(_, _) { true }
|
|
||||||
expect { conn.post('/unstable', body) }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(3)
|
|
||||||
expect(calls.all? { |env| env[:body] == body }).to be_truthy
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not retry if retry_if block returns false checking env' do
|
|
||||||
@check = ->(env, _) { env[:method] != :post }
|
|
||||||
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not retry if retry_if block returns false checking exception' do
|
|
||||||
@check = ->(_, exception) { !exception.is_a?(Errno::ETIMEDOUT) }
|
|
||||||
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'FilePart: should rewind files on retry' do
|
|
||||||
io = StringIO.new('Test data')
|
|
||||||
filepart = Faraday::FilePart.new(io, 'application/octet/stream')
|
|
||||||
|
|
||||||
rewound = 0
|
|
||||||
rewind = -> { rewound += 1 }
|
|
||||||
|
|
||||||
@check = ->(_, _) { true }
|
|
||||||
allow(filepart).to receive(:rewind, &rewind)
|
|
||||||
expect { conn.post('/unstable', file: filepart) }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(3)
|
|
||||||
expect(rewound).to eq(2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'UploadIO: should rewind files on retry' do
|
|
||||||
io = StringIO.new('Test data')
|
|
||||||
upload_io = Faraday::UploadIO.new(io, 'application/octet/stream')
|
|
||||||
|
|
||||||
rewound = 0
|
|
||||||
rewind = -> { rewound += 1 }
|
|
||||||
|
|
||||||
@check = ->(_, _) { true }
|
|
||||||
allow(upload_io).to receive(:rewind, &rewind)
|
|
||||||
expect { conn.post('/unstable', file: upload_io) }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(3)
|
|
||||||
expect(rewound).to eq(2)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when explicitly specifying methods to retry' do
|
|
||||||
let(:options) { [{ retry_if: @check, methods: [:post] }] }
|
|
||||||
|
|
||||||
it 'does not call retry_if for specified methods' do
|
|
||||||
@check = ->(_, _) { raise 'this should have never been called' }
|
|
||||||
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with empty list of methods to retry' do
|
|
||||||
let(:options) { [{ retry_if: @check, methods: [] }] }
|
|
||||||
|
|
||||||
it 'calls retry_if for all methods' do
|
|
||||||
@check = ->(_, _) { calls.size < 2 }
|
|
||||||
expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
|
||||||
expect(times_called).to eq(2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'retry_after header support' do
|
|
||||||
let(:callback) { -> { [504, headers, ''] } }
|
|
||||||
let(:elapsed) { Time.now - @started }
|
|
||||||
|
|
||||||
before do
|
|
||||||
@started = Time.now
|
|
||||||
conn.get('/unstable')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when retry_after bigger than interval' do
|
|
||||||
let(:headers) { { 'Retry-After' => '0.5' } }
|
|
||||||
let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
|
|
||||||
|
|
||||||
it { expect(elapsed).to be > 0.5 }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when retry_after smaller than interval' do
|
|
||||||
let(:headers) { { 'Retry-After' => '0.1' } }
|
|
||||||
let(:options) { [{ max: 1, interval: 0.2, retry_statuses: 504 }] }
|
|
||||||
|
|
||||||
it { expect(elapsed).to be > 0.2 }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when retry_after is a timestamp' do
|
|
||||||
let(:headers) { { 'Retry-After' => (Time.now.utc + 2).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
|
|
||||||
let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
|
|
||||||
|
|
||||||
it { expect(elapsed).to be > 1 }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when retry_after is bigger than max_interval' do
|
|
||||||
let(:headers) { { 'Retry-After' => (Time.now.utc + 20).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
|
|
||||||
let(:options) { [{ max: 2, interval: 0.1, max_interval: 5, retry_statuses: 504 }] }
|
|
||||||
|
|
||||||
it { expect(times_called).to eq(1) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
x
Reference in New Issue
Block a user