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.
Files changed (60) hide show
  1. data/Gemfile +27 -0
  2. data/LICENSE.md +20 -0
  3. data/README.md +250 -0
  4. data/Rakefile +87 -0
  5. data/config.ru +6 -0
  6. data/faraday.gemspec +86 -0
  7. data/lib/faraday.rb +276 -0
  8. data/lib/faraday/adapter.rb +71 -0
  9. data/lib/faraday/adapter/em_http.rb +217 -0
  10. data/lib/faraday/adapter/em_synchrony.rb +89 -0
  11. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
  12. data/lib/faraday/adapter/excon.rb +59 -0
  13. data/lib/faraday/adapter/httpclient.rb +92 -0
  14. data/lib/faraday/adapter/net_http.rb +116 -0
  15. data/lib/faraday/adapter/net_http_persistent.rb +37 -0
  16. data/lib/faraday/adapter/patron.rb +65 -0
  17. data/lib/faraday/adapter/rack.rb +57 -0
  18. data/lib/faraday/adapter/test.rb +162 -0
  19. data/lib/faraday/adapter/typhoeus.rb +107 -0
  20. data/lib/faraday/builder.rb +184 -0
  21. data/lib/faraday/connection.rb +468 -0
  22. data/lib/faraday/error.rb +40 -0
  23. data/lib/faraday/middleware.rb +41 -0
  24. data/lib/faraday/request.rb +101 -0
  25. data/lib/faraday/request/authorization.rb +40 -0
  26. data/lib/faraday/request/basic_authentication.rb +13 -0
  27. data/lib/faraday/request/multipart.rb +62 -0
  28. data/lib/faraday/request/retry.rb +67 -0
  29. data/lib/faraday/request/token_authentication.rb +15 -0
  30. data/lib/faraday/request/url_encoded.rb +35 -0
  31. data/lib/faraday/response.rb +99 -0
  32. data/lib/faraday/response/logger.rb +34 -0
  33. data/lib/faraday/response/raise_error.rb +16 -0
  34. data/lib/faraday/upload_io.rb +23 -0
  35. data/lib/faraday/utils.rb +274 -0
  36. data/script/test +91 -0
  37. data/test/adapters/default_test.rb +14 -0
  38. data/test/adapters/em_http_test.rb +19 -0
  39. data/test/adapters/em_synchrony_test.rb +20 -0
  40. data/test/adapters/excon_test.rb +15 -0
  41. data/test/adapters/httpclient_test.rb +16 -0
  42. data/test/adapters/integration.rb +193 -0
  43. data/test/adapters/logger_test.rb +37 -0
  44. data/test/adapters/net_http_persistent_test.rb +11 -0
  45. data/test/adapters/net_http_test.rb +49 -0
  46. data/test/adapters/patron_test.rb +17 -0
  47. data/test/adapters/rack_test.rb +26 -0
  48. data/test/adapters/test_middleware_test.rb +70 -0
  49. data/test/adapters/typhoeus_test.rb +20 -0
  50. data/test/authentication_middleware_test.rb +65 -0
  51. data/test/connection_test.rb +375 -0
  52. data/test/env_test.rb +183 -0
  53. data/test/helper.rb +75 -0
  54. data/test/live_server.rb +57 -0
  55. data/test/middleware/retry_test.rb +62 -0
  56. data/test/middleware_stack_test.rb +203 -0
  57. data/test/middleware_test.rb +12 -0
  58. data/test/request_middleware_test.rb +108 -0
  59. data/test/response_middleware_test.rb +74 -0
  60. 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
@@ -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