faraday 0.7.4 → 1.0.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 (119) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +276 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +40 -153
  5. data/Rakefile +4 -139
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday/adapter/em_http.rb +286 -0
  9. data/lib/faraday/adapter/em_http_ssl_patch.rb +62 -0
  10. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +69 -0
  11. data/lib/faraday/adapter/em_synchrony.rb +120 -36
  12. data/lib/faraday/adapter/excon.rb +108 -12
  13. data/lib/faraday/adapter/httpclient.rb +152 -0
  14. data/lib/faraday/adapter/net_http.rb +187 -43
  15. data/lib/faraday/adapter/net_http_persistent.rb +91 -0
  16. data/lib/faraday/adapter/patron.rb +106 -10
  17. data/lib/faraday/adapter/rack.rb +75 -0
  18. data/lib/faraday/adapter/test.rb +160 -61
  19. data/lib/faraday/adapter/typhoeus.rb +7 -46
  20. data/lib/faraday/adapter.rb +105 -33
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +95 -0
  23. data/lib/faraday/connection.rb +525 -157
  24. data/lib/faraday/dependency_loader.rb +37 -0
  25. data/lib/faraday/encoders/flat_params_encoder.rb +98 -0
  26. data/lib/faraday/encoders/nested_params_encoder.rb +171 -0
  27. data/lib/faraday/error.rb +122 -30
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/middleware.rb +14 -22
  31. data/lib/faraday/middleware_registry.rb +129 -0
  32. data/lib/faraday/options/connection_options.rb +22 -0
  33. data/lib/faraday/options/env.rb +181 -0
  34. data/lib/faraday/options/proxy_options.rb +28 -0
  35. data/lib/faraday/options/request_options.rb +22 -0
  36. data/lib/faraday/options/ssl_options.rb +59 -0
  37. data/lib/faraday/options.rb +222 -0
  38. data/lib/faraday/param_part.rb +53 -0
  39. data/lib/faraday/parameters.rb +5 -0
  40. data/lib/faraday/rack_builder.rb +248 -0
  41. data/lib/faraday/request/authorization.rb +55 -0
  42. data/lib/faraday/request/basic_authentication.rb +20 -0
  43. data/lib/faraday/request/instrumentation.rb +54 -0
  44. data/lib/faraday/request/multipart.rb +84 -48
  45. data/lib/faraday/request/retry.rb +239 -0
  46. data/lib/faraday/request/token_authentication.rb +20 -0
  47. data/lib/faraday/request/url_encoded.rb +46 -27
  48. data/lib/faraday/request.rb +112 -50
  49. data/lib/faraday/response/logger.rb +24 -25
  50. data/lib/faraday/response/raise_error.rb +40 -11
  51. data/lib/faraday/response.rb +44 -35
  52. data/lib/faraday/utils/headers.rb +139 -0
  53. data/lib/faraday/utils/params_hash.rb +61 -0
  54. data/lib/faraday/utils.rb +72 -117
  55. data/lib/faraday.rb +142 -64
  56. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  57. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  58. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  59. data/spec/faraday/adapter/excon_spec.rb +49 -0
  60. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  61. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  62. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  63. data/spec/faraday/adapter/patron_spec.rb +18 -0
  64. data/spec/faraday/adapter/rack_spec.rb +8 -0
  65. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  66. data/spec/faraday/adapter_registry_spec.rb +28 -0
  67. data/spec/faraday/adapter_spec.rb +55 -0
  68. data/spec/faraday/composite_read_io_spec.rb +80 -0
  69. data/spec/faraday/connection_spec.rb +691 -0
  70. data/spec/faraday/error_spec.rb +45 -0
  71. data/spec/faraday/middleware_spec.rb +26 -0
  72. data/spec/faraday/options/env_spec.rb +70 -0
  73. data/spec/faraday/options/options_spec.rb +297 -0
  74. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  75. data/spec/faraday/options/request_options_spec.rb +19 -0
  76. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  77. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  78. data/spec/faraday/rack_builder_spec.rb +196 -0
  79. data/spec/faraday/request/authorization_spec.rb +88 -0
  80. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  81. data/spec/faraday/request/multipart_spec.rb +274 -0
  82. data/spec/faraday/request/retry_spec.rb +242 -0
  83. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  84. data/spec/faraday/request_spec.rb +109 -0
  85. data/spec/faraday/response/logger_spec.rb +220 -0
  86. data/spec/faraday/response/middleware_spec.rb +68 -0
  87. data/spec/faraday/response/raise_error_spec.rb +106 -0
  88. data/spec/faraday/response_spec.rb +75 -0
  89. data/spec/faraday/utils/headers_spec.rb +82 -0
  90. data/spec/faraday/utils_spec.rb +56 -0
  91. data/spec/faraday_spec.rb +37 -0
  92. data/spec/spec_helper.rb +132 -0
  93. data/spec/support/disabling_stub.rb +14 -0
  94. data/spec/support/fake_safe_buffer.rb +15 -0
  95. data/spec/support/helper_methods.rb +133 -0
  96. data/spec/support/shared_examples/adapter.rb +104 -0
  97. data/spec/support/shared_examples/params_encoder.rb +18 -0
  98. data/spec/support/shared_examples/request_method.rb +234 -0
  99. data/spec/support/streaming_response_checker.rb +35 -0
  100. data/spec/support/webmock_rack_app.rb +68 -0
  101. metadata +126 -126
  102. data/Gemfile +0 -29
  103. data/config.ru +0 -6
  104. data/faraday.gemspec +0 -92
  105. data/lib/faraday/adapter/action_dispatch.rb +0 -29
  106. data/lib/faraday/builder.rb +0 -160
  107. data/lib/faraday/request/json.rb +0 -35
  108. data/lib/faraday/upload_io.rb +0 -23
  109. data/test/adapters/live_test.rb +0 -205
  110. data/test/adapters/logger_test.rb +0 -37
  111. data/test/adapters/net_http_test.rb +0 -33
  112. data/test/adapters/test_middleware_test.rb +0 -70
  113. data/test/connection_test.rb +0 -254
  114. data/test/env_test.rb +0 -158
  115. data/test/helper.rb +0 -41
  116. data/test/live_server.rb +0 -45
  117. data/test/middleware_stack_test.rb +0 -118
  118. data/test/request_middleware_test.rb +0 -116
  119. data/test/response_middleware_test.rb +0 -74
