faraday 0.8.11 → 0.9.0.rc1
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.
- data/.document +6 -0
- data/CONTRIBUTING.md +36 -0
- data/Gemfile +7 -6
- data/LICENSE.md +1 -1
- data/README.md +38 -51
- data/Rakefile +2 -18
- data/faraday.gemspec +34 -0
- data/lib/faraday.rb +181 -67
- data/lib/faraday/adapter.rb +19 -34
- data/lib/faraday/adapter/em_http.rb +24 -10
- data/lib/faraday/adapter/em_synchrony.rb +1 -15
- data/lib/faraday/adapter/excon.rb +6 -12
- data/lib/faraday/adapter/httpclient.rb +92 -0
- data/lib/faraday/adapter/net_http.rb +2 -3
- data/lib/faraday/adapter/net_http_persistent.rb +3 -15
- data/lib/faraday/adapter/patron.rb +13 -21
- data/lib/faraday/adapter/rack.rb +0 -2
- data/lib/faraday/adapter/test.rb +35 -36
- data/lib/faraday/adapter/typhoeus.rb +10 -12
- data/lib/faraday/autoload.rb +87 -0
- data/lib/faraday/connection.rb +196 -99
- data/lib/faraday/error.rb +33 -33
- data/lib/faraday/options.rb +215 -0
- data/lib/faraday/parameters.rb +193 -0
- data/lib/faraday/{builder.rb → rack_builder.rb} +78 -21
- data/lib/faraday/request.rb +12 -25
- data/lib/faraday/request/authorization.rb +3 -3
- data/lib/faraday/request/basic_authentication.rb +1 -1
- data/lib/faraday/request/instrumentation.rb +38 -0
- data/lib/faraday/request/multipart.rb +10 -9
- data/lib/faraday/request/retry.rb +70 -6
- data/lib/faraday/request/token_authentication.rb +2 -2
- data/lib/faraday/request/url_encoded.rb +7 -6
- data/lib/faraday/response.rb +17 -22
- data/lib/faraday/response/logger.rb +4 -4
- data/lib/faraday/response/raise_error.rb +4 -5
- data/lib/faraday/utils.rb +54 -67
- data/script/console +7 -0
- data/script/release +6 -3
- data/script/server +3 -1
- data/script/test +7 -33
- data/test/adapters/em_http_test.rb +6 -1
- data/test/adapters/em_synchrony_test.rb +7 -1
- data/test/adapters/excon_test.rb +0 -7
- data/test/adapters/httpclient_test.rb +16 -0
- data/test/adapters/integration.rb +8 -39
- data/test/adapters/logger_test.rb +1 -1
- data/test/adapters/net_http_test.rb +0 -31
- data/test/adapters/patron_test.rb +1 -1
- data/test/adapters/rack_test.rb +0 -5
- data/test/adapters/test_middleware_test.rb +19 -4
- data/test/adapters/typhoeus_test.rb +20 -3
- data/test/authentication_middleware_test.rb +7 -7
- data/test/connection_test.rb +52 -75
- data/test/env_test.rb +33 -24
- data/test/helper.rb +15 -13
- data/test/live_server.rb +10 -4
- data/test/middleware/instrumentation_test.rb +75 -0
- data/test/middleware/retry_test.rb +44 -38
- data/test/middleware_stack_test.rb +12 -11
- data/test/options_test.rb +126 -0
- data/test/request_middleware_test.rb +17 -7
- data/test/response_middleware_test.rb +2 -4
- data/test/strawberry.rb +2 -0
- metadata +82 -28
- checksums.yaml +0 -7
- data/script/proxy-server +0 -41
- data/test/multibyte.txt +0 -1
- data/test/parameters_test.rb +0 -24
- data/test/utils_test.rb +0 -30
data/lib/faraday/error.rb
CHANGED
@@ -1,45 +1,45 @@
|
|
1
1
|
module Faraday
|
2
|
-
|
3
|
-
|
4
|
-
attr_reader :response
|
2
|
+
class Error < StandardError; end
|
3
|
+
class MissingDependency < Error; end
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
@response = response
|
5
|
+
class ClientError < Error
|
6
|
+
attr_reader :response
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
elsif ex.respond_to?(:each_key)
|
14
|
-
super("the server responded with status #{ex[:status]}")
|
15
|
-
@response = ex
|
16
|
-
else
|
17
|
-
super(ex.to_s)
|
18
|
-
end
|
19
|
-
end
|
8
|
+
def initialize(ex, response = nil)
|
9
|
+
@wrapped_exception = nil
|
10
|
+
@response = response
|
20
11
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
12
|
+
if ex.respond_to?(:backtrace)
|
13
|
+
super(ex.message)
|
14
|
+
@wrapped_exception = ex
|
15
|
+
elsif ex.respond_to?(:each_key)
|
16
|
+
super("the server responded with status #{ex[:status]}")
|
17
|
+
@response = ex
|
18
|
+
else
|
19
|
+
super(ex.to_s)
|
27
20
|
end
|
21
|
+
end
|
28
22
|
|
29
|
-
|
30
|
-
|
23
|
+
def backtrace
|
24
|
+
if @wrapped_exception
|
25
|
+
@wrapped_exception.backtrace
|
26
|
+
else
|
27
|
+
super
|
31
28
|
end
|
32
29
|
end
|
33
30
|
|
34
|
-
|
35
|
-
|
36
|
-
class ParsingError < ClientError; end
|
37
|
-
class MissingDependency < StandardError; end
|
38
|
-
|
39
|
-
class TimeoutError < ClientError
|
40
|
-
def initialize(ex = nil)
|
41
|
-
super(ex || "timeout")
|
42
|
-
end
|
31
|
+
def inspect
|
32
|
+
%(#<#{self.class}>)
|
43
33
|
end
|
44
34
|
end
|
35
|
+
|
36
|
+
class ConnectionFailed < ClientError; end
|
37
|
+
class ResourceNotFound < ClientError; end
|
38
|
+
class ParsingError < ClientError; end
|
39
|
+
class TimeoutError < ClientError; end
|
40
|
+
|
41
|
+
[:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
|
42
|
+
:ParsingError, :TimeoutError].each do |const|
|
43
|
+
Error.const_set(const, Faraday.const_get(const))
|
44
|
+
end
|
45
45
|
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module Faraday
|
2
|
+
# Subclasses Struct with some special helpers for converting from a Hash to
|
3
|
+
# a Struct.
|
4
|
+
class Options < Struct
|
5
|
+
# Public
|
6
|
+
def self.from(value)
|
7
|
+
value ? new.update(value) : new
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public
|
11
|
+
def each(&block)
|
12
|
+
members.each do |key|
|
13
|
+
block.call key.to_sym, send(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public
|
18
|
+
def update(obj)
|
19
|
+
obj.each do |key, value|
|
20
|
+
next unless value
|
21
|
+
sub_options = self.class.options_for(key)
|
22
|
+
if sub_options && value
|
23
|
+
value = sub_options.from(value)
|
24
|
+
elsif Hash === value
|
25
|
+
hash = {}
|
26
|
+
value.each do |hash_key, hash_value|
|
27
|
+
hash[hash_key] = hash_value
|
28
|
+
end
|
29
|
+
value = hash
|
30
|
+
end
|
31
|
+
|
32
|
+
self.send("#{key}=", value)
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public
|
38
|
+
def delete(key)
|
39
|
+
value = send(key)
|
40
|
+
send("#{key}=", nil)
|
41
|
+
value
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public
|
45
|
+
def merge(value)
|
46
|
+
dup.update(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public
|
50
|
+
def fetch(key, default = nil)
|
51
|
+
send(key) || send("#{key}=", default ||
|
52
|
+
(block_given? ? Proc.new.call : nil))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public
|
56
|
+
def values_at(*keys)
|
57
|
+
keys.map { |key| send(key) }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public
|
61
|
+
def keys
|
62
|
+
members.reject { |m| send(m).nil? }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public
|
66
|
+
def to_hash
|
67
|
+
hash = {}
|
68
|
+
members.each do |key|
|
69
|
+
value = send(key)
|
70
|
+
hash[key.to_sym] = value if value
|
71
|
+
end
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
# Internal
|
76
|
+
def inspect
|
77
|
+
values = []
|
78
|
+
members.each do |m|
|
79
|
+
value = send(m)
|
80
|
+
values << "#{m}=#{value.inspect}" if value
|
81
|
+
end
|
82
|
+
values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
|
83
|
+
|
84
|
+
%(#<#{self.class}#{values}>)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Internal
|
88
|
+
def self.options(mapping)
|
89
|
+
attribute_options.update(mapping)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Internal
|
93
|
+
def self.options_for(key)
|
94
|
+
attribute_options[key]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Internal
|
98
|
+
def self.attribute_options
|
99
|
+
@attribute_options ||= {}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
|
104
|
+
:timeout, :open_timeout, :boundary,
|
105
|
+
:oauth)
|
106
|
+
|
107
|
+
def []=(key, value)
|
108
|
+
if key && key.to_sym == :proxy
|
109
|
+
super(key, value ? ProxyOptions.from(value) : nil)
|
110
|
+
else
|
111
|
+
super(key, value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
|
117
|
+
:cert_store, :client_cert, :client_key, :verify_depth, :version)
|
118
|
+
|
119
|
+
def verify?
|
120
|
+
verify != false
|
121
|
+
end
|
122
|
+
|
123
|
+
def disable?
|
124
|
+
!verify?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class ProxyOptions < Options.new(:uri, :user, :password)
|
129
|
+
extend Forwardable
|
130
|
+
def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
|
131
|
+
|
132
|
+
def self.from(value)
|
133
|
+
case value
|
134
|
+
when String then value = {:uri => Utils.URI(value)}
|
135
|
+
when URI then value = {:uri => value}
|
136
|
+
when Hash, Options
|
137
|
+
if uri = value.delete(:uri)
|
138
|
+
value[:uri] = Utils.URI(uri)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
super(value)
|
142
|
+
end
|
143
|
+
|
144
|
+
def user
|
145
|
+
self[:user] ||= Utils.unescape(uri.user)
|
146
|
+
end
|
147
|
+
|
148
|
+
def password
|
149
|
+
self[:password] ||= Utils.unescape(uri.password)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
|
154
|
+
:parallel_manager, :params, :headers, :builder_class)
|
155
|
+
|
156
|
+
options :request => RequestOptions, :ssl => SSLOptions
|
157
|
+
|
158
|
+
def request
|
159
|
+
self[:request] ||= self.class.options_for(:request).new
|
160
|
+
end
|
161
|
+
|
162
|
+
def ssl
|
163
|
+
self[:ssl] ||= self.class.options_for(:ssl).new
|
164
|
+
end
|
165
|
+
|
166
|
+
def builder_class
|
167
|
+
self[:builder_class] ||= RackBuilder
|
168
|
+
end
|
169
|
+
|
170
|
+
def new_builder(block)
|
171
|
+
builder_class.new(&block)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class Env < Options.new(:method, :body, :url, :request, :request_headers,
|
176
|
+
:ssl, :parallel_manager, :params, :response, :response_headers, :status)
|
177
|
+
|
178
|
+
ContentLength = 'Content-Length'.freeze
|
179
|
+
StatusesWithoutBody = Set.new [204, 304]
|
180
|
+
SuccessfulStatuses = 200..299
|
181
|
+
|
182
|
+
# A Set of HTTP verbs that typically send a body. If no body is set for
|
183
|
+
# these requests, the Content-Length header is set to 0.
|
184
|
+
MethodsWithBodies = Set.new [:post, :put, :patch, :options]
|
185
|
+
|
186
|
+
options :request => RequestOptions,
|
187
|
+
:request_headers => Utils::Headers, :response_headers => Utils::Headers
|
188
|
+
|
189
|
+
extend Forwardable
|
190
|
+
|
191
|
+
def_delegators :request, :params_encoder
|
192
|
+
|
193
|
+
def success?
|
194
|
+
SuccessfulStatuses.include?(status)
|
195
|
+
end
|
196
|
+
|
197
|
+
def needs_body?
|
198
|
+
!body && MethodsWithBodies.include?(method)
|
199
|
+
end
|
200
|
+
|
201
|
+
def clear_body
|
202
|
+
request_headers[ContentLength] = '0'
|
203
|
+
self.body = ''
|
204
|
+
end
|
205
|
+
|
206
|
+
def parse_body?
|
207
|
+
!StatusesWithoutBody.include?(status)
|
208
|
+
end
|
209
|
+
|
210
|
+
def parallel?
|
211
|
+
!!parallel_manager
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Faraday
|
2
|
+
module NestedParamsEncoder
|
3
|
+
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
|
4
|
+
|
5
|
+
def self.escape(s)
|
6
|
+
return s.to_s.gsub(ESCAPE_RE) {
|
7
|
+
'%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
|
8
|
+
}.tr(' ', '+')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.unescape(s)
|
12
|
+
CGI.unescape(s.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.encode(params)
|
16
|
+
return nil if params == nil
|
17
|
+
|
18
|
+
if !params.is_a?(Array)
|
19
|
+
if !params.respond_to?(:to_hash)
|
20
|
+
raise TypeError,
|
21
|
+
"Can't convert #{params.class} into Hash."
|
22
|
+
end
|
23
|
+
params = params.to_hash
|
24
|
+
params = params.map do |key, value|
|
25
|
+
key = key.to_s if key.kind_of?(Symbol)
|
26
|
+
[key, value]
|
27
|
+
end
|
28
|
+
# Useful default for OAuth and caching.
|
29
|
+
# Only to be used for non-Array inputs. Arrays should preserve order.
|
30
|
+
params.sort!
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper lambda
|
34
|
+
to_query = lambda do |parent, value|
|
35
|
+
if value.is_a?(Hash)
|
36
|
+
value = value.map do |key, val|
|
37
|
+
key = escape(key)
|
38
|
+
[key, val]
|
39
|
+
end
|
40
|
+
value.sort!
|
41
|
+
buffer = ""
|
42
|
+
value.each do |key, val|
|
43
|
+
new_parent = "#{parent}%5B#{key}%5D"
|
44
|
+
buffer << "#{to_query.call(new_parent, val)}&"
|
45
|
+
end
|
46
|
+
return buffer.chop
|
47
|
+
elsif value.is_a?(Array)
|
48
|
+
buffer = ""
|
49
|
+
value.each_with_index do |val, i|
|
50
|
+
new_parent = "#{parent}%5B%5D"
|
51
|
+
buffer << "#{to_query.call(new_parent, val)}&"
|
52
|
+
end
|
53
|
+
return buffer.chop
|
54
|
+
else
|
55
|
+
encoded_value = escape(value)
|
56
|
+
return "#{parent}=#{encoded_value}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
61
|
+
buffer = ''
|
62
|
+
params.each do |parent, value|
|
63
|
+
encoded_parent = escape(parent)
|
64
|
+
buffer << "#{to_query.call(encoded_parent, value)}&"
|
65
|
+
end
|
66
|
+
return buffer.chop
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.decode(query)
|
70
|
+
return nil if query == nil
|
71
|
+
# Recursive helper lambda
|
72
|
+
dehash = lambda do |hash|
|
73
|
+
hash.each do |(key, value)|
|
74
|
+
if value.kind_of?(Hash)
|
75
|
+
hash[key] = dehash.call(value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# Numeric keys implies an array
|
79
|
+
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
|
80
|
+
hash.sort.inject([]) do |accu, (_, value)|
|
81
|
+
accu << value; accu
|
82
|
+
end
|
83
|
+
else
|
84
|
+
hash
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
empty_accumulator = {}
|
89
|
+
return ((query.split('&').map do |pair|
|
90
|
+
pair.split('=', 2) if pair && !pair.empty?
|
91
|
+
end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
|
92
|
+
key = unescape(key)
|
93
|
+
if value.kind_of?(String)
|
94
|
+
value = unescape(value.gsub(/\+/, ' '))
|
95
|
+
end
|
96
|
+
|
97
|
+
array_notation = !!(key =~ /\[\]$/)
|
98
|
+
subkeys = key.split(/[\[\]]+/)
|
99
|
+
current_hash = accu
|
100
|
+
for i in 0...(subkeys.size - 1)
|
101
|
+
subkey = subkeys[i]
|
102
|
+
current_hash[subkey] = {} unless current_hash[subkey]
|
103
|
+
current_hash = current_hash[subkey]
|
104
|
+
end
|
105
|
+
if array_notation
|
106
|
+
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
|
107
|
+
current_hash[subkeys.last] << value
|
108
|
+
else
|
109
|
+
current_hash[subkeys.last] = value
|
110
|
+
end
|
111
|
+
accu
|
112
|
+
end).inject(empty_accumulator.dup) do |accu, (key, value)|
|
113
|
+
accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
|
114
|
+
accu
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
module FlatParamsEncoder
|
120
|
+
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
|
121
|
+
|
122
|
+
def self.escape(s)
|
123
|
+
return s.to_s.gsub(ESCAPE_RE) {
|
124
|
+
'%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
|
125
|
+
}.tr(' ', '+')
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.unescape(s)
|
129
|
+
CGI.unescape(s.to_s)
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.encode(params)
|
133
|
+
return nil if params == nil
|
134
|
+
|
135
|
+
if !params.is_a?(Array)
|
136
|
+
if !params.respond_to?(:to_hash)
|
137
|
+
raise TypeError,
|
138
|
+
"Can't convert #{params.class} into Hash."
|
139
|
+
end
|
140
|
+
params = params.to_hash
|
141
|
+
params = params.map do |key, value|
|
142
|
+
key = key.to_s if key.kind_of?(Symbol)
|
143
|
+
[key, value]
|
144
|
+
end
|
145
|
+
# Useful default for OAuth and caching.
|
146
|
+
# Only to be used for non-Array inputs. Arrays should preserve order.
|
147
|
+
params.sort!
|
148
|
+
end
|
149
|
+
|
150
|
+
# The params have form [['key1', 'value1'], ['key2', 'value2']].
|
151
|
+
buffer = ''
|
152
|
+
params.each do |key, value|
|
153
|
+
encoded_key = escape(key)
|
154
|
+
value = value.to_s if value == true || value == false
|
155
|
+
if value == nil
|
156
|
+
buffer << "#{encoded_key}&"
|
157
|
+
elsif value.kind_of?(Array)
|
158
|
+
value.each do |sub_value|
|
159
|
+
encoded_value = escape(sub_value)
|
160
|
+
buffer << "#{encoded_key}=#{encoded_value}&"
|
161
|
+
end
|
162
|
+
else
|
163
|
+
encoded_value = escape(value)
|
164
|
+
buffer << "#{encoded_key}=#{encoded_value}&"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
return buffer.chop
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.decode(query)
|
171
|
+
empty_accumulator = {}
|
172
|
+
return nil if query == nil
|
173
|
+
split_query = (query.split('&').map do |pair|
|
174
|
+
pair.split('=', 2) if pair && !pair.empty?
|
175
|
+
end).compact
|
176
|
+
return split_query.inject(empty_accumulator.dup) do |accu, pair|
|
177
|
+
pair[0] = unescape(pair[0])
|
178
|
+
pair[1] = true if pair[1].nil?
|
179
|
+
if pair[1].respond_to?(:to_str)
|
180
|
+
pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
|
181
|
+
end
|
182
|
+
if accu[pair[0]].kind_of?(Array)
|
183
|
+
accu[pair[0]] << pair[1]
|
184
|
+
elsif accu[pair[0]]
|
185
|
+
accu[pair[0]] = [accu[pair[0]], pair[1]]
|
186
|
+
else
|
187
|
+
accu[pair[0]] = pair[1]
|
188
|
+
end
|
189
|
+
accu
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|