agiley-faraday_middleware 0.8.3.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +2 -2
  2. data/.rspec +2 -1
  3. data/.travis.yml +6 -3
  4. data/Gemfile +5 -1
  5. data/Rakefile +16 -4
  6. data/faraday_middleware.gemspec +1 -1
  7. data/lib/faraday_middleware.rb +17 -13
  8. data/lib/faraday_middleware/instrumentation.rb +2 -2
  9. data/lib/faraday_middleware/rack_compatible.rb +14 -9
  10. data/lib/faraday_middleware/request/encode_json.rb +4 -2
  11. data/lib/faraday_middleware/request/method_override.rb +51 -0
  12. data/lib/faraday_middleware/request/oauth.rb +28 -5
  13. data/lib/faraday_middleware/response/caching.rb +7 -2
  14. data/lib/faraday_middleware/response/chunked.rb +29 -0
  15. data/lib/faraday_middleware/response/follow_redirects.rb +100 -11
  16. data/lib/faraday_middleware/response/mashify.rb +11 -2
  17. data/lib/faraday_middleware/response/parse_dates.rb +39 -0
  18. data/lib/faraday_middleware/response/parse_json.rb +17 -5
  19. data/lib/faraday_middleware/response/parse_marshal.rb +2 -2
  20. data/lib/faraday_middleware/response/parse_xml.rb +3 -2
  21. data/lib/faraday_middleware/response/parse_yaml.rb +3 -1
  22. data/lib/faraday_middleware/response/rashify.rb +2 -0
  23. data/lib/faraday_middleware/response_middleware.rb +2 -2
  24. data/lib/faraday_middleware/version.rb +1 -1
  25. data/spec/caching_test.rb +37 -4
  26. data/spec/chunked_spec.rb +78 -0
  27. data/spec/encode_json_spec.rb +13 -13
  28. data/spec/follow_redirects_spec.rb +203 -18
  29. data/spec/helper.rb +15 -1
  30. data/spec/mashify_spec.rb +46 -26
  31. data/spec/method_override_spec.rb +92 -0
  32. data/spec/oauth2_spec.rb +18 -18
  33. data/spec/oauth_spec.rb +67 -17
  34. data/spec/parse_dates_spec.rb +39 -0
  35. data/spec/parse_json_spec.rb +37 -19
  36. data/spec/parse_marshal_spec.rb +3 -3
  37. data/spec/parse_xml_spec.rb +13 -13
  38. data/spec/parse_yaml_spec.rb +10 -10
  39. data/spec/rashify_spec.rb +26 -23
  40. metadata +78 -21
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  # Mac OS X
2
2
  .DS_Store
3
3
 
4
+ # RVM
4
5
  .rvmrc
5
6
 
6
7
  # TextMate
@@ -21,13 +22,12 @@ rdoc
21
22
  doc
22
23
  log
23
24
  .yardoc
25
+ tmp
24
26
 
25
27
  # Bundler
26
- *.gem
27
28
  .bundle
28
29
  Gemfile.lock
29
30
  pkg
30
- *.gem
31
31
 
32
32
  # Rubinius
33
33
  *.rbc
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
- --backtrace
2
+ --fail-fast
3
+ --order random
@@ -1,7 +1,10 @@
1
+ language: ruby
1
2
  rvm:
3
+ - rbx-18mode
4
+ - rbx-19mode
5
+ - jruby-18mode
6
+ - jruby-19mode
2
7
  - 1.8.7
3
8
  - 1.9.2
4
9
  - 1.9.3
5
- - jruby
6
- - rbx
7
- - ree
10
+ - ruby-head
data/Gemfile CHANGED
@@ -1,6 +1,10 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'simplecov' unless ENV['CI']
3
+ platforms :mri_19 do
4
+ gem 'simplecov'
5
+ gem 'cane', '~> 2.2.2'
6
+ end
7
+
4
8
  gem 'json', :platforms => [:ruby_18, :jruby]
5
9
  gem 'jruby-openssl', '~> 0.7', :platforms => :jruby
