avdi-faraday 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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