@@ -1,66 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
1
5
  module Faraday
2
6
  class Adapter
7
+ # EventMachine Synchrony adapter.
3
8
  class EMSynchrony < Faraday::Adapter
9
+ include EMHttp::Options
10
+
4
11
  dependency do
5
12
  require 'em-synchrony/em-http'
13
+ require 'em-synchrony/em-multi'
6
14
  require 'fiber'
7
15
  end
8
16
 
17
+ self.supports_parallel = true
18
+
19
+ # @return [ParallelManager]
20
+ def self.setup_parallel_manager(_options = nil)
21
+ ParallelManager.new
22
+ end
23
+
9
24
  def call(env)
10
25
  super
11
- request = EventMachine::HttpRequest.new(URI::parse(env[:url].to_s))
12
- options = {:head => env[:request_headers]}
13
- options[:ssl] = env[:ssl] if env[:ssl]
14
-
15
- if env[:body]
16
- if env[:body].respond_to? :read
17
- options[:body] = env[:body].read
18
- else
19
- options[:body] = env[:body]
20
- end
26
+ request = create_request(env)
27
+
28
+ http_method = env[:method].to_s.downcase.to_sym
29
+
30
+ if env[:parallel_manager]
31
+ # Queue requests for parallel execution.
32
+ execute_parallel_request(env, request, http_method)
33
+ else
34
+ # Execute single request.
35
+ execute_single_request(env, request, http_method)
36
+ end
37
+
38
+ @app.call env
39
+ rescue Errno::ECONNREFUSED
40
+ raise Faraday::ConnectionFailed, $ERROR_INFO
41
+ rescue EventMachine::Connectify::CONNECTError => e
42
+ if e.message.include?('Proxy Authentication Required')
43
+ raise Faraday::ConnectionFailed,
44
+ %(407 "Proxy Authentication Required")
45
+ end
46
+
47
+ raise Faraday::ConnectionFailed, e
48
+ rescue Errno::ETIMEDOUT => e
49
+ raise Faraday::TimeoutError, e
50
+ rescue RuntimeError => e
51
+ if e.message == 'connection closed by server'
52
+ raise Faraday::ConnectionFailed, e
53
+ end
54
+
55
+ raise Faraday::TimeoutError, e if e.message.include?('timeout error')
56
+
57
+ raise
58
+ rescue StandardError => e
59
+ if defined?(OpenSSL) && e.is_a?(OpenSSL::SSL::SSLError)
60
+ raise Faraday::SSLError, e
21
61
  end