6
10
 
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- #!/usr/bin/env rake
2
-
3
- task :default => [:enable_coverage, :spec, :test]
1
+ if defined? RUBY_ENGINE and 'ruby' == RUBY_ENGINE and RUBY_VERSION.index('1.9') == 0
2
+ task :default => [:enable_coverage, :spec, :test, :quality]
3
+ else
4
+ task :default => [:spec, :test]
5
+ end
4
6
 
5
7
  require 'bundler'
6
8
  Bundler::GemHelper.install_tasks
@@ -9,9 +11,19 @@ require 'rspec/core/rake_task'
9
11
  RSpec::Core::RakeTask.new(:spec)
10
12
 
11
13
  task :enable_coverage do
12
- ENV['COVERAGE'] = 'yes' unless ENV['CI']
14
+ ENV['COVERAGE'] = 'yes'
13
15
  end
14
16
 
17
+ desc %(Run Test::Unit tests)
15
18
  task :test do
16
19
  sh 'ruby', '-Ilib', 'spec/caching_test.rb'
17
20
  end
21
+
22
+ desc %(Check code quality metrics with Cane)
23
+ task :quality do
24
+ sh 'cane',
25
+ '--abc-max=15',
26
+ '--style-measure=110',
27
+ '--gte=coverage/covered_percent,99',
28
+ '--max-violations=0'
29
+ end
@@ -21,4 +21,4 @@ Gem::Specification.new do |gem|
21
21
  gem.summary = gem.description
22
22
  gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
23
  gem.version = FaradayMiddleware::VERSION
24
- end
24
+ end
@@ -4,16 +4,18 @@ module FaradayMiddleware
4
4
  autoload :OAuth, 'faraday_middleware/request/oauth'
5
5
  autoload :OAuth2, 'faraday_middleware/request/oauth2'
6
6
  autoload :EncodeJson, 'faraday_middleware/request/encode_json'
7
+ autoload :MethodOverride, 'faraday_middleware/request/method_override'
7
8
  autoload :Mashify, 'faraday_middleware/response/mashify'
8
9
  autoload :Rashify, 'faraday_middleware/response/rashify'
9
10
  autoload :ParseJson, 'faraday_middleware/response/parse_json'
10
11
  autoload :ParseXml, 'faraday_middleware/response/parse_xml'
11
12
  autoload :ParseNokogiriXml, 'faraday_middleware/response/parse_nokogiri_xml'
12
13
  autoload :ParseNokogiriHtml, 'faraday_middleware/response/parse_nokogiri_html'
13
- autoload :ForceUtf, 'faraday_middleware/response/force_utf'
14
14
  autoload :ParseMarshal, 'faraday_middleware/response/parse_marshal'
15
15
  autoload :ParseYaml, 'faraday_middleware/response/parse_yaml'
16
+ autoload :ParseDates, 'faraday_middleware/response/parse_dates'
16
17
  autoload :Caching, 'faraday_middleware/response/caching'
18
+ autoload :Chunked, 'faraday_middleware/response/chunked'
17
19
  autoload :RackCompatible, 'faraday_middleware/rack_compatible'
18
20
  autoload :FollowRedirects, 'faraday_middleware/response/follow_redirects'
19
21
  autoload :Instrumentation, 'faraday_middleware/instrumentation'
@@ -22,25 +24,27 @@ module FaradayMiddleware
22
24
  Faraday.register_middleware :request,
23
25
  :oauth => lambda { OAuth },
24
26
  :oauth2 => lambda { OAuth2 },
25
- :json => lambda { EncodeJson }
27
+ :json => lambda { EncodeJson },
28
+ :method_override => lambda { MethodOverride }
26
29
 
27
30
  Faraday.register_middleware :response,
28
- :mashify => lambda { Mashify },
29
- :rashify => lambda { Rashify },
30
- :json => lambda { ParseJson },
31
- :json_fix => lambda { ParseJson::MimeTypeFix },
32
- :xml => lambda { ParseXml },
31
+ :mashify => lambda { Mashify },
32
+ :rashify => lambda { Rashify },
33
+ :json => lambda { ParseJson },
34
+ :json_fix => lambda { ParseJson::MimeTypeFix },
35
+ :xml => lambda { ParseXml },
33
36
  :nokogiri_xml => lambda { ParseNokogiriXml },
