excon 0.49.0 → 0.88.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTORS.md +33 -3
  3. data/LICENSE.md +1 -1
  4. data/README.md +77 -7
  5. data/data/cacert.pem +1279 -1940
  6. data/excon.gemspec +32 -170
  7. data/lib/excon/connection.rb +257 -154
  8. data/lib/excon/constants.rb +44 -16
  9. data/lib/excon/error.rb +229 -0
  10. data/lib/excon/extensions/uri.rb +1 -0
  11. data/lib/excon/headers.rb +5 -3
  12. data/lib/excon/instrumentors/logging_instrumentor.rb +48 -0
  13. data/lib/excon/instrumentors/standard_instrumentor.rb +21 -0
  14. data/lib/excon/middlewares/base.rb +7 -0
  15. data/lib/excon/middlewares/capture_cookies.rb +2 -1
  16. data/lib/excon/middlewares/decompress.rb +3 -2
  17. data/lib/excon/middlewares/escape_path.rb +1 -0
  18. data/lib/excon/middlewares/expects.rb +8 -1
  19. data/lib/excon/middlewares/idempotent.rb +27 -3
  20. data/lib/excon/middlewares/instrumentor.rb +9 -0
  21. data/lib/excon/middlewares/mock.rb +14 -4
  22. data/lib/excon/middlewares/redirect_follower.rb +28 -4
  23. data/lib/excon/middlewares/response_parser.rb +4 -0
  24. data/lib/excon/pretty_printer.rb +2 -8
  25. data/lib/excon/response.rb +16 -12
  26. data/lib/excon/socket.rb +56 -36
  27. data/lib/excon/ssl_socket.rb +70 -25
  28. data/lib/excon/test/plugin/server/exec.rb +26 -0
  29. data/lib/excon/test/plugin/server/puma.rb +23 -0
  30. data/lib/excon/test/plugin/server/unicorn.rb +38 -0
  31. data/lib/excon/test/plugin/server/webrick.rb +26 -0
  32. data/lib/excon/test/server.rb +106 -0
  33. data/lib/excon/unix_socket.rb +2 -0
  34. data/lib/excon/utils.rb +65 -10
  35. data/lib/excon/version.rb +4 -0
  36. data/lib/excon.rb +35 -20
  37. metadata +69 -87
  38. data/Gemfile +0 -19
  39. data/Gemfile.lock +0 -285
  40. data/Rakefile +0 -144
  41. data/benchmarks/class_vs_lambda.rb +0 -50
  42. data/benchmarks/concat_vs_insert.rb +0 -21
  43. data/benchmarks/concat_vs_interpolate.rb +0 -21
  44. data/benchmarks/cr_lf.rb +0 -21
  45. data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
  46. data/benchmarks/excon.rb +0 -69
  47. data/benchmarks/excon_vs.rb +0 -165
  48. data/benchmarks/for_vs_array_each.rb +0 -27
  49. data/benchmarks/for_vs_hash_each.rb +0 -27
  50. data/benchmarks/has_key-vs-lookup.rb +0 -177
  51. data/benchmarks/headers_case_sensitivity.rb +0 -83
  52. data/benchmarks/headers_split_vs_match.rb +0 -34
  53. data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
  54. data/benchmarks/merging.rb +0 -21
  55. data/benchmarks/single_vs_double_quotes.rb +0 -21
  56. data/benchmarks/string_ranged_index.rb +0 -87
  57. data/benchmarks/strip_newline.rb +0 -115
  58. data/benchmarks/vs_stdlib.rb +0 -82
  59. data/changelog.txt +0 -959
  60. data/lib/excon/errors.rb +0 -172
  61. data/lib/excon/standard_instrumentor.rb +0 -27
  62. data/tests/authorization_header_tests.rb +0 -33
  63. data/tests/bad_tests.rb +0 -47
  64. data/tests/basic_tests.rb +0 -334
  65. data/tests/complete_responses.rb +0 -31
  66. data/tests/data/127.0.0.1.cert.crt +0 -14
  67. data/tests/data/127.0.0.1.cert.key +0 -15
  68. data/tests/data/excon.cert.crt +0 -14
  69. data/tests/data/excon.cert.key +0 -15
  70. data/tests/data/xs +0 -1
  71. data/tests/errors_tests.rb +0 -58
  72. data/tests/header_tests.rb +0 -119
  73. data/tests/middlewares/canned_response_tests.rb +0 -34
  74. data/tests/middlewares/capture_cookies_tests.rb +0 -34
  75. data/tests/middlewares/decompress_tests.rb +0 -157
  76. data/tests/middlewares/escape_path_tests.rb +0 -36
  77. data/tests/middlewares/idempotent_tests.rb +0 -131
  78. data/tests/middlewares/instrumentation_tests.rb +0 -312
  79. data/tests/middlewares/mock_tests.rb +0 -293
  80. data/tests/middlewares/redirect_follower_tests.rb +0 -80
  81. data/tests/pipeline_tests.rb +0 -40
  82. data/tests/proxy_tests.rb +0 -306
  83. data/tests/query_string_tests.rb +0 -87
  84. data/tests/rackups/basic.rb +0 -41
  85. data/tests/rackups/basic.ru +0 -3
  86. data/tests/rackups/basic_auth.ru +0 -14
  87. data/tests/rackups/deflater.ru +0 -4
  88. data/tests/rackups/proxy.ru +0 -18
  89. data/tests/rackups/query_string.ru +0 -13
  90. data/tests/rackups/redirecting.ru +0 -23
  91. data/tests/rackups/redirecting_with_cookie.ru +0 -40
  92. data/tests/rackups/request_headers.ru +0 -15
  93. data/tests/rackups/request_methods.ru +0 -21
  94. data/tests/rackups/response_header.ru +0 -18
  95. data/tests/rackups/ssl.ru +0 -16
  96. data/tests/rackups/ssl_mismatched_cn.ru +0 -15
  97. data/tests/rackups/ssl_verify_peer.ru +0 -16
  98. data/tests/rackups/streaming.ru +0 -30
  99. data/tests/rackups/thread_safety.ru +0 -17
  100. data/tests/rackups/timeout.ru +0 -14
  101. data/tests/rackups/webrick_patch.rb +0 -34
  102. data/tests/request_headers_tests.rb +0 -21
  103. data/tests/request_method_tests.rb +0 -47
  104. data/tests/request_tests.rb +0 -59
  105. data/tests/response_tests.rb +0 -197
  106. data/tests/servers/bad.rb +0 -20
  107. data/tests/servers/eof.rb +0 -17
  108. data/tests/servers/error.rb +0 -20
  109. data/tests/servers/good.rb +0 -350
  110. data/tests/test_helper.rb +0 -306
  111. data/tests/thread_safety_tests.rb +0 -39
  112. data/tests/timeout_tests.rb +0 -12
  113. data/tests/utils_tests.rb +0 -81
