faraday 0.6.0 → 0.9.0

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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTING.md +36 -0
  5. data/Gemfile +25 -15
  6. data/{LICENSE → LICENSE.md} +1 -1
  7. data/README.md +187 -137
  8. data/Rakefile +38 -101
  9. data/faraday.gemspec +26 -81
  10. data/lib/faraday/adapter/em_http.rb +237 -0
  11. data/lib/faraday/adapter/em_http_ssl_patch.rb +56 -0
  12. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
  13. data/lib/faraday/adapter/em_synchrony.rb +65 -38
  14. data/lib/faraday/adapter/excon.rb +60 -9
  15. data/lib/faraday/adapter/httpclient.rb +106 -0
  16. data/lib/faraday/adapter/net_http.rb +89 -38
  17. data/lib/faraday/adapter/net_http_persistent.rb +47 -0
  18. data/lib/faraday/adapter/patron.rb +46 -9
  19. data/lib/faraday/adapter/rack.rb +58 -0
  20. data/lib/faraday/adapter/test.rb +68 -28
  21. data/lib/faraday/adapter/typhoeus.rb +94 -16
  22. data/lib/faraday/adapter.rb +33 -25
  23. data/lib/faraday/autoload.rb +85 -0
  24. data/lib/faraday/connection.rb +342 -134
  25. data/lib/faraday/error.rb +44 -29
  26. data/lib/faraday/middleware.rb +18 -10
  27. data/lib/faraday/options.rb +350 -0
  28. data/lib/faraday/parameters.rb +193 -0
  29. data/lib/faraday/rack_builder.rb +212 -0
  30. data/lib/faraday/request/authorization.rb +42 -0
  31. data/lib/faraday/request/basic_authentication.rb +13 -0
  32. data/lib/faraday/request/instrumentation.rb +36 -0
  33. data/lib/faraday/request/multipart.rb +15 -15
  34. data/lib/faraday/request/retry.rb +118 -0
  35. data/lib/faraday/request/token_authentication.rb +15 -0
  36. data/lib/faraday/request/url_encoded.rb +8 -9
  37. data/lib/faraday/request.rb +46 -45
  38. data/lib/faraday/response/logger.rb +4 -4
  39. data/lib/faraday/response/raise_error.rb +8 -3
  40. data/lib/faraday/response.rb +20 -25
  41. data/lib/faraday/upload_io.rb +51 -7
  42. data/lib/faraday/utils.rb +240 -44
  43. data/lib/faraday.rb +218 -38
  44. data/script/console +7 -0
  45. data/script/generate_certs +42 -0
  46. data/script/package +7 -0
  47. data/script/proxy-server +42 -0
  48. data/script/release +17 -0
  49. data/script/server +36 -0
  50. data/script/test +172 -0
  51. data/test/adapters/default_test.rb +14 -0
  52. data/test/adapters/em_http_test.rb +20 -0
  53. data/test/adapters/em_synchrony_test.rb +20 -0
  54. data/test/adapters/excon_test.rb +20 -0
  55. data/test/adapters/httpclient_test.rb +21 -0
  56. data/test/adapters/integration.rb +254 -0
  57. data/test/adapters/logger_test.rb +3 -3
  58. data/test/adapters/net_http_persistent_test.rb +20 -0
  59. data/test/adapters/net_http_test.rb +6 -25
  60. data/test/adapters/patron_test.rb +20 -0
  61. data/test/adapters/rack_test.rb +31 -0
  62. data/test/adapters/test_middleware_test.rb +74 -3
  63. data/test/adapters/typhoeus_test.rb +28 -0
  64. data/test/authentication_middleware_test.rb +65 -0
  65. data/test/composite_read_io_test.rb +111 -0
  66. data/test/connection_test.rb +381 -104
  67. data/test/env_test.rb +116 -40
  68. data/test/helper.rb +61 -25
  69. data/test/live_server.rb +55 -29
  70. data/test/middleware/instrumentation_test.rb +88 -0
  71. data/test/middleware/retry_test.rb +109 -0
  72. data/test/middleware_stack_test.rb +87 -5
  73. data/test/multibyte.txt +1 -0
  74. data/test/options_test.rb +252 -0
  75. data/test/request_middleware_test.rb +78 -21
  76. data/test/response_middleware_test.rb +32 -7
  77. data/test/strawberry.rb +2 -0
  78. data/test/utils_test.rb +58 -0
  79. metadata +116 -117
  80. data/lib/faraday/adapter/action_dispatch.rb +0 -30
  81. data/lib/faraday/builder.rb +0 -137
  82. data/lib/faraday/request/json.rb +0 -31
  83. data/test/adapters/live_test.rb +0 -186