34
37
  :nokogiri_html => lambda { ParseNokogiriHtml },
35
- :force_utf => lambda { ForceUtf },
36
- :marshal => lambda { ParseMarshal },
37
- :yaml => lambda { ParseYaml },
38
- :caching => lambda { Caching },
39
- :follow_redirects => lambda { FollowRedirects }
38
+ :marshal => lambda { ParseMarshal },
39
+ :yaml => lambda { ParseYaml },
40
+ :dates => lambda { ParseDates },
41
+ :caching => lambda { Caching },
42
+ :follow_redirects => lambda { FollowRedirects },
43
+ :chunked => lambda { Chunked }
40
44
 
41
45
  Faraday.register_middleware \
42
46
  :instrumentation => lambda { Instrumentation }
43
47
  end
44
48
  end
45
49
 
46
- require 'faraday_middleware/backwards_compatibility'
50
+ require 'faraday_middleware/backwards_compatibility'
@@ -7,10 +7,10 @@ module FaradayMiddleware
7
7
  #
8
8
  # Examples
9
9
  #
10
- # ActiveSupport::Notifications.subscribe('request.faraday') do |name, start_time, end_time, _, env|
10
+ # ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
11
11
  # url = env[:url]
12
12
  # http_method = env[:method].to_s.upcase
13
- # duration = end_time - start_time
13
+ # duration = ends - starts
14
14
  # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
15
15
  # end
16
16
  class Instrumentation < Faraday::Middleware
@@ -26,11 +26,7 @@ module FaradayMiddleware
26
26
 
27
27
  # faraday to rack-compatible
28
28
  def prepare_env(env)
29
- env[:request_headers].each do |name, value|
30
- name = name.upcase.tr('-', '_')
31
- name = "HTTP_#{name}" unless NonPrefixedHeaders.include? name
32
- env[name] = value
33
- end
29
+ headers_to_rack(env)
34
30
 
35
31
  url = env[:url]
36
32
  env['rack.url_scheme'] = url.scheme
@@ -44,6 +40,14 @@ module FaradayMiddleware
44
40
  env
45
41
  end
46
42
 
43
+ def headers_to_rack(env)
44
+ env[:request_headers].each do |name, value|
45
+ name = name.upcase.tr('-', '_')
46
+ name = "HTTP_#{name}" unless NonPrefixedHeaders.include? name
47
+ env[name] = value
48
+ end
49
+ end
50
+
47
51
  # rack to faraday-compatible
48
52
  def restore_env(env)
49
53
  headers = env[:request_headers]
@@ -63,13 +67,14 @@ module FaradayMiddleware
63
67
 
64
68
  def finalize_response(env, rack_response)
65
69
  status, headers, body = rack_response
66
- body = body.inject('') { |str, part| str << part }
70
+ body = body.inject() { |str, part| str << part }
67
71
  headers = Faraday::Utils::Headers.new(headers) unless Faraday::Utils::Headers === headers
68
72
 
69
- response_env = { :status => status, :body => body, :response_headers => headers }
73
+ env.update :status => status.to_i,
74
+ :body => body,
75
+ :response_headers => headers
70
76
 
71
- env[:response] ||= Faraday::Response.new({})
72
- env[:response].env.update(response_env)
77
+ env[:response] ||= Faraday::Response.new(env)
73
78
  env[:response]
74
79
  end
75
80
  end
@@ -12,7 +12,9 @@ module FaradayMiddleware
12
12
  CONTENT_TYPE = 'Content-Type'.freeze
13
13
  MIME_TYPE = 'application/json'.freeze
14
14
 
15
- dependency 'json'
15
+ dependency do
16
+ require 'json' unless defined?(::JSON)
17
+ end
16
18
 
17
19
  def call(env)
18
20
  match_content_type(env) do |data|
@@ -22,7 +24,7 @@ module FaradayMiddleware
22
24
  end
23
25
 
24
26
  def encode(data)
25
- JSON.dump data
27
+ ::JSON.dump data
26
28
  end