@@ -1,7 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
 
3
- VERSION = '0.49.0'
4
-
5
4
  CR_NL = "\r\n"
6
5
 
7
6
  DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "cacert.pem"))
@@ -13,8 +12,16 @@ module Excon
13
12
  CHUNK_SIZE = DEFAULT_CHUNK_SIZE
14
13
  end
15
14
 
15
+ DEFAULT_REDIRECT_LIMIT = 10
16
+
16
17
  DEFAULT_RETRY_LIMIT = 4
17
18
 
19
+ DEFAULT_RETRY_ERRORS = [
20
+ Excon::Error::Timeout,
21
+ Excon::Error::Socket,
22
+ Excon::Error::HTTPStatus
23
+ ]
24
+
18
25
  FORCE_ENC = CR_NL.respond_to?(:force_encoding)
19
26
 
20
27
  HTTP_1_1 = " HTTP/1.1\r\n"
@@ -29,24 +36,22 @@ module Excon
29
36
 
30
37
  UNIX = 'unix'
31
38
 
32
- USER_AGENT = 'excon/' << VERSION
39
+ USER_AGENT = "excon/#{VERSION}"
33
40
 
34
- VERSIONS = USER_AGENT + ' (' << RUBY_PLATFORM << ') ruby/' << RUBY_VERSION
41
+ VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
35
42
 
