excon 1.7.3 → 1.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.
data/excon.gemspec CHANGED
@@ -23,20 +23,9 @@ Gem::Specification.new do |s|
23
23
  ]
24
24
  s.required_ruby_version = '>= 3.3.0'
25
25
 
26
- s.add_dependency 'logger'
27
-
28
- s.add_development_dependency('activesupport')
29
- s.add_development_dependency('delorean')
30
- s.add_development_dependency('eventmachine', '>= 1.0.4')
31
- s.add_development_dependency('json', '>= 1.8.5')
32
- s.add_development_dependency('open4')
26
+ s.add_development_dependency('minitest', '>= 6')
33
27
  s.add_development_dependency('puma')
34
28
  s.add_development_dependency('rake')
35
- s.add_development_dependency('rdoc')
36
- s.add_development_dependency('rspec', '>= 3.5.0')
37
- s.add_development_dependency('shindo')
38
- s.add_development_dependency('sinatra')
39
- s.add_development_dependency('sinatra-contrib')
40
29
  s.add_development_dependency('webrick')
41
30
 
42
31
  s.metadata = {
@@ -56,14 +56,15 @@ module Excon
56
56
  # @option params [String] :socket The path to the unix socket (required for 'unix://' connections)
57
57
  # @option params [String] :ciphers Only use the specified SSL/TLS cipher suites; use OpenSSL cipher spec format e.g. 'HIGH:!aNULL:!3DES' or 'AES256-SHA:DES-CBC3-SHA'
58
58
  # @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
59
- # @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
60
- # @option params [Fixnum] :retry_interval Set how long to wait between retries. (Default 0)
59
+ # @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4) Only applies to :idempotent requests.
60
+ # @option params [Fixnum] :retry_interval Set how long to wait between retries. (Default 0) Only applies to :idempotent requests.
61
61
  # @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
62
62
  # @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
63
63
  # @option params [Resolv, nil] :resolv_resolver A ready to use +Resolv+ resolver instance.
64
64
  # @option params [Class] :resolver_factory Const of the resolver factory. Defaults to 'Excon::ResolverFactory'
65
65
  def initialize(params = {})
66
66
  @pid = Process.pid
67
+ @sockets_mutex = Mutex.new
67
68
  @data = Excon.defaults.dup
68
69
  # merge does not deep-dup, so make sure headers is not the original
69
70
  @data[:headers] = @data[:headers].dup
@@ -512,21 +513,26 @@ module Excon
512
513
  end
513
514
 
514
515
  def sockets
515
- if @pid != Process.pid
516
- @_excon_sockets&.clear # GC will take care of closing sockets
517
- @pid = Process.pid
518
- end
516
+ # Threads sharing this connection all read and write @_excon_sockets.
517
+ # On a Ruby without a GVL (JRuby) concurrent updates can lose another
518
+ # thread's entry, so that thread opens a new socket mid-request.
519
+ @sockets_mutex.synchronize do
520
+ if @pid != Process.pid
521
+ @_excon_sockets&.clear # GC will take care of closing sockets
522
+ @pid = Process.pid
523
+ end
519
524
 
520
- if @data[:thread_safe_sockets]
521
- @_excon_sockets ||= ObjectSpace::WeakKeyMap.new
525
+ if @data[:thread_safe_sockets]
526
+ @_excon_sockets ||= ObjectSpace::WeakKeyMap.new
522
527
 
523
- # In a multi-threaded world, if the same connection is used by multiple
524
- # threads at the same time to connect to the same destination, they may
525
- # stomp on each other's sockets. This ensures every thread gets their
526
- # own socket cache, within the context of a single connection.
527
- @_excon_sockets[Thread.current] ||= {}
528
- else
529
- @_excon_sockets ||= {}.compare_by_identity
528
+ # In a multi-threaded world, if the same connection is used by multiple
529
+ # threads at the same time to connect to the same destination, they may
530
+ # stomp on each other's sockets. This ensures every thread gets their
531
+ # own socket cache, within the context of a single connection.
532
+ @_excon_sockets[Thread.current] ||= {}
533
+ else
534
+ @_excon_sockets ||= {}.compare_by_identity
535
+ end
530
536
  end
531
537
  end
532
538
 
@@ -545,8 +551,10 @@ module Excon
545
551
  rescue IPAddr::Error
546
552
  false
547
553
  end
554
+ elsif host == '*'
555
+ true
548
556
  else
549
- /(^|\.)#{host}$/.match(@data[:host])
557
+ /(^|\.)#{Regexp.escape(host)}$/.match(@data[:host])
550
558
  end
551
559
  host_match && (port.nil? || port.to_i == @data[:port])
552
560
  end
@@ -1,12 +1,19 @@
1
- require 'logger'
2
-
3
1
  module Excon
4
2
  class LoggingInstrumentor
5
3
 
4
+ # Stand-in for Logger.new($stderr), matching Logger's default format, so
5
+ # excon does not need to depend on the logger gem.
6
+ module StderrLogger
7
+ def self.info(message)
8
+ timestamp = Time.now.strftime('%Y-%m-%dT%H:%M:%S.%6N')
9
+ $stderr.write("I, [#{timestamp} ##{Process.pid}] INFO -- : #{message}\n")
10
+ end
11
+ end
12
+
6
13
  def self.instrument(name, params = {})
7
14
  params = params.dup
8
15
 
9
- logger = params[:logger] || Logger.new($stderr)
16
+ logger = params[:logger] || StderrLogger
10
17
 
11
18
  # reduce duplication/noise of output
12
19
  params.delete(:connection)
@@ -2,6 +2,14 @@
2
2
  module Excon
3
3
  module Middleware
4
4
  class RedirectFollower < Excon::Middleware::Base
5
+ CONTENT_HEADERS = %w[
6
+ Content-Encoding
7
+ Content-Language
8
+ Content-Length
9
+ Content-Location
10
+ Content-Type
11
+ ].freeze
12
+
5
13
  REDACTION_HEADERS = %w[
6
14
  Authorization
7
15
  Cookie
@@ -39,6 +47,10 @@ module Excon
39
47
  if datum.has_key?(:response)
40
48
  case datum[:response][:status]
41
49
  when 301, 302, 303, 307, 308
50
+ location = get_header(datum, 'Location')
51
+ # Without a Location there is nowhere to go, so return the response as is
52
+ return @stack.response_call(datum) unless location
53
+
42
54
  if datum[:redirects_remaining] <= 0
43
55
  raise Excon::Errors::TooManyRedirects
44
56
  end
@@ -47,7 +59,6 @@ module Excon
47
59
 
48
60
  uri_parser = datum[:uri_parser] || Excon.defaults[:uri_parser]
49
61
 
50
- location = get_header(datum, 'Location')
51
62
  base_uri = Excon::Utils.request_uri(datum)
52
63
  uri = uri_parser.join(base_uri, location)
53
64
 
@@ -61,14 +72,18 @@ module Excon
61
72
  params.delete(:stack)
62
73
  params.delete(:user)
63
74
 
75
+ params[:headers] = datum[:headers].dup
64
76
  if [301, 302, 303].include?(response[:status])
65
77
  params[:method] = :get
66
78
  params.delete(:body)
67
- params[:headers].delete('Content-Length')
79
+ # The body is not sent again, so neither are the header fields describing it
80
+ params[:headers].delete_if do |key, _|
81
+ CONTENT_HEADERS.any? { |header| key.to_s.casecmp?(header) }
82
+ end
68
83
  end
69
- params[:headers] = datum[:headers].dup
70
- REDACTION_HEADERS.each do |header|
71
- params[:headers].delete(header)
84
+ # drop sensitive headers in any casing
85
+ params[:headers].delete_if do |key, _|
86
+ REDACTION_HEADERS.any? { |header| key.to_s.casecmp?(header) }
72
87
  end
73
88
  params.merge!(
74
89
  :scheme => uri.scheme || datum[:scheme],
data/lib/excon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Excon
4
- VERSION = "1.7.3"
4
+ VERSION = "1.9.0"
5
5
  end
data/lib/excon.rb CHANGED
@@ -139,6 +139,9 @@ module Excon
139
139
  # @param request_params [Hash<Symbol, >] request params to match against, omitted params match all
140
140
  # @param response_params [Hash<Symbol, >] response params to return or block to call with matched params
141
141
  def stub(request_params = {}, response_params = nil, &block)
142
+ # copy, so later changes to the caller's hash (e.g. stub_for adding
143
+ # :captures) don't change the stored stub
144
+ request_params = request_params.dup
142
145
  if (method = request_params.delete(:method))
143
146
  request_params[:method] = method.to_s.downcase.to_sym
144
147
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excon
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.3
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dpiddy (Dan Peterson)
@@ -12,89 +12,19 @@ cert_chain: []
12
12
  date: 1980-01-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: logger
15
+ name: minitest
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
21
- type: :runtime
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: activesupport
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
- - !ruby/object:Gem::Dependency
43
- name: delorean
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '0'
20
+ version: '6'
49
21
  type: :development
50
22
  prerelease: false
51
23
  version_requirements: !ruby/object:Gem::Requirement
52
24
  requirements:
53
25
  - - ">="
54
26
  - !ruby/object:Gem::Version
55
- version: '0'
56
- - !ruby/object:Gem::Dependency
57
- name: eventmachine
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: 1.0.4
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: 1.0.4
70
- - !ruby/object:Gem::Dependency
71
- name: json
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: 1.8.5
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: 1.8.5
84
- - !ruby/object:Gem::Dependency
85
- name: open4
86
- requirement: !ruby/object:Gem::Requirement
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: '0'
91
- type: :development
92
- prerelease: false
93
- version_requirements: !ruby/object:Gem::Requirement
94
- requirements:
95
- - - ">="
96
- - !ruby/object:Gem::Version
97
- version: '0'
27
+ version: '6'
98
28
  - !ruby/object:Gem::Dependency
99
29
  name: puma
100
30
  requirement: !ruby/object:Gem::Requirement
@@ -123,76 +53,6 @@ dependencies:
123
53
  - - ">="
124
54
  - !ruby/object:Gem::Version
125
55
  version: '0'
126
- - !ruby/object:Gem::Dependency
127
- name: rdoc
128
- requirement: !ruby/object:Gem::Requirement
129
- requirements:
130
- - - ">="
131
- - !ruby/object:Gem::Version
132
- version: '0'
133
- type: :development
134
- prerelease: false
135
- version_requirements: !ruby/object:Gem::Requirement
136
- requirements:
137
- - - ">="
138
- - !ruby/object:Gem::Version
139
- version: '0'
140
- - !ruby/object:Gem::Dependency
141
- name: rspec
142
- requirement: !ruby/object:Gem::Requirement
143
- requirements:
144
- - - ">="
145
- - !ruby/object:Gem::Version
146
- version: 3.5.0
147
- type: :development
148
- prerelease: false
149
- version_requirements: !ruby/object:Gem::Requirement
150
- requirements:
151
- - - ">="
152
- - !ruby/object:Gem::Version
153
- version: 3.5.0
154
- - !ruby/object:Gem::Dependency
155
- name: shindo
156
- requirement: !ruby/object:Gem::Requirement
157
- requirements:
158
- - - ">="
159
- - !ruby/object:Gem::Version
160
- version: '0'
161
- type: :development
162
- prerelease: false
163
- version_requirements: !ruby/object:Gem::Requirement
164
- requirements:
165
- - - ">="
166
- - !ruby/object:Gem::Version
167
- version: '0'
168
- - !ruby/object:Gem::Dependency
169
- name: sinatra
170
- requirement: !ruby/object:Gem::Requirement
171
- requirements:
172
- - - ">="
173
- - !ruby/object:Gem::Version
174
- version: '0'
175
- type: :development
176
- prerelease: false
177
- version_requirements: !ruby/object:Gem::Requirement
178
- requirements:
179
- - - ">="
180
- - !ruby/object:Gem::Version
181
- version: '0'
182
- - !ruby/object:Gem::Dependency
183
- name: sinatra-contrib
184
- requirement: !ruby/object:Gem::Requirement
185
- requirements:
186
- - - ">="
187
- - !ruby/object:Gem::Version
188
- version: '0'
189
- type: :development
190
- prerelease: false
191
- version_requirements: !ruby/object:Gem::Requirement
192
- requirements:
193
- - - ">="
194
- - !ruby/object:Gem::Version
195
- version: '0'
196
56
  - !ruby/object:Gem::Dependency
197
57
  name: webrick
198
58
  requirement: !ruby/object:Gem::Requirement
@@ -247,11 +107,6 @@ files:
247
107
  - lib/excon/socks5_socket.rb
248
108
  - lib/excon/socks5_ssl_socket.rb
249
109
  - lib/excon/ssl_socket.rb
250
- - lib/excon/test/plugin/server/exec.rb
251
- - lib/excon/test/plugin/server/puma.rb
252
- - lib/excon/test/plugin/server/unicorn.rb
253
- - lib/excon/test/plugin/server/webrick.rb
254
- - lib/excon/test/server.rb
255
110
  - lib/excon/unix_socket.rb
256
111
  - lib/excon/utils.rb
257
112
  - lib/excon/version.rb
@@ -1,26 +0,0 @@
1
- module Excon
2
- module Test
3
- module Plugin
4
- module Server
5
- module Exec
6
- def start(app_str = app)
7
- open_process(app_str)
8
- process_stderr = ""
9
- line = ''
10
- until line =~ /\Aready\Z/
11
- line = error.gets
12
- raise process_stderr if line.nil?
13
- process_stderr << line
14
- fatal_time = elapsed_time > timeout
15
- if fatal_time
16
- msg = "executable #{app_str} has taken too long to start"
17
- raise msg
18
- end
19
- end
20
- true
21
- end
22
- end
23
- end
24
- end
25
- end
26
- end
@@ -1,23 +0,0 @@
1
- module Excon
2
- module Test
3
- module Plugin
4
- module Server
5
- module Puma
6
- def start(app_str = app, bind_uri = bind)
7
- open_process(RbConfig.ruby, '-S', 'puma', '-b', bind_uri.to_s, app_str)
8
- process_stderr = ""
9
- line = ''
10
- until line.include?('Use Ctrl-C to stop')
11
- line = read.gets
12
- raise process_stderr if line.nil?
13
- process_stderr << line
14
- fatal_time = elapsed_time > timeout
15
- raise 'puma server has taken too long to start' if fatal_time
16
- end
17
- true
18
- end
19
- end
20
- end
21
- end
22
- end
23
- end
@@ -1,40 +0,0 @@
1
- module Excon
2
- module Test
3
- module Plugin
4
- module Server
5
- module Unicorn
6
- def start(app_str = app, bind_uri = bind)
7
- bind_uri = URI.parse(bind_uri) unless bind_uri.is_a? URI::Generic
8
- is_unix_socket = (bind_uri.scheme == "unix")
9
- if is_unix_socket
10
- bind_str = bind_uri.to_s
11
- else
12
- host = bind_uri.host.gsub(/[\[\]]/, '')
13
- bind_str = "#{host}:#{bind_uri.port}"
14
- end
15
- args = [
16
- RbConfig.ruby,
17
- '-S',
18
- 'unicorn',
19
- '--no-default-middleware',
20
- '-l',
21
- bind_str,
22
- app_str
23
- ]
24
- open_process(*args)
25
- process_stderr = ''
26
- line = ''
27
- until line.include?('worker=0 ready')
28
- line = error.gets
29
- raise process_stderr if line.nil?
30
- process_stderr << line
31
- fatal_time = elapsed_time > timeout
32
- raise 'unicorn server has taken too long to start' if fatal_time
33
- end
34
- true
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,26 +0,0 @@
1
- module Excon
2
- module Test
3
- module Plugin
4
- module Server
5
- module Webrick
6
- def start(app_str = app, bind_uri = bind)
7
- bind_uri = URI.parse(bind_uri) unless bind_uri.is_a? URI::Generic
8
- host = bind_uri.host.gsub(/[\[\]]/, '')
9
- port = bind_uri.port.to_s
10
- open_process(RbConfig.ruby, '-S', 'rackup', '-s', 'webrick', '--host', host, '--port', port, app_str)
11
- process_stderr = ""
12
- line = ''
13
- until line.include?('Server#start')
14
- line = error.gets
15
- raise process_stderr if line.nil?
16
- process_stderr << line
17
- fatal_time = elapsed_time > timeout
18
- raise 'webrick server has taken too long to start' if fatal_time
19
- end
20
- true
21
- end
22
- end
23
- end
24
- end
25
- end
26
- end
@@ -1,99 +0,0 @@
1
- require 'open4'
2
- require 'excon'
3
- require 'excon/test/plugin/server/webrick'
4
- require 'excon/test/plugin/server/unicorn'
5
- require 'excon/test/plugin/server/puma'
6
- require 'excon/test/plugin/server/exec'
7
-
8
-
9
- module Excon
10
- module Test
11
- class Server
12
- attr_accessor :app, :server, :bind, :pid, :read, :write, :error, :started_at, :timeout
13
-
14
- # Methods that must be implemented by a plugin
15
- INSTANCE_REQUIRES = [:start]
16
-
17
- def initialize(args)
18
- # TODO: Validate these args
19
- @server = args.keys.first
20
- @app = args[server]
21
- args[:bind] ||= 'tcp://127.0.0.1:9292'
22
- @bind = URI.parse(args[:bind])
23
- @is_unix_socket = (@bind.scheme == 'unix')
24
- @bind.host = @bind.host.gsub(/[\[\]]/, '') unless @is_unix_socket
25
- if args[:timeout]
26
- @timeout = args[:timeout]
27
- else
28
- @timeout = 20
29
- end
30
- name = @server.to_s.split('_').collect(&:capitalize).join
31
- plug = nested_const_get("Excon::Test::Plugin::Server::#{name}")
32
- self.extend plug
33
- check_implementation(plug)
34
- end
35
-
36
- def open_process(*args)
37
- if RUBY_PLATFORM == 'java'
38
- @pid, @write, @read, @error = IO.popen4(*args)
39
- else
40
- @pid, @write, @read, @error = Open4.popen4(*args)
41
- end
42
- @started_at = Time.now
43
- end
44
-
45
- def elapsed_time
46
- Time.now - started_at
47
- end
48
-
49
- def stop
50
- if RUBY_PLATFORM == 'java'
51
- Process.kill('USR1', pid)
52
- else
53
- Process.kill('KILL', pid)
54
- Process.wait(pid)
55
- end
56
-
57
- if @is_unix_socket
58
- socket = @bind.path
59
- File.delete(socket) if File.exist?(socket)
60
- end
61
-
62
- # TODO: Ensure process is really dead
63
- dump_errors
64
- true
65
- end
66
- def dump_errors
67
- lines = error.read.split($/)
68
- while (line = lines.shift)
69
- case line
70
- when /(ERROR|Error)/
71
- unless line.match?(/(null cert chain|did not return a certificate|SSL_read:: internal error)/)
72
- in_err = true
73
- puts
74
- end
75
- when /^(127|localhost)/
76
- in_err = false
77
- end
78
- puts line if in_err
79
- end
80
- end
81
-
82
- private
83
-
84
- def nested_const_get(namespace)
85
- namespace.split('::').inject(Object) do |mod, klass|
86
- mod.const_get(klass)
87
- end
88
- end
89
-
90
- def check_implementation(plug)
91
- INSTANCE_REQUIRES.each do |m|
92
- unless self.respond_to? m
93
- raise "FATAL: #{plug} does not implement ##{m}"
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end