27
29
 
28
30
  def match_content_type(env)
@@ -0,0 +1,51 @@
1
+ require 'faraday'
2
+
3
+ module FaradayMiddleware
4
+ # Public: Writes the original HTTP method to "X-Http-Method-Override" header
5
+ # and sends the request as POST.
6
+ #
7
+ # This can be used to work around technical issues with making non-POST
8
+ # requests, e.g. faulty HTTP client or server router.
9
+ #
10
+ # This header is recognized in Rack apps by default, courtesy of the
11
+ # Rack::MethodOverride module. See
12
+ # http://rack.rubyforge.org/doc/classes/Rack/MethodOverride.html
13
+ class MethodOverride < Faraday::Middleware
14
+
15
+ HEADER = "X-Http-Method-Override".freeze
16
+
17
+ # Public: Initialize the middleware.
18
+ #
19
+ # app - the Faraday app to wrap
20
+ # options - (optional)
21
+ # :rewrite - Array of HTTP methods to rewrite
22
+ # (default: all but GET and POST)
23
+ def initialize(app, options = nil)
24
+ super(app)
25
+ @methods = options && options.fetch(:rewrite).map { |method|
26
+ method = method.downcase if method.respond_to? :downcase
27
+ method.to_sym
28
+ }
29
+ end
30
+
31
+ def call(env)
32
+ method = env[:method]
33
+ rewrite_request(env, method) if rewrite_request?(method)
34
+ @app.call(env)
35
+ end
36
+
37
+ def rewrite_request?(method)
38
+ if @methods.nil? or @methods.empty?
39
+ method != :get and method != :post
40
+ else
41
+ @methods.include? method
42
+ end
43
+ end
44
+
45
+ # Internal: Write the original HTTP method to header, change method to POST.
46
+ def rewrite_request(env, original_method)
47
+ env[:request_headers][HEADER] = original_method.to_s.upcase
48
+ env[:method] = :post
49
+ end
50
+ end
51
+ end
@@ -1,4 +1,5 @@
1
1
  require 'faraday'
2
+ require 'forwardable'
2
3
 
3
4
  module FaradayMiddleware
4
5
  # Public: Uses the simple_oauth library to sign requests according the
@@ -11,14 +12,25 @@ module FaradayMiddleware
11
12
  # The signature is added to the "Authorization" HTTP request header. If the
12
13
  # value for this header already exists, it is not overriden.
13
14
  #
14
- # For requests that have parameters in the body, such as POST, this
15
- # middleware expects them to be in Hash form, i.e. not encoded to string.
16
- # This means this middleware has to be positioned on the stack before any
17
- # encoding middleware such as UrlEncoded.
15
+ # If no Content-Type header is specified, this middleware assumes that
16
+ # request body parameters should be included while signing the request.
17
+ # Otherwise, it only includes them if the Content-Type is
18
+ # "application/x-www-form-urlencoded", as per OAuth 1.0.
19
+ #
20
+ # For better performance while signing requests, this middleware should be
21
+ # positioned before UrlEncoded middleware on the stack, but after any other
22
+ # body-encoding middleware (such as EncodeJson).
18
23
  class OAuth < Faraday::Middleware
19
24
  dependency 'simple_oauth'
20
25
 
21
26
  AUTH_HEADER = 'Authorization'.freeze
27
+ CONTENT_TYPE = 'Content-Type'.freeze
28
+ TYPE_URLENCODED = 'application/x-www-form-urlencoded'.freeze
29
+
30
+ extend Forwardable
31
+ parser_method = :parse_nested_query
32
+ parser_module = ::Faraday::Utils.respond_to?(parser_method) ? 'Faraday::Utils' : 'Rack::Utils'
33
+ def_delegator parser_module, parser_method
22
34
 
23
35
  def initialize(app, options)
24
36
  super(app)
@@ -50,7 +62,18 @@ module FaradayMiddleware
50
62
  end
51
63
 
52
64
  def body_params(env)