22
62
 
23
- if req = env[:request]
24
- if proxy = req[:proxy]
25
- uri = Addressable::URI.parse(proxy[:uri])
26
- options[:proxy] = {
27
- :host => uri.host,
28
- :port => uri.inferred_port
29
- }
30
- if proxy[:username] && proxy[:password]
31
- options[:proxy][:authorization] = [proxy[:username], proxy[:password]]
63
+ raise
64
+ end
65
+
66
+ def create_request(env)
67
+ EventMachine::HttpRequest.new(
68
+ Utils::URI(env[:url].to_s),
69
+ connection_config(env).merge(@connection_options)
70
+ )
71
+ end
72
+
73
+ private
74
+
75
+ def execute_parallel_request(env, request, http_method)
76
+ env[:parallel_manager].add(request, http_method,
77
+ request_config(env)) do |resp|
78
+ if (req = env[:request]).stream_response?
79
+ warn "Streaming downloads for #{self.class.name} " \
80
+ 'are not yet implemented.'
81
+ req.on_data.call(resp.response, resp.response.bytesize)
82
+ end
83
+
84
+ save_response(env, resp.response_header.status,
85
+ resp.response) do |resp_headers|
86
+ resp.response_header.each do |name, value|
87
+ resp_headers[name.to_sym] = value
32
88
  end
33
89
  end
34
90
 
35
- # only one timeout currently supported by em http request
36
- if req[:timeout] or req[:open_timeout]
37
- options[:timeout] = [req[:timeout] || 0, req[:open_timeout] || 0].max
91
+ # Finalize the response object with values from `env`.
92
+ env[:response].finish(env)
93
+ end
94
+ end
95
+
96
+ def execute_single_request(env, request, http_method)
97
+ block = -> { request.send(http_method, request_config(env)) }
98
+ client = call_block(block)
99
+
100
+ raise client.error if client&.error
101
+
102
+ if env[:request].stream_response?
103
+ warn "Streaming downloads for #{self.class.name} " \
104
+ 'are not yet implemented.'
105
+ env[:request].on_data.call(
106
+ client.response,
107
+ client.response.bytesize
108
+ )
109
+ end
110
+ status = client.response_header.status
111
+ reason = client.response_header.http_reason
112
+ save_response(env, status, client.response, nil, reason) do |headers|
113
+ client.response_header.each do |name, value|
114
+ headers[name.to_sym] = value
38
115
  end
39
116
  end
117
+ end
40
118
 
119
+ def call_block(block)
41
120
  client = nil