@@ -1,16 +1,21 @@
1
1
  module Faraday
2
2
  class Response::RaiseError < Response::Middleware
3
+ ClientErrorStatuses = 400...600
4
+
3
5
  def on_complete(env)
4
6
  case env[:status]
5
7
  when 404
6
8
  raise Faraday::Error::ResourceNotFound, response_values(env)
7
- when 400...600
9
+ when 407
10
+ # mimic the behavior that we get with proxy requests with HTTPS
11
+ raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
12
+ when ClientErrorStatuses
8
13
  raise Faraday::Error::ClientError, response_values(env)
9
14
  end
10
15
  end
11
-
16
+
12
17
  def response_values(env)
13
- {:status => env[:status], :headers => env[:response_headers], :body => env[:body]}
18
+ {:status => env.status, :headers => env.response_headers, :body => env.body}
14
19
  end
15
20
  end
16
21
  end
@@ -5,50 +5,45 @@ module Faraday
5
5
  # Used for simple response middleware.
6
6
  class Middleware < Faraday::Middleware
7
7
  def call(env)
8
- @app.call(env).on_complete do |env|
9
- on_complete(env)
8
+ @app.call(env).on_complete do |environment|
9
+ on_complete(environment)
10
10
  end
11
11
  end
12
12
 
13
13
  # Override this to modify the environment after the response has finished.
14
14
  # Calls the `parse` method if defined
15
15
  def on_complete(env)
16
- if respond_to? :parse
17
- env[:body] = parse(env[:body])
18
- end
16
+ env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
19
17
  end
20
18
  end
21
19
 
22
20
  extend Forwardable
23
- extend AutoloadHelper
24
-
25
- autoload_all 'faraday/response',
26
- :RaiseError => 'raise_error',
27
- :Logger => 'logger'
21
+ extend MiddlewareRegistry
28
22
 
29
- register_lookup_modules \
30
- :raise_error => :RaiseError,
31
- :logger => :Logger
23
+ register_middleware File.expand_path('../response', __FILE__),
24
+ :raise_error => [:RaiseError, 'raise_error'],
25
+ :logger => [:Logger, 'logger']
32
26
 
33
27
  def initialize(env = nil)
34
- @env = env
28
+ @env = Env.from(env) if env
35
29
  @on_complete_callbacks = []
36
30
  end
37
31
 
38
32
  attr_reader :env
39
- alias_method :to_hash, :env
33
+
34
+ def_delegators :env, :to_hash
40
35
 
41
36
  def status
42
- finished? ? env[:status] : nil
37
+ finished? ? env.status : nil
43
38
  end
44
39
 
45
40
  def headers
46
- finished? ? env[:response_headers] : {}
41
+ finished? ? env.response_headers : {}
47
42
  end
48
43
  def_delegator :headers, :[]
49
44
 
50
45
  def body
51
- finished? ? env[:body] : nil
46
+ finished? ? env.body : nil
52
47
  end
53
48
 
54
49
  def finished?
@@ -59,39 +54,39 @@ module Faraday
59
54
  if not finished?
60
55
  @on_complete_callbacks << Proc.new
61
56
  else
62
- yield env
57
+ yield(env)
63
58
  end
64
59
  return self
65
60
  end
66
61
 
67
62
  def finish(env)
68
63
  raise "response already finished" if finished?
69
- @env = env
64
+ @env = Env.from(env)
70
65
  @on_complete_callbacks.each { |callback| callback.call(env) }
71
66
  return self
72
67
  end
73
68
 
74
69
  def success?
75
- status == 200
70
+ finished? && env.success?
76
71
  end
77
72
 
78
73
  # because @on_complete_callbacks cannot be marshalled
79
74
  def marshal_dump