53
- env[:body] || {}
65
+ if include_body_params?(env)
66
+ if env[:body].respond_to?(:to_str)
67
+ parse_nested_query env[:body]
68
+ else
69
+ env[:body]
70
+ end
71
+ end || {}
72
+ end
73
+
74
+ def include_body_params?(env)
75
+ # see RFC 5489, section 3.4.1.3.1 for details
76
+ !(type = env[:request_headers][CONTENT_TYPE]) or type == TYPE_URLENCODED
54
77
  end
55
78
 
56
79
  def signature_params(params)
@@ -68,8 +68,13 @@ module FaradayMiddleware
68
68
  end
69
69
 
70
70
  def finalize_response(response, env)
71
- response = env[:response] = response.dup if response.frozen?
72
- response.apply_request env unless response.env[:method]
71
+ response = response.dup if response.frozen?
72
+ env[:response] = response
73
+ unless env[:response_headers]
74
+ env.update response.env
75
+ # FIXME: omg hax
76
+ response.instance_variable_set('@env', env)
77
+ end
73
78
  response
74
79
  end
75
80
  end
@@ -0,0 +1,29 @@
1
+ require 'faraday_middleware/response_middleware'
2
+
3
+ module FaradayMiddleware
4
+ # Public: Parse a Transfer-Encoding: Chunked response to just the original data
5
+ class Chunked < FaradayMiddleware::ResponseMiddleware
6
+ TRANSFER_ENCODING = 'transfer-encoding'.freeze
7
+
8
+ define_parser do |raw_body|
9
+ decoded_body = []
10
+ until raw_body.empty?
11
+ chunk_len, raw_body = raw_body.split("\r\n", 2)
12
+ chunk_len = chunk_len.split(';',2).first.hex
13
+ break if chunk_len == 0
14
+ decoded_body << raw_body[0, chunk_len]
15
+ # The 2 is to strip the extra CRLF at the end of the chunk
16
+ raw_body = raw_body[chunk_len + 2, raw_body.length - chunk_len - 2]
17
+ end
18
+ decoded_body.join('')
19
+ end
20
+
21
+ def parse_response?(env)
22
+ super and chunked_encoding?(env[:response_headers])
23
+ end
24
+
25
+ def chunked_encoding?(headers)
26
+ encoding = headers[TRANSFER_ENCODING] and encoding.split(',').include?('chunked')
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,8 @@
1
1
  require 'faraday'
2
+ require 'set'
2
3
 
3
4
  module FaradayMiddleware
5
+ # Public: Exception thrown when the maximum amount of requests is exceeded.
4
6
  class RedirectLimitReached < Faraday::Error::ClientError
5
7
  attr_reader :response
6
8
 
@@ -10,10 +12,34 @@ module FaradayMiddleware
10
12
  end
11
13
  end
12
14
 
13
- # Public: Follow HTTP 30x redirects.
15
+ # Public: Follow HTTP 301, 302, 303, and 307 redirects for GET, PATCH, POST,
16
+ # PUT, and DELETE requests.
17
+ #
18
+ # This middleware does not follow the HTTP specification for HTTP 302, by
19
+ # default, in that it follows the improper implementation used by most major
20
+ # web browsers which forces the redirected request to become a GET request
21
+ # regardless of the original request method.
22
+ #
23
+ # For HTTP 301, 302, and 303, the original request is transformed into a
24
+ # GET request to the response Location, by default. However, with standards
25
+ # compliance enabled, a 302 will instead act in accordance with the HTTP
26
+ # specification, which will replay the original request to the received
27
+ # Location, just as with a 307.
28
+ #
29
+ # For HTTP 307, the original request is replayed to the response Location,
30
+ # including original HTTP request method (GET, POST, PUT, DELETE, PATCH),
31
+ # original headers, and original body.
32
+ #
33
+ # This middleware currently only works with synchronous requests; in other
34
+ # words, it doesn't support parallelism.
14
35
  class FollowRedirects < Faraday::Middleware