42
- block = lambda { request.send env[:method].to_s.downcase.to_sym, options }
43
- if !EM.reactor_running?
44
- EM.run {
121
+
122
+ if EM.reactor_running?
123
+ client = block.call
124
+ else
125
+ EM.run do
45
126
  Fiber.new do
46
127
  client = block.call
47
128
  EM.stop
48
129
  end.resume
49
- }
50
- else
51
- client = block.call
52
- end
53
-
54
- save_response(env, client.response_header.status, client.response) do |response_headers|
55
- client.response_header.each do |name, value|
56
- response_headers[name.to_sym] = value
57
130
  end
58
131
  end
59
132
 
60
- @app.call env
61
- rescue Errno::ECONNREFUSED
62
- raise Error::ConnectionFailed, $!
133
+ client
63
134
  end
64
135
  end
65
136
  end
66
137
  end
138
+
139
+ require 'faraday/adapter/em_synchrony/parallel_manager'
140
+
141
+ if Faraday::Adapter::EMSynchrony.loaded?
142
+ begin
143
+ require 'openssl'
144
+ rescue LoadError
145
+ warn 'Warning: no such file to load -- openssl. ' \
146
+ 'Make sure it is installed if you want HTTPS support'
147
+ else
148
+ require 'faraday/adapter/em_http_ssl_patch'
149
+ end
150
+ end
@@ -1,27 +1,123 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
4
  class Adapter
5
+ # Excon adapter.
3
6
  class Excon < Faraday::Adapter
4
7
  dependency 'excon'
5
8
 
9
+ def build_connection(env)
10
+ opts = opts_from_env(env)
11
+ ::Excon.new(env[:url].to_s, opts.merge(@connection_options))
12
+ end
13
+
6
14
  def call(env)
7
15
  super
8
16
 
9
- conn = ::Excon.new(env[:url].to_s)
10
- if ssl = (env[:url].scheme == 'https' && env[:ssl])
11
- ::Excon.ssl_verify_peer = !!ssl.fetch(:verify, true)
12
- ::Excon.ssl_ca_path = ssl[:ca_file] if ssl[:ca_file]
17
+ req_opts = {
18
+ method: env[:method].to_s.upcase,
19
+ headers: env[:request_headers],
20
+ body: read_body(env)
21
+ }
22
+
23
+ req = env[:request]
24
+ if req&.stream_response?
25
+ total = 0
26
+ req_opts[:response_block] = lambda do |chunk, _remain, _total|
27
+ req.on_data.call(chunk, total += chunk.size)
28
+ end
29
+ end
30
+
31
+ resp = connection(env) { |http| http.request(req_opts) }
32
+ save_response(env, resp.status.to_i, resp.body, resp.headers,
33
+ resp.reason_phrase)
34
+
35
+ @app.call(env)
36
+ rescue ::Excon::Errors::SocketError => e
37
+ raise Faraday::TimeoutError, e if e.message =~ /\btimeout\b/
38
+
39
+ raise Faraday::SSLError, e if e.message =~ /\bcertificate\b/
40
+
41
+ raise Faraday::ConnectionFailed, e
42
+ rescue ::Excon::Errors::Timeout => e
43
+ raise Faraday::TimeoutError, e
44
+ end
45
+
46
+ # TODO: support streaming requests
47
+ def read_body(env)
48
+ env[:body].respond_to?(:read) ? env[:body].read : env[:body]
49
+ end
50
+
51
+ private
52
+
53
+ def opts_from_env(env)
54
+ opts = {}
55
+ amend_opts_with_ssl!(opts, env[:ssl]) if needs_ssl_settings?(env)
56
+
57
+ if (req = env[:request])
58
+ amend_opts_with_timeouts!(opts, req)
59
+ amend_opts_with_proxy_settings!(opts, req)
60
+ end
61
+
62
+ opts
63
+ end
64
+
65
+ def needs_ssl_settings?(env)
66
+ env[:url].scheme == 'https' && env[:ssl]
67
+ end
68
+
69
+ OPTS_KEYS = [
70
+ %i[client_cert client_cert],
71
+ %i[client_key client_key],
72
+ %i[certificate certificate],
73
+ %i[private_key private_key],
74
+ %i[ssl_ca_path ca_path],
75
+ %i[ssl_ca_file ca_file],
76
+ %i[ssl_version version],
77
+ %i[ssl_min_version min_version],
78
+ %i[ssl_max_version max_version]
79
+ ].freeze
80
+
81
+ def amend_opts_with_ssl!(opts, ssl)
82
+ opts[:ssl_verify_peer] = !!ssl.fetch(:verify, true)
83
+ # https://github.com/geemus/excon/issues/106
84
+ # https://github.com/jruby/jruby-ossl/issues/19
85
+ opts[:nonblock] = false
86
+
87
+ OPTS_KEYS.each do |(key_in_opts, key_in_ssl)|
88
+ next unless ssl[key_in_ssl]
89
+
90
+ opts[key_in_opts] = ssl[key_in_ssl]
13
91
  end
