avdi-faraday 0.8.1
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/Gemfile +27 -0
- data/LICENSE.md +20 -0
- data/README.md +250 -0
- data/Rakefile +87 -0
- data/config.ru +6 -0
- data/faraday.gemspec +86 -0
- data/lib/faraday.rb +276 -0
- data/lib/faraday/adapter.rb +71 -0
- data/lib/faraday/adapter/em_http.rb +217 -0
- data/lib/faraday/adapter/em_synchrony.rb +89 -0
- data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
- data/lib/faraday/adapter/excon.rb +59 -0
- data/lib/faraday/adapter/httpclient.rb +92 -0
- data/lib/faraday/adapter/net_http.rb +116 -0
- data/lib/faraday/adapter/net_http_persistent.rb +37 -0
- data/lib/faraday/adapter/patron.rb +65 -0
- data/lib/faraday/adapter/rack.rb +57 -0
- data/lib/faraday/adapter/test.rb +162 -0
- data/lib/faraday/adapter/typhoeus.rb +107 -0
- data/lib/faraday/builder.rb +184 -0
- data/lib/faraday/connection.rb +468 -0
- data/lib/faraday/error.rb +40 -0
- data/lib/faraday/middleware.rb +41 -0
- data/lib/faraday/request.rb +101 -0
- data/lib/faraday/request/authorization.rb +40 -0
- data/lib/faraday/request/basic_authentication.rb +13 -0
- data/lib/faraday/request/multipart.rb +62 -0
- data/lib/faraday/request/retry.rb +67 -0
- data/lib/faraday/request/token_authentication.rb +15 -0
- data/lib/faraday/request/url_encoded.rb +35 -0
- data/lib/faraday/response.rb +99 -0
- data/lib/faraday/response/logger.rb +34 -0
- data/lib/faraday/response/raise_error.rb +16 -0
- data/lib/faraday/upload_io.rb +23 -0
- data/lib/faraday/utils.rb +274 -0
- data/script/test +91 -0
- data/test/adapters/default_test.rb +14 -0
- data/test/adapters/em_http_test.rb +19 -0
- data/test/adapters/em_synchrony_test.rb +20 -0
- data/test/adapters/excon_test.rb +15 -0
- data/test/adapters/httpclient_test.rb +16 -0
- data/test/adapters/integration.rb +193 -0
- data/test/adapters/logger_test.rb +37 -0
- data/test/adapters/net_http_persistent_test.rb +11 -0
- data/test/adapters/net_http_test.rb +49 -0
- data/test/adapters/patron_test.rb +17 -0
- data/test/adapters/rack_test.rb +26 -0
- data/test/adapters/test_middleware_test.rb +70 -0
- data/test/adapters/typhoeus_test.rb +20 -0
- data/test/authentication_middleware_test.rb +65 -0
- data/test/connection_test.rb +375 -0
- data/test/env_test.rb +183 -0
- data/test/helper.rb +75 -0
- data/test/live_server.rb +57 -0
- data/test/middleware/retry_test.rb +62 -0
- data/test/middleware_stack_test.rb +203 -0
- data/test/middleware_test.rb +12 -0
- data/test/request_middleware_test.rb +108 -0
- data/test/response_middleware_test.rb +74 -0
- metadata +182 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
class Response::Logger < Response::Middleware
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize(app, logger = nil)
|
8
|
+
super(app)
|
9
|
+
@logger = logger || begin
|
10
|
+
require 'logger'
|
11
|
+
::Logger.new(STDOUT)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
info "#{env[:method]} #{env[:url].to_s}"
|
19
|
+
debug('request') { dump_headers env[:request_headers] }
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_complete(env)
|
24
|
+
info('Status') { env[:status].to_s }
|
25
|
+
debug('response') { dump_headers env[:response_headers] }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def dump_headers(headers)
|
31
|
+
headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Faraday
|
2
|
+
class Response::RaiseError < Response::Middleware
|
3
|
+
def on_complete(env)
|
4
|
+
case env[:status]
|
5
|
+
when 404
|
6
|
+
raise Faraday::Error::ResourceNotFound, response_values(env)
|
7
|
+
when 400...600
|
8
|
+
raise Faraday::Error::ClientError, response_values(env)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def response_values(env)
|
13
|
+
{:status => env[:status], :headers => env[:response_headers], :body => env[:body]}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
begin
|
2
|
+
require 'composite_io'
|
3
|
+
require 'parts'
|
4
|
+
require 'stringio'
|
5
|
+
rescue LoadError
|
6
|
+
$stderr.puts "Install the multipart-post gem."
|
7
|
+
raise
|
8
|
+
end
|
9
|
+
|
10
|
+
module Faraday
|
11
|
+
class CompositeReadIO < ::CompositeReadIO
|
12
|
+
attr_reader :length
|
13
|
+
|
14
|
+
def initialize(parts)
|
15
|
+
@length = parts.inject(0) { |sum, part| sum + part.length }
|
16
|
+
ios = parts.map{ |part| part.to_io }
|
17
|
+
super(*ios)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
UploadIO = ::UploadIO
|
22
|
+
Parts = ::Parts
|
23
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Faraday
|
4
|
+
module Utils
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Adapted from Rack::Utils::HeaderHash
|
8
|
+
class Headers < ::Hash
|
9
|
+
def initialize(hash={})
|
10
|
+
super()
|
11
|
+
@names = {}
|
12
|
+
self.update hash
|
13
|
+
end
|
14
|
+
|
15
|
+
# symbol -> string mapper + cache
|
16
|
+
KeyMap = Hash.new do |map, key|
|
17
|
+
map[key] = if key.respond_to?(:to_str) then key
|
18
|
+
else
|
19
|
+
key.to_s.split('_'). # :user_agent => %w(user agent)
|
20
|
+
each { |w| w.capitalize! }. # => %w(User Agent)
|
21
|
+
join('-') # => "User-Agent"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
KeyMap[:etag] = "ETag"
|
25
|
+
|
26
|
+
def [](k)
|
27
|
+
k = KeyMap[k]
|
28
|
+
super(k) || super(@names[k.downcase])
|
29
|
+
end
|
30
|
+
|
31
|
+
def []=(k, v)
|
32
|
+
k = KeyMap[k]
|
33
|
+
k = (@names[k.downcase] ||= k)
|
34
|
+
# join multiple values with a comma
|
35
|
+
v = v.to_ary.join(', ') if v.respond_to? :to_ary
|
36
|
+
super k, v
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(k)
|
40
|
+
k = KeyMap[k]
|
41
|
+
if k = @names[k.downcase]
|
42
|
+
@names.delete k.downcase
|
43
|
+
super k
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def include?(k)
|
48
|
+
@names.include? k.downcase
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :has_key?, :include?
|
52
|
+
alias_method :member?, :include?
|
53
|
+
alias_method :key?, :include?
|
54
|
+
|
55
|
+
def merge!(other)
|
56
|
+
other.each { |k, v| self[k] = v }
|
57
|
+
self
|
58
|
+
end
|
59
|
+
alias_method :update, :merge!
|
60
|
+
|
61
|
+
def merge(other)
|
62
|
+
hash = dup
|
63
|
+
hash.merge! other
|
64
|
+
end
|
65
|
+
|
66
|
+
def replace(other)
|
67
|
+
clear
|
68
|
+
self.update other
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_hash() ::Hash.new.update(self) end
|
73
|
+
|
74
|
+
def parse(header_string)
|
75
|
+
return unless header_string && !header_string.empty?
|
76
|
+
header_string.split(/\r\n/).
|
77
|
+
tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
|
78
|
+
map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
|
79
|
+
each { |key, value|
|
80
|
+
# join multiple values with a comma
|
81
|
+
if self[key] then self[key] << ', ' << value
|
82
|
+
else self[key] = value
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# hash with stringified keys
|
89
|
+
class ParamsHash < Hash
|
90
|
+
def [](key)
|
91
|
+
super(convert_key(key))
|
92
|
+
end
|
93
|
+
|
94
|
+
def []=(key, value)
|
95
|
+
super(convert_key(key), value)
|
96
|
+
end
|
97
|
+
|
98
|
+
def delete(key)
|
99
|
+
super(convert_key(key))
|
100
|
+
end
|
101
|
+
|
102
|
+
def include?(key)
|
103
|
+
super(convert_key(key))
|
104
|
+
end
|
105
|
+
|
106
|
+
alias_method :has_key?, :include?
|
107
|
+
alias_method :member?, :include?
|
108
|
+
alias_method :key?, :include?
|
109
|
+
|
110
|
+
def update(params)
|
111
|
+
params.each do |key, value|
|
112
|
+
self[key] = value
|
113
|
+
end
|
114
|
+
self
|
115
|
+
end
|
116
|
+
alias_method :merge!, :update
|
117
|
+
|
118
|
+
def merge(params)
|
119
|
+
dup.update(params)
|
120
|
+
end
|
121
|
+
|
122
|
+
def replace(other)
|
123
|
+
clear
|
124
|
+
update(other)
|
125
|
+
end
|
126
|
+
|
127
|
+
def merge_query(query)
|
128
|
+
if query && !query.empty?
|
129
|
+
update Utils.parse_query(query)
|
130
|
+
end
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_query
|
135
|
+
Utils.build_nested_query(self)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def convert_key(key)
|
141
|
+
key.to_s
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Copied from Rack
|
146
|
+
def build_query(params)
|
147
|
+
params.map { |k, v|
|
148
|
+
if v.class == Array
|
149
|
+
build_query(v.map { |x| [k, x] })
|
150
|
+
else
|
151
|
+
v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
|
152
|
+
end
|
153
|
+
}.join("&")
|
154
|
+
end
|
155
|
+
|
156
|
+
# Rack's version modified to handle non-String values
|
157
|
+
def build_nested_query(value, prefix = nil)
|
158
|
+
case value
|
159
|
+
when Array
|
160
|
+
value.map { |v| build_nested_query(v, "#{prefix}%5B%5D") }.join("&")
|
161
|
+
when Hash
|
162
|
+
value.map { |k, v|
|
163
|
+
build_nested_query(v, prefix ? "#{prefix}%5B#{escape(k)}%5D" : escape(k))
|
164
|
+
}.join("&")
|
165
|
+
when NilClass
|
166
|
+
prefix
|
167
|
+
else
|
168
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
169
|
+
"#{prefix}=#{escape(value)}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
ESCAPE_RE = /[^\w .~-]+/
|
174
|
+
|
175
|
+
def escape(s)
|
176
|
+
s.to_s.gsub(ESCAPE_RE) {
|
177
|
+
'%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
|
178
|
+
}.tr(' ', '+')
|
179
|
+
end
|
180
|
+
|
181
|
+
def unescape(s) CGI.unescape s.to_s end
|
182
|
+
|
183
|
+
DEFAULT_SEP = /[&;] */n
|
184
|
+
|
185
|
+
# Adapted from Rack
|
186
|
+
def parse_query(qs)
|
187
|
+
params = {}
|
188
|
+
|
189
|
+
(qs || '').split(DEFAULT_SEP).each do |p|
|
190
|
+
k, v = p.split('=', 2).map { |x| unescape(x) }
|
191
|
+
|
192
|
+
if cur = params[k]
|
193
|
+
if cur.class == Array then params[k] << v
|
194
|
+
else params[k] = [cur, v]
|
195
|
+
end
|
196
|
+
else
|
197
|
+
params[k] = v
|
198
|
+
end
|
199
|
+
end
|
200
|
+
params
|
201
|
+
end
|
202
|
+
|
203
|
+
def parse_nested_query(qs)
|
204
|
+
params = {}
|
205
|
+
|
206
|
+
(qs || '').split(DEFAULT_SEP).each do |p|
|
207
|
+
k, v = p.split('=', 2).map { |s| unescape(s) }
|
208
|
+
normalize_params(params, k, v)
|
209
|
+
end
|
210
|
+
params
|
211
|
+
end
|
212
|
+
|
213
|
+
# Stolen from Rack
|
214
|
+
def normalize_params(params, name, v = nil)
|
215
|
+
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
216
|
+
k = $1 || ''
|
217
|
+
after = $' || ''
|
218
|
+
|
219
|
+
return if k.empty?
|
220
|
+
|
221
|
+
if after == ""
|
222
|
+
params[k] = v
|
223
|
+
elsif after == "[]"
|
224
|
+
params[k] ||= []
|
225
|
+
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
226
|
+
params[k] << v
|
227
|
+
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
228
|
+
child_key = $1
|
229
|
+
params[k] ||= []
|
230
|
+
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
231
|
+
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
|
232
|
+
normalize_params(params[k].last, child_key, v)
|
233
|
+
else
|
234
|
+
params[k] << normalize_params({}, child_key, v)
|
235
|
+
end
|
236
|
+
else
|
237
|
+
params[k] ||= {}
|
238
|
+
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
|
239
|
+
params[k] = normalize_params(params[k], after, v)
|
240
|
+
end
|
241
|
+
|
242
|
+
return params
|
243
|
+
end
|
244
|
+
|
245
|
+
# Receives a URL and returns just the path with the query string sorted.
|
246
|
+
def normalize_path(url)
|
247
|
+
(url.path != "" ? url.path : "/") +
|
248
|
+
(url.query ? "?#{sort_query_params(url.query)}" : "")
|
249
|
+
end
|
250
|
+
|
251
|
+
# Recursive hash update
|
252
|
+
def deep_merge!(target, hash)
|
253
|
+
hash.each do |key, value|
|
254
|
+
if Hash === value and Hash === target[key]
|
255
|
+
target[key] = deep_merge(target[key], value)
|
256
|
+
else
|
257
|
+
target[key] = value
|
258
|
+
end
|
259
|
+
end
|
260
|
+
target
|
261
|
+
end
|
262
|
+
|
263
|
+
# Recursive hash merge
|
264
|
+
def deep_merge(source, hash)
|
265
|
+
deep_merge!(source.dup, hash)
|
266
|
+
end
|
267
|
+
|
268
|
+
protected
|
269
|
+
|
270
|
+
def sort_query_params(query)
|
271
|
+
query.split('&').sort.join('&')
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
data/script/test
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Runs the test suite against a local server spawned automatically in a
|
3
|
+
# thread. After tests are done, the server is shut down.
|
4
|
+
#
|
5
|
+
# If filename arguments are given, only those files are run. If arguments given
|
6
|
+
# are not filenames, they are taken as words that filter the list of files to run.
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# $ script/test
|
11
|
+
# $ script/test test/env_test.rb
|
12
|
+
# $ script/test excon typhoeus
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
|
16
|
+
# Enable warnings
|
17
|
+
$VERBOSE = 2
|
18
|
+
|
19
|
+
require 'bundler'
|
20
|
+
Bundler.setup
|
21
|
+
|
22
|
+
host = '127.0.0.1'
|
23
|
+
logfile = 'log/test.log'
|
24
|
+
test_glob = 'test/**/*_test.rb'
|
25
|
+
|
26
|
+
require 'fileutils'
|
27
|
+
FileUtils.mkdir_p 'log'
|
28
|
+
|
29
|
+
# find available port
|
30
|
+
require 'socket'
|
31
|
+
port = begin
|
32
|
+
server = TCPServer.new(host, 0)
|
33
|
+
server.addr[1]
|
34
|
+
ensure
|
35
|
+
server.close if server
|
36
|
+
end
|
37
|
+
|
38
|
+
server = nil
|
39
|
+
|
40
|
+
# start test server in a separate thread
|
41
|
+
thread = Thread.new do
|
42
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
43
|
+
begin
|
44
|
+
require File.expand_path('../../test/live_server', __FILE__)
|
45
|
+
ensure
|
46
|
+
$VERBOSE = old_verbose
|
47
|
+
end
|
48
|
+
require 'webrick'
|
49
|
+
log_io = File.open logfile, 'w'
|
50
|
+
webrick_opts = {
|
51
|
+
:Port => port, :Logger => WEBrick::Log::new(log_io),
|
52
|
+
:AccessLog => [[log_io, "[%{X-Faraday-Adapter}i] %m %U -> %s %b"]]
|
53
|
+
}
|
54
|
+
Rack::Handler::WEBrick.run(Faraday::LiveServer, webrick_opts) {|serv| server = serv }
|
55
|
+
end
|
56
|
+
|
57
|
+
# find files to test
|
58
|
+
test_files = Dir[test_glob]
|
59
|
+
if ARGV.any?
|
60
|
+
all_files, test_files = test_files, []
|
61
|
+
for arg in ARGV
|
62
|
+
re = /(\b|_)#{arg}(\b|_)/
|
63
|
+
test_files.concat all_files.select { |f| f =~ re }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
require 'net/http'
|
68
|
+
conn = Net::HTTP.new host, port
|
69
|
+
conn.open_timeout = conn.read_timeout = 0.05
|
70
|
+
|
71
|
+
# test if test server is accepting requests
|
72
|
+
responsive = lambda { |path|
|
73
|
+
begin
|
74
|
+
res = conn.start { conn.get(path) }
|
75
|
+
res.is_a?(Net::HTTPSuccess)
|
76
|
+
rescue Errno::ECONNREFUSED, Errno::EBADF, Timeout::Error
|
77
|
+
false
|
78
|
+
end
|
79
|
+
}
|
80
|
+
|
81
|
+
require 'timeout'
|
82
|
+
Timeout.timeout 20 do
|
83
|
+
# block until test server is ready
|
84
|
+
thread.join 0.05 until responsive.call('/echo')
|
85
|
+
end
|
86
|
+
|
87
|
+
ENV['LIVE'] = "http://#{host}:#{port}"
|
88
|
+
system 'ruby', '-Ilib:test', '-S', 'testrb', *test_files
|
89
|
+
|
90
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
91
|
+
thread.join
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path('../integration', __FILE__)
|
2
|
+
|
3
|
+
module Adapters
|
4
|
+
class DefaultTest < Faraday::TestCase
|
5
|
+
|
6
|
+
def adapter() :default end
|
7
|
+
|
8
|
+
Integration.apply(self, :NonParallel) do
|
9
|
+
# default stack is not configured with Multipart
|
10
|
+
undef :test_POST_sends_files
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|