15
- # TODO: 307 & standards-compliant 302
16
- REDIRECTS = [301, 302, 303]
36
+ # HTTP methods for which 30x redirects can be followed
37
+ ALLOWED_METHODS = Set.new [:head, :options, :get, :post, :put, :patch, :delete]
38
+ # HTTP redirect status codes that this middleware implements
39
+ REDIRECT_CODES = Set.new [301, 302, 303, 307]
40
+ # Keys in env hash which will get cleared between requests
41
+ ENV_TO_CLEAR = Set.new [:status, :response, :response_headers]
42
+
17
43
  # Default value for max redirects followed
18
44
  FOLLOW_LIMIT = 3
19
45
 
@@ -21,33 +47,96 @@ module FaradayMiddleware
21
47
  #
22
48
  # options - An options Hash (default: {}):
23
49
  # limit - A Numeric redirect limit (default: 3)
50
+ # standards_compliant - A Boolean indicating whether to respect
51
+ # the HTTP spec when following 302
52
+ # (default: false)
53
+ # cookie - Use either an array of strings
54
+ # (e.g. ['cookie1', 'cookie2']) to choose kept cookies
55
+ # or :all to keep all cookies.
24
56
  def initialize(app, options = {})
25
57
  super(app)
26
58
  @options = options
59
+
60
+ @replay_request_codes = Set.new [307]
61
+ @replay_request_codes << 302 if standards_compliant?
27
62
  end
28
63
 
29
64
  def call(env)
30
- process_response(@app.call(env), follow_limit)
65
+ perform_with_redirection(env, follow_limit)
66
+ end
67
+
68
+ private
69
+
70
+ def transform_into_get?(response)
71
+ return false if [:head, :options].include? response.env[:method]
72
+ # Never convert head or options to a get. That would just be silly.
73
+
74
+ !@replay_request_codes.include? response.status
31
75
  end
32
76
 
33
- def process_response(response, follows)
77
+ def perform_with_redirection(env, follows)
78
+ request_body = env[:body]
79
+ response = @app.call(env)
80
+
34
81
  response.on_complete do |env|
35
- if redirect? response
82
+ if follow_redirect?(env, response)
36
83
  raise RedirectLimitReached, response if follows.zero?
37
- env[:url] += response['location']
38
- env[:method] = :get
39
- response = process_response(@app.call(env), follows - 1)
84
+ env = update_env(env, request_body, response)
85
+ response = perform_with_redirection(env, follows - 1)
40
86
  end
41
87
  end
42
88
  response
43
89
  end
44
90
 
45
- def redirect?(response)
46
- REDIRECTS.include? response.status
91
+ def update_env(env, request_body, response)
92
+ env[:url] += response['location']
93
+ if @options[:cookies]
94
+ cookies = keep_cookies(env)
95
+ env[:request_headers][:cookies] = cookies unless cookies.nil?
96
+ end
97
+
98
+ if transform_into_get?(response)
99
+ env[:method] = :get
100
+ env[:body] = nil
101
+ else
102
+ env[:body] = request_body
103
+ end
104
+
105
+ ENV_TO_CLEAR.each {|key| env.delete key }
106
+
107
+ env
108
+ end
109
+
110
+ def follow_redirect?(env, response)
111
+ ALLOWED_METHODS.include? env[:method] and
112
+ REDIRECT_CODES.include? response.status
47
113
  end
48
114
 
49
115
  def follow_limit
50
116
  @options.fetch(:limit, FOLLOW_LIMIT)
51
117
  end
118
+
119
+ def keep_cookies(env)
120
+ cookies = @options.fetch(:cookies, [])
121
+ response_cookies = env[:response_headers][:cookies]
122
+ cookies == :all ? response_cookies : selected_request_cookies(response_cookies)
123
+ end
124
+
125
+ def selected_request_cookies(cookies)
126
+ selected_cookies(cookies)[0...-1]
127
+ end
128
+
129
+ def selected_cookies(cookies)
130
+ "".tap do |cookie_string|
131
+ @options[:cookies].each do |cookie|
132
+ string = /#{cookie}=?[^;]*/.match(cookies)[0] + ';'
133
+ cookie_string << string
134
+ end
135
+ end
136
+ end
137
+
138
+ def standards_compliant?
139
+ @options.fetch(:standards_compliant, false)
140
+ end
52
141
  end
53
142
  end