80
75
  !finished? ? nil : {
81
- :status => @env[:status], :body => @env[:body],
82
- :response_headers => @env[:response_headers]
76
+ :status => @env.status, :body => @env.body,
77
+ :response_headers => @env.response_headers
83
78
  }
84
79
  end
85
80
 
86
81
  def marshal_load(env)
87
- @env = env
82
+ @env = Env.from(env)
88
83
  end
89
84
 
90
85
  # Expand the env with more properties, without overriding existing ones.
91
86
  # Useful for applying request params after restoring a marshalled Response.
92
87
  def apply_request(request_env)
93
88
  raise "response didn't finish yet" unless finished?
94
- @env = request_env.merge @env
89
+ @env = Env.from(request_env).update(@env)
95
90
  return self
96
91
  end
97
92
  end
@@ -8,13 +8,57 @@ rescue LoadError
8
8
  end
9
9
 
10
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)
11
+ # Similar but not compatible with ::CompositeReadIO provided by multipart-post.
12
+ class CompositeReadIO
13
+ def initialize(*parts)
14
+ @parts = parts.flatten
15
+ @ios = @parts.map { |part| part.to_io }
16
+ @index = 0
17
+ end
18
+
19
+ def length
20
+ @parts.inject(0) { |sum, part| sum + part.length }
21
+ end
22
+
23
+ def rewind
24
+ @ios.each { |io| io.rewind }
25
+ @index = 0
26
+ end
27
+
28
+ # Read from IOs in order until `length` bytes have been received.
29
+ def read(length = nil, outbuf = nil)
30
+ got_result = false
31
+ outbuf = outbuf ? outbuf.replace("") : ""
32
+
33
+ while io = current_io
34
+ if result = io.read(length)
35
+ got_result ||= !result.nil?
36
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
37
+ outbuf << result
38
+ length -= result.length if length
39
+ break if length == 0
40
+ end
41
+ advance_io
42
+ end
43
+ (!got_result && length) ? nil : outbuf
44
+ end
45
+
46
+ def close
47
+ @ios.each { |io| io.close }
48
+ end
49
+
50
+ def ensure_open_and_readable
51
+ # Rubinius compatibility
52
+ end
53
+
54
+ private
55
+
56
+ def current_io
57
+ @ios[@index]
58
+ end
59
+
60
+ def advance_io
61
+ @index += 1
18
62
  end
19
63
  end
20
64
 
data/lib/faraday/utils.rb CHANGED
@@ -1,97 +1,293 @@
1
- require 'rack/utils'
1
+ require 'thread'
2
+ Faraday.require_libs 'parameters'
3
+
2
4
  module Faraday
3
5
  module Utils
4
- include Rack::Utils
5
-
6
- extend Rack::Utils
7
6
  extend self
8
7
 
9
- class Headers < HeaderHash
8
+ # Adapted from Rack::Utils::HeaderHash
9
+ class Headers < ::Hash
10
+ def self.from(value)
11
+ new(value)
12
+ end
13
+
14
+ def initialize(hash = nil)
15
+ super()
16
+ @names = {}
17
+ self.update(hash || {})
18
+ end
19
+
20
+ # need to synchronize concurrent writes to the shared KeyMap
21
+ keymap_mutex = Mutex.new
22
+
10
23
  # symbol -> string mapper + cache
11
24
  KeyMap = Hash.new do |map, key|
12
- map[key] = if key.respond_to?(:to_str) then key
25
+ value = if key.respond_to?(:to_str)
26
+ key
13
27
  else
14
28
  key.to_s.split('_'). # :user_agent => %w(user agent)
15
29
  each { |w| w.capitalize! }. # => %w(User Agent)
16
30
  join('-') # => "User-Agent"
17
31
  end
32
+ keymap_mutex.synchronize { map[key] = value }
18
33
  end
19
34
  KeyMap[:etag] = "ETag"
20
-
35
+
21
36
  def [](k)
22
- super(KeyMap[k])
37
+ k = KeyMap[k]
38
+ super(k) || super(@names[k.downcase])
23
39
  end
24
40
 
25
41
  def []=(k, v)
42
+ k = KeyMap[k]
43
+ k = (@names[k.downcase] ||= k)
26
44
  # join multiple values with a comma
27
45
  v = v.to_ary.join(', ') if v.respond_to? :to_ary