92
+ end
93
+
94
+ def amend_opts_with_timeouts!(opts, req)
95
+ if (sec = request_timeout(:read, req))
96
+ opts[:read_timeout] = sec
97
+ end
98
+
99
+ if (sec = request_timeout(:write, req))
100
+ opts[:write_timeout] = sec
101
+ end
102
+
103
+ return unless (sec = request_timeout(:open, req))
14
104
 
15
- resp = conn.request \
16
- :method => env[:method].to_s.upcase,
17
- :headers => env[:request_headers],
18
- :body => env[:body]
105
+ opts[:connect_timeout] = sec
106
+ end
19
107
 
20
- save_response(env, resp.status.to_i, resp.body, resp.headers)
108
+ def amend_opts_with_proxy_settings!(opts, req)
109
+ opts[:proxy] = proxy_settings_for_opts(req[:proxy]) if req[:proxy]
110
+ end
21
111
 
22
- @app.call env
23
- rescue ::Excon::Errors::SocketError
24
- raise Error::ConnectionFailed, $!
112
+ def proxy_settings_for_opts(proxy)
113
+ {
114
+ host: proxy[:uri].host,
115
+ hostname: proxy[:uri].hostname,
116
+ port: proxy[:uri].port,
117
+ scheme: proxy[:uri].scheme,
118
+ user: proxy[:user],
119
+ password: proxy[:password]
120
+ }
25
121
  end
26
122
  end
27
123
  end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ class Adapter