36
43
  VALID_REQUEST_KEYS = [
44
+ :allow_unstubbed_requests,
37
45
  :body,
38
- :captures,
39
46
  :chunk_size,
40
47
  :debug_request,
41
48
  :debug_response,
42
- :expects,
43
49
  :headers,
44
- :idempotent,
45
- :instrumentor,
46
- :instrumentor_name,
50
+ :instrumentor, # Used for setting logging within Connection
51
+ :logger,
47
52
  :method,
48
53
  :middlewares,
49
- :mock,
54
+ :password,
50
55
  :path,
51
56
  :persistent,
52
57
  :pipeline,
@@ -54,8 +59,8 @@ module Excon
54
59
  :read_timeout,
55
60
  :request_block,
56
61
  :response_block,
57
- :retries_remaining, # used internally
58
- :retry_limit,
62
+ :stubs,
63
+ :user,
59
64
  :versions,
60
65
  :write_timeout
61
66
  ]
@@ -63,8 +68,10 @@ module Excon
63
68
  VALID_CONNECTION_KEYS = VALID_REQUEST_KEYS + [
64
69
  :ciphers,
65
70
  :client_key,
71
+ :client_key_data,
66
72
  :client_key_pass,
67
73
  :client_cert,
74
+ :client_cert_data,
68
75
  :certificate,
69
76
  :certificate_path,
70
77
  :disable_proxy,
@@ -72,12 +79,12 @@ module Excon
72
79
  :private_key_path,
73
80
  :connect_timeout,
74
81
  :family,
82
+ :keepalive,
75
83
  :host,
76
84
  :hostname,
77
85
  :omit_default_port,
78
86
  :nonblock,
79
87
  :reuseaddr,
80
- :password,
81
88
  :port,
82
89
  :proxy,
83
90
  :scheme,
@@ -88,13 +95,30 @@ module Excon
88
95
  :ssl_verify_callback,
89
96
  :ssl_verify_peer,
90
97
  :ssl_verify_peer_host,
98
+ :ssl_verify_hostname,
91
99
  :ssl_version,
100
+ :ssl_min_version,
101
+ :ssl_max_version,
102
+ :ssl_security_level,
103
+ :ssl_proxy_headers,
104
+ :ssl_uri_schemes,
92
105
  :tcp_nodelay,
93
106
  :thread_safe_sockets,
94
107
  :uri_parser,
95
- :user
96
108
  ]
97
109
 
110
+ DEPRECATED_VALID_REQUEST_KEYS = {
111
+ :captures => 'Mock',
112
+ :expects => 'Expects',
113
+ :idempotent => 'Idempotent',
114
+ :instrumentor_name => 'Instrumentor',
115
+ :mock => 'Mock',
116
+ :retries_remaining => 'Idempotent', # referenced in Instrumentor, but only relevant with Idempotent
117
+ :retry_errors => 'Idempotent',
118
+ :retry_interval => 'Idempotent',
119
+ :retry_limit => 'Idempotent' # referenced in Instrumentor, but only relevant with Idempotent
120
+ }
121
+
98
122
  unless ::IO.const_defined?(:WaitReadable)
99
123
  class ::IO
100
124
  module WaitReadable; end
@@ -109,12 +133,15 @@ module Excon
109
133
  # these come last as they rely on the above