28
- super(KeyMap[k], v)
46
+ super(k, v)
47
+ end
48
+
49
+ def fetch(k, *args, &block)
50
+ k = KeyMap[k]
51
+ key = @names.fetch(k.downcase, k)
52
+ super(key, *args, &block)
53
+ end
54
+
55
+ def delete(k)
56
+ k = KeyMap[k]
57
+ if k = @names[k.downcase]
58
+ @names.delete k.downcase
59
+ super(k)
60
+ end
61
+ end
62
+
63
+ def include?(k)
64
+ @names.include? k.downcase
65
+ end
66
+
67
+ alias_method :has_key?, :include?
68
+ alias_method :member?, :include?
69
+ alias_method :key?, :include?
70
+
71
+ def merge!(other)
72
+ other.each { |k, v| self[k] = v }
73
+ self
29
74
  end
30
-
31
75
  alias_method :update, :merge!
32
-
76
+
77
+ def merge(other)
78
+ hash = dup
79
+ hash.merge! other
80
+ end
81
+
82
+ def replace(other)
83
+ clear
84
+ self.update other
85
+ self
86
+ end
87
+
88
+ def to_hash() ::Hash.new.update(self) end
89
+
33
90
  def parse(header_string)
34
91
  return unless header_string && !header_string.empty?
35
92
  header_string.split(/\r\n/).
36
93
  tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
37
- map { |h| h.split(/:\s+/, 2) }.reject { |(k, v)| k.nil? }. # split key and value, ignore blank lines
94
+ map { |h| h.split(/:\s+/, 2) }.reject { |p| p[0].nil? }. # split key and value, ignore blank lines
38
95
  each { |key, value|
39
96
  # join multiple values with a comma
40
- if self[key] then self[key] << ', ' << value
41
- else self[key] = value
97
+ if self[key]
98
+ self[key] << ', ' << value
99
+ else
100
+ self[key] = value
42
101
  end
43
102
  }
44
103
  end
45
104
  end
46
105
 
47
- # Make Rack::Utils build_query method public.
48
- public :build_query
106
+ # hash with stringified keys
107
+ class ParamsHash < Hash
108
+ def [](key)
109
+ super(convert_key(key))
110
+ end
49
111
 
50
- # Override Rack's version since it doesn't handle non-String values
51
- def build_nested_query(value, prefix = nil)
52
- case value
53
- when Array
54
- value.map { |v| build_nested_query(v, "#{prefix}[]") }.join("&")
55
- when Hash
56
- value.map { |k, v|
57
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
58
- }.join("&")
59
- when NilClass
60
- prefix
61
- else
62
- raise ArgumentError, "value must be a Hash" if prefix.nil?
63
- "#{prefix}=#{escape(value)}"
112
+ def []=(key, value)
113
+ super(convert_key(key), value)
64
114
  end
115
+
116
+ def delete(key)
117
+ super(convert_key(key))
118
+ end
119
+
120
+ def include?(key)
121
+ super(convert_key(key))
122
+ end
123
+
124
+ alias_method :has_key?, :include?
125
+ alias_method :member?, :include?
126
+ alias_method :key?, :include?
127
+
128
+ def update(params)
129
+ params.each do |key, value|
130
+ self[key] = value
131
+ end
132
+ self
133
+ end
134
+ alias_method :merge!, :update
135
+
136
+ def merge(params)
137
+ dup.update(params)
138
+ end
139
+
140
+ def replace(other)
141
+ clear
142
+ update(other)
143
+ end
144
+
145
+ def merge_query(query, encoder = nil)
146
+ if query && !query.empty?
147
+ update((encoder || Utils.default_params_encoder).decode(query))
148
+ end
149
+ self
150
+ end
151
+
152
+ def to_query(encoder = nil)
153
+ (encoder || Utils.default_params_encoder).encode(self)
154
+ end
155
+
156
+ private
157
+
158
+ def convert_key(key)
159
+ key.to_s
160
+ end
161
+ end
162
+
163
+ def build_query(params)
164
+ FlatParamsEncoder.encode(params)
65
165
  end
66
166
 
67
- # Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
68
- # as spaces.
167
+ def build_nested_query(params)
168
+ NestedParamsEncoder.encode(params)
169
+ end
170
+
171
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
172
+
69
173
  def escape(s)