5
+ # HTTPClient adapter.
6
+ class HTTPClient < Faraday::Adapter
7
+ dependency 'httpclient'
8
+
9
+ def build_connection(env)
10
+ @client ||= ::HTTPClient.new.tap do |cli|
11
+ # enable compression
12
+ cli.transparent_gzip_decompression = true
13
+ end
14
+
15
+ if (req = env[:request])
16
+ if (proxy = req[:proxy])
17
+ configure_proxy @client, proxy
18
+ end
19
+
20
+ if (bind = req[:bind])
21
+ configure_socket @client, bind
22
+ end
23
+
24
+ configure_timeouts @client, req
25
+ end
26
+
27
+ if env[:url].scheme == 'https' && (ssl = env[:ssl])
28
+ configure_ssl @client, ssl
29
+ end
30
+
31
+ configure_client @client
32
+
33
+ @client
34
+ end
35
+
36
+ def call(env)
37
+ super
38
+
39
+ # TODO: Don't stream yet.
40
+ # https://github.com/nahi/httpclient/pull/90
41
+ env[:body] = env[:body].read if env[:body].respond_to? :read
42
+
43
+ connection(env) do |http|
44
+ resp = http.request env[:method], env[:url],
45
+ body: env[:body],
46
+ header: env[:request_headers]
47
+
48
+ if (req = env[:request]).stream_response?
49
+ warn "Streaming downloads for #{self.class.name} " \
50
+ 'are not yet implemented.'
51
+ req.on_data.call(resp.body, resp.body.bytesize)
52
+ end
53
+ save_response env, resp.status, resp.body, resp.headers, resp.reason
54
+
55
+ @app.call env
56
+ end
57
+ rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
58
+ raise Faraday::TimeoutError, $ERROR_INFO
59
+ rescue ::HTTPClient::BadResponseError => e
60
+ if e.message.include?('status 407')
61
+ raise Faraday::ConnectionFailed,
62
+ %(407 "Proxy Authentication Required ")
63
+ end
64
+
65
+ raise Faraday::ClientError, $ERROR_INFO
66
+ rescue Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED, IOError, SocketError
67
+ raise Faraday::ConnectionFailed, $ERROR_INFO
68
+ rescue StandardError => e
69
+ if defined?(::OpenSSL::SSL::SSLError) && \
70
+ e.is_a?(::OpenSSL::SSL::SSLError)
71
+ raise Faraday::SSLError, e
72
+ end
73
+
74
+ raise
75
+ end
76
+
77
+ # @param bind [Hash]
78
+ def configure_socket(client, bind)
79
+ client.socket_local.host = bind[:host]
80
+ client.socket_local.port = bind[:port]
81
+ end
82
+
83
+ # Configure proxy URI and any user credentials.
84
+ #
85
+ # @param proxy [Hash]
86
+ def configure_proxy(client, proxy)
87
+ client.proxy = proxy[:uri]
88
+ return unless proxy[:user] && proxy[:password]
89
+
90
+ client.set_proxy_auth(proxy[:user], proxy[:password])
91
+ end
92
+
93
+ # @param ssl [Hash]
94
+ def configure_ssl(client, ssl)
95
+ ssl_config = client.ssl_config
96
+ ssl_config.verify_mode = ssl_verify_mode(ssl)
97
+ ssl_config.cert_store = ssl_cert_store(ssl)
98
+
99
+ ssl_config.add_trust_ca ssl[:ca_file] if ssl[:ca_file]
100
+ ssl_config.add_trust_ca ssl[:ca_path] if ssl[:ca_path]
101
+ ssl_config.client_cert = ssl[:client_cert] if ssl[:client_cert]
102
+ ssl_config.client_key = ssl[:client_key] if ssl[:client_key]
103
+ ssl_config.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
104
+ end
105
+
106
+ # @param req [Hash]
107
+ def configure_timeouts(client, req)
108
+ if (sec = request_timeout(:open, req))
109
+ client.connect_timeout = sec
110
+ end
111
+
112
+ if (sec = request_timeout(:write, req))
113
+ client.send_timeout = sec
114
+ end
115
+
116
+ return unless (sec = request_timeout(:read, req))
117
+
118
+ client.receive_timeout = sec
119
+ end
120
+
121
+ def configure_client(client)
122
+ @config_block&.call(client)
123
+ end
124
+
125
+ # @param ssl [Hash]
126
+ # @return [OpenSSL::X509::Store]
127
+ def ssl_cert_store(ssl)
128
+ return ssl[:cert_store] if ssl[:cert_store]
129
+
130
+ # Memoize the cert store so that the same one is passed to
131
+ # HTTPClient each time, to avoid resyncing SSL sessions when
132
+ # it's changed
133
+ @ssl_cert_store ||= begin
134
+ # Use the default cert store by default, i.e. system ca certs
135
+ OpenSSL::X509::Store.new.tap(&:set_default_paths)
136
+ end
137
+ end
138
+
139
+ # @param ssl [Hash]
140
+ def ssl_verify_mode(ssl)
141
+ ssl[:verify_mode] || begin
142
+ if ssl.fetch(:verify, true)
143
+ OpenSSL::SSL::VERIFY_PEER |
144
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
145
+ else
146
+ OpenSSL::SSL::VERIFY_NONE
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end