110
134
  DEFAULTS = {
111
135
  :chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
112
- :ciphers => 'HIGH:!SSLv2:!aNULL:!eNULL:!3DES',
136
+ # see https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
137
+ # list provided then had DES related things sorted to the end
138
+ :ciphers => 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:DES-CBC3-SHA:!DSS',
113
139
  :connect_timeout => 60,
114
140
  :debug_request => false,
115
141
  :debug_response => false,
116
142
  :headers => {
117
- 'User-Agent' => USER_AGENT
143
+ 'User-Agent' => USER_AGENT,
144
+ 'Accept' => '*/*'
118
145
  },
119
146
  :idempotent => false,
120
147
  :instrumentor_name => 'excon',
@@ -130,6 +157,7 @@ module Excon
130
157
  :omit_default_port => false,
131
158
  :persistent => false,
132
159
  :read_timeout => 60,
160
+ :retry_errors => DEFAULT_RETRY_ERRORS,
133
161
  :retry_limit => DEFAULT_RETRY_LIMIT,
134
162
  :ssl_verify_peer => true,
135
163
  :ssl_uri_schemes => [HTTPS],
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+ module Excon
3
+ # Excon exception classes
4
+ class Error < StandardError
5
+ @default_status_error = :HTTPStatus
6
+
7
+ class StubNotFound < Error; end
8
+ class InvalidStub < Error; end
9
+ class Warning < Error; end
10
+
11
+ # Socket related errors
12
+ class Socket < Error
13
+ attr_reader :socket_error
14
+
15
+ def initialize(socket_error = Excon::Error.new)
16
+ if is_a?(Certificate) || is_a?(Excon::Errors::CertificateError)
17
+ super
18
+ else
19
+ super("#{socket_error.message} (#{socket_error.class})")
20
+ set_backtrace(socket_error.backtrace)
21
+ @socket_error = socket_error
22
+ end
23
+ end
24
+ end
25
+
26
+ # Certificate related errors
27
+ class Certificate < Socket
28
+ def initialize(socket_error = Excon::Error.new)
29
+ msg = <<-EOL
30
+ Unable to verify certificate. This may be an issue with the remote host or with Excon. Excon has certificates bundled, but these can be customized:
31
+
32
+ `Excon.defaults[:ssl_ca_path] = path_to_certs`
33
+ `ENV['SSL_CERT_DIR'] = path_to_certs`
34
+ `Excon.defaults[:ssl_ca_file] = path_to_file`
35
+ `ENV['SSL_CERT_FILE'] = path_to_file`
36
+ `Excon.defaults[:ssl_verify_callback] = callback`
37
+ (see OpenSSL::SSL::SSLContext#verify_callback)
38
+ or:
39
+ `Excon.defaults[:ssl_verify_peer] = false` (less secure).
40
+ EOL
41
+ full_message = "#{socket_error.message} (#{socket_error.class})" +
42
+ ' ' + msg
43
+ super(full_message)
44
+ set_backtrace(socket_error.backtrace)
45
+ @socket_error = socket_error
46
+ end
47
+ end
48
+
49
+ class InvalidHeaderKey < Error; end
50
+ class InvalidHeaderValue < Error; end
51
+ class Timeout < Error; end
52
+ class ResponseParse < Error; end
53
+
54
+ class ProxyConnectionError < Error
55
+ attr_reader :request, :response
56
+
57
+ def initialize(msg, request = nil, response = nil)
58
+ super(msg)
59
+ @request = request
60
+ @response = response
61
+ end
62
+ end
63
+
64
+ class ProxyParse < Error; end
65
+ class TooManyRedirects < Error; end
66
+
67
+ # Base class for HTTP Error classes
68
+ class HTTPStatus < Error
69
+ attr_reader :request, :response
70
+
71
+ def initialize(msg, request = nil, response = nil)
72
+ super(msg)
73
+ @request = request
74
+ @response = response
75
+ end
76
+ end
77
+
78
+ # HTTP Error classes
79
+ class Informational < HTTPStatus; end
80
+ class Success < HTTPStatus; end
81
+ class Redirection < HTTPStatus; end
82
+ class Client < HTTPStatus; end
83
+ class Server < HTTPStatus; end
84
+
85
+ class Continue < Informational; end # 100
86
+ class SwitchingProtocols < Informational; end # 101
87
+ class OK < Success; end # 200
88
+ class Created < Success; end # 201
89
+ class Accepted < Success; end # 202
90
+ class NonAuthoritativeInformation < Success; end # 203
91
+ class NoContent < Success; end # 204
92
+ class ResetContent < Success; end # 205
93
+ class PartialContent < Success; end # 206
94
+ class MultipleChoices < Redirection; end # 300
95
+ class MovedPermanently < Redirection; end # 301
96
+ class Found < Redirection; end # 302
97
+ class SeeOther < Redirection; end # 303
98
+ class NotModified < Redirection; end # 304
99
+ class UseProxy < Redirection; end # 305
100
+ class TemporaryRedirect < Redirection; end # 307
101
+ class BadRequest < Client; end # 400
102
+ class Unauthorized < Client; end # 401
103
+ class PaymentRequired < Client; end # 402
104
+ class Forbidden < Client; end # 403
105
+ class NotFound < Client; end # 404
106
+ class MethodNotAllowed < Client; end # 405
107
+ class NotAcceptable < Client; end # 406
108
+ class ProxyAuthenticationRequired < Client; end # 407
109
+ class RequestTimeout < Client; end # 408
110
+ class Conflict < Client; end # 409
111
+ class Gone < Client; end # 410
112
+ class LengthRequired < Client; end # 411
113
+ class PreconditionFailed < Client; end # 412
114
+ class RequestEntityTooLarge < Client; end # 413
115
+ class RequestURITooLong < Client; end # 414
116
+ class UnsupportedMediaType < Client; end # 415
117
+ class RequestedRangeNotSatisfiable < Client; end # 416
118
+ class ExpectationFailed < Client; end # 417
119
+ class UnprocessableEntity < Client; end # 422
120
+ class TooManyRequests < Client; end # 429
121
+ class InternalServerError < Server; end # 500
122
+ class NotImplemented < Server; end # 501
123
+ class BadGateway < Server; end # 502
124
+ class ServiceUnavailable < Server; end # 503
125
+ class GatewayTimeout < Server; end # 504
126
+
127
+ def self.status_errors
128
+ @status_errors ||= {
129
+ 100 => [Excon::Error::Continue, 'Continue'],
130
+ 101 => [Excon::Error::SwitchingProtocols, 'Switching Protocols'],
131
+ 200 => [Excon::Error::OK, 'OK'],
132
+ 201 => [Excon::Error::Created, 'Created'],
133
+ 202 => [Excon::Error::Accepted, 'Accepted'],
134
+ 203 => [Excon::Error::NonAuthoritativeInformation, 'Non-Authoritative Information'],
135
+ 204 => [Excon::Error::NoContent, 'No Content'],
136
+ 205 => [Excon::Error::ResetContent, 'Reset Content'],
137
+ 206 => [Excon::Error::PartialContent, 'Partial Content'],
138
+ 300 => [Excon::Error::MultipleChoices, 'Multiple Choices'],
139
+ 301 => [Excon::Error::MovedPermanently, 'Moved Permanently'],
140
+ 302 => [Excon::Error::Found, 'Found'],
141
+ 303 => [Excon::Error::SeeOther, 'See Other'],
142
+ 304 => [Excon::Error::NotModified, 'Not Modified'],
143
+ 305 => [Excon::Error::UseProxy, 'Use Proxy'],
144
+ 307 => [Excon::Error::TemporaryRedirect, 'Temporary Redirect'],
145
+ 400 => [Excon::Error::BadRequest, 'Bad Request'],
146
+ 401 => [Excon::Error::Unauthorized, 'Unauthorized'],
147
+ 402 => [Excon::Error::PaymentRequired, 'Payment Required'],
148
+ 403 => [Excon::Error::Forbidden, 'Forbidden'],
149
+ 404 => [Excon::Error::NotFound, 'Not Found'],
150
+ 405 => [Excon::Error::MethodNotAllowed, 'Method Not Allowed'],
151
+ 406 => [Excon::Error::NotAcceptable, 'Not Acceptable'],
152
+ 407 => [Excon::Error::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
153
+ 408 => [Excon::Error::RequestTimeout, 'Request Timeout'],
154
+ 409 => [Excon::Error::Conflict, 'Conflict'],
155
+ 410 => [Excon::Error::Gone, 'Gone'],
156
+ 411 => [Excon::Error::LengthRequired, 'Length Required'],
157
+ 412 => [Excon::Error::PreconditionFailed, 'Precondition Failed'],
158
+ 413 => [Excon::Error::RequestEntityTooLarge, 'Request Entity Too Large'],
159
+ 414 => [Excon::Error::RequestURITooLong, 'Request-URI Too Long'],
160
+ 415 => [Excon::Error::UnsupportedMediaType, 'Unsupported Media Type'],
161
+ 416 => [Excon::Error::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
162
+ 417 => [Excon::Error::ExpectationFailed, 'Expectation Failed'],
163
+ 422 => [Excon::Error::UnprocessableEntity, 'Unprocessable Entity'],
164
+ 429 => [Excon::Error::TooManyRequests, 'Too Many Requests'],
165
+ 500 => [Excon::Error::InternalServerError, 'InternalServerError'],
166
+ 501 => [Excon::Error::NotImplemented, 'Not Implemented'],
167
+ 502 => [Excon::Error::BadGateway, 'Bad Gateway'],
168
+ 503 => [Excon::Error::ServiceUnavailable, 'Service Unavailable'],
169
+ 504 => [Excon::Error::GatewayTimeout, 'Gateway Timeout']
170
+ }
171
+ end
172
+
173
+ # Messages for nicer exceptions, from rfc2616
174
+ def self.status_error(request, response)
175
+ error_class, error_message = status_errors[response[:status]]
176
+ if error_class.nil?
177
+ default_class = Excon::Error.const_get(@default_status_error)
178
+ error_class, error_message = [default_class, 'Unknown']
179
+ end
180
+ message = StringIO.new
181
+ str = "Expected(#{request[:expects].inspect}) <=>" +
182
+ " Actual(#{response[:status]} #{error_message})"
183
+ message.puts(str)
184
+ if request[:debug_request]
185
+ message.puts('excon.error.request')
186
+ Excon::PrettyPrinter.pp(message, request)
187
+ end
188
+
189
+ if request[:debug_response]
190
+ message.puts('excon.error.response')
191
+ Excon::PrettyPrinter.pp(message, response.data)
192
+ end
193
+ message.rewind
194
+ error_class.new(message.read, request, response)
195
+ end
196
+ end
197
+
198
+ # Legacy
199
+ module Errors
200
+ Excon::Errors::Error = Excon::Error
201
+
202
+ legacy_re = /
203
+ \A
204
+ Client
205
+ |Server
206
+ |Socket
207
+ |Certificate
208
+ |HTTPStatus
209
+ |InternalServer
210
+ \Z
211
+ /x
212
+
213
+ klasses = Excon::Error.constants.select do |c|
214
+ Excon::Error.const_get(c).is_a? Class
215
+ end
216
+
217
+ klasses.each do |klass|
218
+ class_name = klass.to_s
219
+ unless class_name =~ /Error\Z/
220
+ class_name = klass.to_s + 'Error' if class_name =~ legacy_re
221
+ end
222
+ Excon::Errors.const_set(class_name, Excon::Error.const_get(klass))
223
+ end
224
+
225
+ def self.status_error(request, response)
226
+ Excon::Error.status_error(request, response)
227
+ end
228
+ end
229
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # TODO: Remove this monkey patch once ruby 1.9.3+ is the minimum supported version.
2
3
  #
3
4
  # This patch backports URI#hostname to ruby 1.9.2 and older.
data/lib/excon/headers.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  class Headers < Hash
3
4
 
@@ -21,6 +22,7 @@ module Excon
21
22
  alias_method :raw_values_at, :values_at
22
23
 
23
24
  def initialize
25
+ super
24
26
  @downcased = {}
25
27
  end
26
28
 
@@ -28,11 +30,11 @@ module Excon
28
30
  @downcased[key.to_s.downcase]
29
31
  end
30
32
 
31
- alias_method :[]=, :store
32
33
  def []=(key, value)
33
34
  raw_writer(key, value)
34
35
  @downcased[key.to_s.downcase] = value
35
36
  end
37
+ alias_method :store, :[]=
36
38
 
37
39
  if SENTINEL.respond_to? :assoc
38
40
  def assoc(obj)
@@ -53,11 +55,11 @@ module Excon
53
55
  end
54
56
  end
55
57
 
56
- alias_method :has_key?, :key?
57
- alias_method :has_key?, :member?
58
58
  def has_key?(key)
59
59
  raw_key?(key) || @downcased.has_key?(key.to_s.downcase)
60
60
  end
61
+ alias_method :key?, :has_key?
62
+ alias_method :member?, :has_key?
61
63
 
62
64
  def merge(other_hash)
63
65
  self.dup.merge!(other_hash)
@@ -0,0 +1,48 @@
1
+ require 'logger'
2
+
3
+ module Excon
4
+ class LoggingInstrumentor
5
+
6
+ def self.instrument(name, params = {})
7
+ params = params.dup
8
+
9
+ logger = params[:logger] || Logger.new($stderr)
10
+
11
+ # reduce duplication/noise of output
12
+ params.delete(:connection)
13
+ params.delete(:stack)
14
+
15
+ if params.has_key?(:headers) && params[:headers].has_key?('Authorization')
16
+ params[:headers] = params[:headers].dup
17
+ params[:headers]['Authorization'] = "REDACTED"
18
+ end
19
+
20
+ if params.has_key?(:password)
21
+ params[:password] = "REDACTED"
22
+ end
23
+
24
+ if name.include?('request')
25
+ info = "request: " + params[:scheme] + "://" + File.join(params[:host], params[:path])
26
+
27
+ if params[:query]
28
+ info << "?"
29
+
30
+ if params[:query].is_a?(Hash)
31
+ info << params[:query].to_a.map { |key,value| "#{key}=#{value}" }.join('&')
32
+ else
33
+ info << params[:query]
34
+ end
35
+ end
36
+ else
37
+ response_type = name.split('.').last
38
+ if params[:body]
39
+ info = "#{response_type}: " + params[:body]
40
+ end
41
+ end
42
+
43
+ logger.info(info) if info
44
+
45
+ yield if block_given?
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Excon
3
+ class StandardInstrumentor
4
+ def self.instrument(name, params = {})
5
+ params = params.dup
6
+
7
+ # reduce duplication/noise of output
8
+ params.delete(:connection)
9
+ params.delete(:stack)
10
+
11
+ params = Utils.redact(params)
12
+
13
+ $stderr.puts(name)
14
+ Excon::PrettyPrinter.pp($stderr, params)
15
+
16
+ if block_given?
17
+ yield
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,6 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class Base
5
+ # Returns the list of parameters that this middleware uses that are valid
6
+ # as arguments to `Connection#request` or `Connection#new`.
7
+ def self.valid_parameter_keys
8
+ []
9
+ end
10
+
4
11
  def initialize(stack)
5
12
  @stack = stack
6
13
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class CaptureCookies < Excon::Middleware::Base
@@ -7,7 +8,7 @@ module Excon
7
8
  end
8
9
 
9
10
  def get_header(datum, header)
10
- _, header_value = datum[:response][:headers].detect do |key, value|
11
+ _, header_value = datum[:response][:headers].detect do |key, _|
11
12
  key.casecmp(header) == 0
12
13
  end
13
14
  header_value
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class Decompress < Excon::Middleware::Base
@@ -14,9 +15,9 @@ module Excon
14
15
  def response_call(datum)
15
16
  body = datum[:response][:body]
16
17
  unless datum.has_key?(:response_block) || body.nil? || body.empty?
17
- if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 }
18
+ if (key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Encoding') == 0 })
18
19
  encodings = Utils.split_header_value(datum[:response][:headers][key])
19
- if encoding = encodings.last
20
+ if (encoding = encodings.last)
20
21
  if encoding.casecmp('deflate') == 0
21
22
  # assume inflate omits header
22
23
  datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(body)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class EscapePath < Excon::Middleware::Base
@@ -1,11 +1,18 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class Expects < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :expects
8
+ ]
9
+ end
10
+
4
11
  def response_call(datum)
5
12
  if datum.has_key?(:expects) && ![*datum[:expects]].include?(datum[:response][:status])
6
13
  raise(
7
14
  Excon::Errors.status_error(
8
- datum.reject {|key,value| key == :response},
15
+ datum.reject {|key,_| key == :response},
9
16
  Excon::Response.new(datum[:response])
10
17
  )
11
18
  )
@@ -1,6 +1,24 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+
1
4
  module Excon
2
5
  module Middleware
3
6
  class Idempotent < Excon::Middleware::Base
7
+ def self.valid_parameter_keys
8
+ [
9
+ :idempotent,
10
+ :retries_remaining,
11
+ :retry_errors,
12
+ :retry_interval,
13
+ :retry_limit
14
+ ]
15
+ end
16
+
17
+ def request_call(datum)
18
+ datum[:retries_remaining] ||= datum[:retry_limit]
19
+ @stack.request_call(datum)
20
+ end
21
+
4
22
  def error_call(datum)
5
23
  if datum[:idempotent]
6
24
  if datum.has_key?(:request_block)
@@ -11,18 +29,24 @@ module Excon
11
29
  datum[:idempotent] = false
12
30
  end
13
31
  end
32
+ if datum.has_key?(:response_block) && datum[:response_block].respond_to?(:rewind)
33
+ datum[:response_block].rewind
34
+ end
14
35
  if datum.has_key?(:pipeline)
15
36
  Excon.display_warning("Excon requests can not be :idempotent when pipelining.")
16
37
  datum[:idempotent] = false
17
38
  end
18
39
  end
19
40
 
20
- if datum[:idempotent] && [Excon::Errors::Timeout, Excon::Errors::SocketError,
21
- Excon::Errors::HTTPStatusError].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1
41
+ if datum[:idempotent] && datum[:retry_errors].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1
42
+
43
+ sleep(datum[:retry_interval]) if datum[:retry_interval]
44
+
22
45
  # reduces remaining retries, reset connection, and restart request_call
23
46
  datum[:retries_remaining] -= 1
24
47
  connection = datum.delete(:connection)
25
- datum.reject! {|key, _| !Excon::VALID_REQUEST_KEYS.include?(key) }
48
+ valid_keys = Set.new(connection.valid_request_keys(datum[:middlewares]))
49
+ datum.select! {|key, _| valid_keys.include?(key) }
26
50
  connection.request(datum)
27
51
  else
28
52
  @stack.error_call(datum)
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class Instrumentor < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :logger,
8
+ :instrumentor,
9
+ :instrumentor_name
10
+ ]
11
+ end
12
+
4
13
  def error_call(datum)
5
14
  if datum.has_key?(:instrumentor)
6
15
  datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.error", :error => datum[:error]) do
@@ -1,6 +1,15 @@
1
+ # frozen_string_literal: true
1
2
  module Excon
2
3
  module Middleware
3
4
  class Mock < Excon::Middleware::Base
5
+ def self.valid_parameter_keys
6
+ [
7
+ :allow_unstubbed_requests,
8
+ :captures,
9
+ :mock
10
+ ]
11
+ end
12
+
4
13
  def request_call(datum)
5
14
  if datum[:mock]
6
15
  # convert File/Tempfile body to string before matching:
@@ -16,12 +25,13 @@ module Excon
16
25
  raise Excon::Errors::InvalidStub.new("Request body should be a string or an IO object. #{datum[:body].class} provided")
17
26
  end
18
27
 
19
- if stub = Excon.stub_for(datum)
28
+ if (stub = Excon.stub_for(datum))
29
+ datum[:remote_ip] ||= '127.0.0.1'
20
30
  datum[:response] = {
21
31
  :body => '',
22
32
  :headers => {},
23
33
  :status => 200,
24
- :remote_ip => '127.0.0.1'
34
+ :remote_ip => datum[:remote_ip]
25
35
  }
26
36
 
27
37
  stub_datum = case stub.last
@@ -31,11 +41,11 @@ module Excon
31
41
  stub.last
32
42
  end
33
43
 
34
- datum[:response].merge!(stub_datum.reject {|key,value| key == :headers})
44
+ datum[:response].merge!(stub_datum.reject {|key,_| key == :headers})
35
45
  if stub_datum.has_key?(:headers)
36
46
  datum[:response][:headers].merge!(stub_datum[:headers])
37
47
  end
38
- else
48
+ elsif datum[:allow_unstubbed_requests] != true
39
49
  # if we reach here no stubs matched
40
50
  message = StringIO.new
41
51
  message.puts('no stubs matched')