70
- s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
71
- '%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
174
+ s.to_s.gsub(ESCAPE_RE) {|match|
175
+ '%' + match.unpack('H2' * match.bytesize).join('%').upcase
176
+ }.tr(' ', '+')
177
+ end
178
+
179
+ def unescape(s) CGI.unescape s.to_s end
180
+
181
+ DEFAULT_SEP = /[&;] */n
182
+
183
+ # Adapted from Rack
184
+ def parse_query(query)
185
+ FlatParamsEncoder.decode(query)
186
+ end
187
+
188
+ def parse_nested_query(query)
189
+ NestedParamsEncoder.decode(query)
190
+ end
191
+
192
+ def default_params_encoder
193
+ @default_params_encoder ||= NestedParamsEncoder
194
+ end
195
+
196
+ class << self
197
+ attr_writer :default_params_encoder
198
+ end
199
+
200
+ # Stolen from Rack
201
+ def normalize_params(params, name, v = nil)
202
+ name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
203
+ k = $1 || ''
204
+ after = $' || ''
205
+
206
+ return if k.empty?
207
+
208
+ if after == ""
209
+ if params[k]
210
+ params[k] = Array[params[k]] unless params[k].kind_of?(Array)
211
+ params[k] << v
212
+ else
213
+ params[k] = v
214
+ end
215
+ elsif after == "[]"
216
+ params[k] ||= []
217
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
218
+ params[k] << v
219
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
220
+ child_key = $1
221
+ params[k] ||= []
222
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
223
+ if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
224
+ normalize_params(params[k].last, child_key, v)
225
+ else
226
+ params[k] << normalize_params({}, child_key, v)
227
+ end
228
+ else
229
+ params[k] ||= {}
230
+ raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
231
+ params[k] = normalize_params(params[k], after, v)
232
+ end
233
+
234
+ return params
235
+ end
236
+
237
+ # Normalize URI() behavior across Ruby versions
238
+ #
239
+ # url - A String or URI.
240
+ #
241
+ # Returns a parsed URI.
242
+ def URI(url)
243
+ if url.respond_to?(:host)
244
+ url
245
+ elsif url.respond_to?(:to_str)
246
+ default_uri_parser.call(url)
247
+ else
248
+ raise ArgumentError, "bad argument (expected URI object or URI string)"
72
249
  end
73
250
  end
74
251
 
75
- # Turns param keys into strings
76
- def merge_params(existing_params, new_params)
77
- new_params.each do |key, value|
78
- existing_params[key.to_s] = value
252
+ def default_uri_parser
253
+ @default_uri_parser ||= begin
254
+ require 'uri'
255
+ Kernel.method(:URI)
79
256
  end
80
257
  end
81
258
 
82
- # Turns headers keys and values into strings
83
- def merge_headers(existing_headers, new_headers)
84
- new_headers.each do |key, value|
85
- existing_headers[key] = value.to_s
259
+ def default_uri_parser=(parser)
260
+ @default_uri_parser = if parser.respond_to?(:call) || parser.nil?
261
+ parser
262
+ else
263
+ parser.method(:parse)
86
264
  end
87
265
  end
88
266
 
89
- # Receives a URL and returns just the path with the query string sorted.
267
+ # Receives a String or URI and returns just the path with the query string sorted.
90
268
  def normalize_path(url)
91
- (url.path != "" ? url.path : "/") +
269
+ url = URI(url)
270
+ (url.path.start_with?('/') ? url.path : '/' + url.path) +
92
271
  (url.query ? "?#{sort_query_params(url.query)}" : "")
93
272
  end
94
273
 
274
+ # Recursive hash update
275
+ def deep_merge!(target, hash)
276
+ hash.each do |key, value|
277
+ if Hash === value and Hash === target[key]
278
+ target[key] = deep_merge(target[key], value)
279
+ else
280
+ target[key] = value
281
+ end
282
+ end
283
+ target
284
+ end
285
+
286
+ # Recursive hash merge
287
+ def deep_merge(source, hash)
288
+ deep_merge!(source.dup, hash)
289
+ end
290
+
95
291
  protected
96
292
 
97
293
  def sort_query_params(query)