ethon 0.14.0 → 0.15.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87e1825a32b82c558aded416768e7dffc8a1be6918bca888187b343e17159432
4
- data.tar.gz: 4773483d3d4e5bf46298d55c4d4967622666bf28f6665445d621ab2b4de30213
3
+ metadata.gz: 04b0080762fbe70bea3b3d5717749a8afb60feb1d843543fc313705dc245b2b8
4
+ data.tar.gz: fab2787f5a0eebd10ed2d6004ba9b6e3cdc5b60f776e9e5ee0d811529a9a50c6
5
5
  SHA512:
6
- metadata.gz: bc51f162c86e89ddc1176368a508870e0be217a11027ae17d93149cbc04ae822d2d03ce6e2fa0e15269bbafefbda9e2dbfd1e64aba0b4e3fddbf820b958a3b82
7
- data.tar.gz: 8c755c396544992d6062fe28f3a7a741c3269941665e89995a5f5060d08b4ed8d4f5768bc0601efa7780b1407c49d1839e4557f3ac591abc0529438ad8506163
6
+ metadata.gz: c8d9d0214408c48e702b0d8256d5d6c9d62540d57b9ce142c4a90ff667bbf4db4661e9423ce372d0652c9df4b0147110c3dcc2f317418d3b0e33fe245b4b876b
7
+ data.tar.gz: 1d351ae5e4b45e736a7eef6d4068cec779bfba6a71097c102a3998315aa391789d86725b173eafc5f15d55e587730334ec6c1eb60d4c5eb63b3cacbd11ddddb1
data/CHANGELOG.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  ## Master
4
4
 
5
- [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.12.0...master)
5
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.15.0...master)
6
+
7
+ ## 0.15.0
8
+
9
+ [Full Changelog](https://github.com/typhoeus/ethon/compare/v0.14.0...v0.15.0)
6
10
 
7
11
  ## 0.12.0
8
12
 
@@ -59,5 +59,22 @@ module Ethon
59
59
  VERSION_NTLM_WB = (1<<15) # NTLM delegating to winbind helper
60
60
  VERSION_HTTP2 = (1<<16) # HTTP2 support built
61
61
  VERSION_GSSAPI = (1<<17) # GSS-API is supported
62
+
63
+ SOCKET_BAD = -1
64
+ SOCKET_TIMEOUT = SOCKET_BAD
65
+
66
+ PollAction = enum(:poll_action, [
67
+ :none,
68
+ :in,
69
+ :out,
70
+ :inout,
71
+ :remove
72
+ ])
73
+
74
+ SocketReadiness = bitmask(:socket_readiness, [
75
+ :in, # CURL_CSELECT_IN - 0x01 (bit 0)
76
+ :out, # CURL_CSELECT_OUT - 0x02 (bit 1)
77
+ :err, # CURL_CSELECT_ERR - 0x04 (bit 2)
78
+ ])
62
79
  end
63
80
  end
@@ -36,6 +36,7 @@ module Ethon
36
36
  base.attach_function :multi_fdset, :curl_multi_fdset, [:pointer, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::FDSet.ptr, :pointer], :multi_code
37
37
  base.attach_function :multi_strerror, :curl_multi_strerror, [:int], :string
38
38
  base.attach_function :multi_setopt, :curl_multi_setopt, [:pointer, :multi_option, :varargs], :multi_code
39
+ base.attach_function :multi_socket_action, :curl_multi_socket_action, [:pointer, :int, :socket_readiness, :pointer], :multi_code
39
40
 
40
41
  base.attach_function :version, :curl_version, [], :string
41
42
  base.attach_function :version_info, :curl_version_info, [], Curl::VersionInfoData.ptr
@@ -82,6 +82,12 @@ module Ethon
82
82
  when :callback
83
83
  va_type=:callback
84
84
  raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? Proc
85
+ when :socket_callback
86
+ va_type=:socket_callback
87
+ raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? Proc
88
+ when :timer_callback
89
+ va_type=:timer_callback
90
+ raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? Proc
85
91
  when :debug_callback
86
92
  va_type=:debug_callback
87
93
  raise Errors::InvalidValue.new(option,value) unless value.nil? or value.is_a? Proc
@@ -137,6 +143,8 @@ module Ethon
137
143
  :dontuse_object => :objectpoint, # An object we don't support (e.g. FILE*)
138
144
  :cbdata => :objectpoint,
139
145
  :callback => :functionpoint,
146
+ :socket_callback => :functionpoint,
147
+ :timer_callback => :functionpoint,
140
148
  :debug_callback => :functionpoint,
141
149
  :progress_callback => :functionpoint,
142
150
  :off_t => :off_t,
@@ -208,10 +216,10 @@ module Ethon
208
216
  # Documentation @ http://curl.haxx.se/libcurl/c/curl_multi_setopt.html
209
217
  option_type :multi
210
218
 
211
- option :multi, :socketfunction, :callback, 1
219
+ option :multi, :socketfunction, :socket_callback, 1
212
220
  option :multi, :socketdata, :cbdata, 2
213
221
  option :multi, :pipelining, :int, 3
214
- option :multi, :timerfunction, :callback, 4
222
+ option :multi, :timerfunction, :timer_callback, 4
215
223
  option :multi, :timerdata, :cbdata, 5
216
224
  option :multi, :maxconnects, :int, 6
217
225
  option :multi, :max_host_connections, :int, 7
@@ -450,6 +458,29 @@ module Ethon
450
458
  option :easy, :gssapi_delegation, :bitmask, 210, [:none, :policy_flag, :flag]
451
459
  option :easy, :pinnedpublickey, :string, 230
452
460
  option_alias :easy, :pinnedpublickey, :pinned_public_key
461
+ ## PROXY SSL OPTIONS
462
+ option :easy, :proxy_cainfo, :string, 246
463
+ option :easy, :proxy_capath, :string, 247
464
+ option :easy, :proxy_ssl_verifypeer, :bool, 248
465
+ option :easy, :proxy_ssl_verifyhost, :int, 249
466
+ option :easy, :proxy_sslversion, :enum, 250, [:default, :tlsv1, :sslv2, :sslv3, :tlsv1_0, :tlsv1_1, :tlsv1_2]
467
+ option :easy, :proxy_tlsauth_username, :string, 251
468
+ option :easy, :proxy_tlsauth_password, :string, 252
469
+ option :easy, :proxy_tlsauth_type, :enum, 253, [:none, :srp]
470
+ option :easy, :proxy_sslcert, :string, 254
471
+ option :easy, :proxy_sslcerttype, :string, 255
472
+ option :easy, :proxy_sslkey, :string, 256
473
+ option :easy, :proxy_sslkeytype, :string, 257
474
+ option :easy, :proxy_keypasswd, :string, 258
475
+ option_alias :easy, :proxy_keypasswd, :proxy_sslcertpasswd
476
+ option_alias :easy, :proxy_keypasswd, :proxy_sslkeypasswd
477
+ option :easy, :proxy_ssl_cipher_list, :string, 259
478
+ option :easy, :proxy_crlfile, :string, 260
479
+ option :easy, :proxy_ssl_options, :bitmask, 261, [nil, :allow_beast]
480
+ option :easy, :pre_proxy, :string, 262
481
+ option :easy, :proxy_pinnedpublickey, :string, 263
482
+ option_alias :easy, :proxy_pinnedpublickey, :proxy_pinned_public_key
483
+ option :easy, :proxy_issuercert, :string, 296
453
484
  ## SSH OPTIONS
454
485
  option :easy, :ssh_auth_types, :bitmask, 151, [:none, :publickey, :password, :host, :keyboard, :agent, {:any => [:all], :default => [:any]}]
455
486
  option :easy, :ssh_host_public_key_md5, :string, 162
@@ -2,6 +2,8 @@
2
2
  module Ethon
3
3
  module Curl
4
4
  callback :callback, [:pointer, :size_t, :size_t, :pointer], :size_t
5
+ callback :socket_callback, [:pointer, :int, :poll_action, :pointer, :pointer], :multi_code
6
+ callback :timer_callback, [:pointer, :long, :pointer], :multi_code
5
7
  callback :debug_callback, [:pointer, :debug_info_type, :pointer, :size_t, :pointer], :int
6
8
  callback :progress_callback, [:pointer, :long_long, :long_long, :long_long, :long_long], :int
7
9
  ffi_lib_flags :now, :global
@@ -71,7 +71,25 @@ module Ethon
71
71
 
72
72
  # Return the total number of redirections that were
73
73
  # actually followed.
74
- :redirect_count => :long
74
+ :redirect_count => :long,
75
+
76
+ # Return the bytes, the total amount of bytes that were uploaded
77
+ :size_upload => :double,
78
+
79
+ # Return the bytes, the total amount of bytes that were downloaded.
80
+ # The amount is only for the latest transfer and will be reset again
81
+ # for each new transfer. This counts actual payload data, what's
82
+ # also commonly called body. All meta and header data are excluded
83
+ # and will not be counted in this number.
84
+ :size_download => :double,
85
+
86
+ # Return the bytes/second, the average upload speed that curl
87
+ # measured for the complete upload
88
+ :speed_upload => :double,
89
+
90
+ # Return the bytes/second, the average download speed that curl
91
+ # measured for the complete download
92
+ :speed_download => :double
75
93
  }
76
94
 
77
95
  AVAILABLE_INFORMATIONS.each do |name, type|
@@ -4,20 +4,6 @@ module Ethon
4
4
  # This module contains the logic to prepare and perform
5
5
  # an easy.
6
6
  module Operations
7
-
8
- class PointerHelper
9
- class<<self
10
- def synchronize( &block )
11
- (@mutex ||= Mutex.new).synchronize( &block )
12
- end
13
-
14
- def release( pointer )
15
- synchronize { Curl.easy_cleanup pointer }
16
- end
17
- end
18
- synchronize{}
19
- end
20
-
21
7
  # Returns a pointer to the curl easy handle.
22
8
  #
23
9
  # @example Return the handle.
@@ -25,7 +11,7 @@ module Ethon
25
11
  #
26
12
  # @return [ FFI::Pointer ] A pointer to the curl easy handle.
27
13
  def handle
28
- @handle ||= FFI::AutoPointer.new(Curl.easy_init, PointerHelper.method(:release) )
14
+ @handle ||= FFI::AutoPointer.new(Curl.easy_init, Curl.method(:easy_cleanup))
29
15
  end
30
16
 
31
17
  # Sets a pointer to the curl easy handle.
@@ -24,12 +24,16 @@ module Ethon
24
24
  #
25
25
  # @return [ void ]
26
26
  def init_vars
27
- @timeout = ::FFI::MemoryPointer.new(:long)
28
- @timeval = Curl::Timeval.new
29
- @fd_read = Curl::FDSet.new
30
- @fd_write = Curl::FDSet.new
31
- @fd_excep = Curl::FDSet.new
32
- @max_fd = ::FFI::MemoryPointer.new(:int)
27
+ if @execution_mode == :perform
28
+ @timeout = ::FFI::MemoryPointer.new(:long)
29
+ @timeval = Curl::Timeval.new
30
+ @fd_read = Curl::FDSet.new
31
+ @fd_write = Curl::FDSet.new
32
+ @fd_excep = Curl::FDSet.new
33
+ @max_fd = ::FFI::MemoryPointer.new(:int)
34
+ elsif @execution_mode == :socket_action
35
+ @running_count_pointer = FFI::MemoryPointer.new(:int)
36
+ end
33
37
  end
34
38
 
35
39
  # Perform multi.
@@ -39,6 +43,8 @@ module Ethon
39
43
  # @example Perform multi.
40
44
  # multi.perform
41
45
  def perform
46
+ ensure_execution_mode(:perform)
47
+
42
48
  Ethon.logger.debug(STARTED_MULTI)
43
49
  while ongoing?
44
50
  run
@@ -67,9 +73,38 @@ module Ethon
67
73
  )
68
74
  end
69
75
 
70
- private
76
+ # Continue execution with an external IO loop.
77
+ #
78
+ # @example When no sockets are ready yet, or to begin.
79
+ # multi.socket_action
80
+ #
81
+ # @example When a socket is readable
82
+ # multi.socket_action(io_object, [:in])
83
+ #
84
+ # @example When a socket is readable and writable
85
+ # multi.socket_action(io_object, [:in, :out])
86
+ #
87
+ # @return [ Symbol ] The Curl.multi_socket_action return code.
88
+ def socket_action(io = nil, readiness = 0)
89
+ ensure_execution_mode(:socket_action)
90
+
91
+ fd = if io.nil?
92
+ ::Ethon::Curl::SOCKET_TIMEOUT
93
+ elsif io.is_a?(Integer)
94
+ io
95
+ else
96
+ io.fileno
97
+ end
98
+
99
+ code = Curl.multi_socket_action(handle, fd, readiness, @running_count_pointer)
100
+ @running_count = @running_count_pointer.read_int
71
101
 
72
- # Return wether the multi still requests or not.
102
+ check
103
+
104
+ code
105
+ end
106
+
107
+ # Return whether the multi still contains requests or not.
73
108
  #
74
109
  # @example Return if ongoing.
75
110
  # multi.ongoing?
@@ -79,6 +114,8 @@ module Ethon
79
114
  easy_handles.size > 0 || (!defined?(@running_count) || running_count > 0)
80
115
  end
81
116
 
117
+ private
118
+
82
119
  # Get timeout.
83
120
  #
84
121
  # @example Get timeout.
data/lib/ethon/multi.rb CHANGED
@@ -74,10 +74,21 @@ module Ethon
74
74
  # is 1 will not be treated as unlimited. Instead it will open only 1
75
75
  # connection and try to pipeline on it.
76
76
  # (Added in 7.30.0)
77
+ # @option options :execution_mode [Boolean]
78
+ # Either :perform (default) or :socket_action, specifies the usage
79
+ # method that will be used on this multi object. The default :perform
80
+ # mode provides a #perform function that uses curl_multi_perform
81
+ # behind the scenes to automatically continue execution until all
82
+ # requests have completed. The :socket_action mode provides an API
83
+ # that allows the {Multi} object to be integrated into an external
84
+ # IO loop, by calling #socket_action and responding to the
85
+ # socketfunction and timerfunction callbacks, using the underlying
86
+ # curl_multi_socket_action semantics.
77
87
  #
78
88
  # @return [ Multi ] The new multi.
79
89
  def initialize(options = {})
80
90
  Curl.init
91
+ @execution_mode = options.delete(:execution_mode) || :perform
81
92
  set_attributes(options)
82
93
  init_vars
83
94
  end
@@ -100,5 +111,16 @@ module Ethon
100
111
  method("#{key}=").call(value)
101
112
  end
102
113
  end
114
+
115
+ private
116
+
117
+ # Internal function to gate functions to a specific execution mode
118
+ #
119
+ # @raise ArgumentError
120
+ #
121
+ # @api private
122
+ def ensure_execution_mode(expected_mode)
123
+ raise ArgumentError, "Expected the Multi to be in #{expected_mode} but it was in #{@execution_mode}" if expected_mode != @execution_mode
124
+ end
103
125
  end
104
126
  end
data/lib/ethon/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  module Ethon
3
3
 
4
4
  # Ethon version.
5
- VERSION = '0.14.0'
5
+ VERSION = '0.15.0'
6
6
  end
@@ -94,5 +94,27 @@ describe Ethon::Easy::Informations do
94
94
  end
95
95
  end
96
96
 
97
+ describe "#size_upload" do
98
+ it "returns float" do
99
+ expect(easy.size_upload).to be_a(Float)
100
+ end
101
+ end
102
+
103
+ describe "#size_download" do
104
+ it "returns float" do
105
+ expect(easy.size_download).to be_a(Float)
106
+ end
107
+ end
97
108
 
109
+ describe "#speed_upload" do
110
+ it "returns float" do
111
+ expect(easy.speed_upload).to be_a(Float)
112
+ end
113
+ end
114
+
115
+ describe "#speed_download" do
116
+ it "returns float" do
117
+ expect(easy.speed_download).to be_a(Float)
118
+ end
119
+ end
98
120
  end
@@ -10,6 +10,7 @@ describe Ethon::Easy::Mirror do
10
10
  :return_code, :response_code, :response_body, :response_headers,
11
11
  :total_time, :starttransfer_time, :appconnect_time,
12
12
  :pretransfer_time, :connect_time, :namelookup_time, :redirect_time,
13
+ :size_upload, :size_download, :speed_upload, :speed_upload,
13
14
  :effective_url, :primary_ip, :redirect_count, :debug_info
14
15
  ].each do |name|
15
16
  it "contains #{name}" do
@@ -20,6 +20,119 @@ describe Ethon::Multi::Options do
20
20
  end
21
21
  end
22
22
 
23
+ context "socket_action mode" do
24
+ let(:multi) { Ethon::Multi.new(execution_mode: :socket_action) }
25
+
26
+ describe "#socketfunction callbacks" do
27
+ it "allows multi_code return values" do
28
+ calls = []
29
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
30
+ calls << what
31
+ :ok
32
+ end
33
+
34
+ easy = Ethon::Easy.new
35
+ easy.url = "http://localhost:3001/?delay=1"
36
+ multi.add(easy)
37
+ expect(calls).to eq([])
38
+ 5.times do
39
+ multi.socket_action
40
+ break unless calls.empty?
41
+ sleep 0.1
42
+ end
43
+ expect(calls.last).to eq(:in).or(eq(:out))
44
+ multi.delete(easy)
45
+ expect(calls.last).to eq(:remove)
46
+ end
47
+
48
+ it "allows integer return values (compatibility)" do
49
+ called = false
50
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
51
+ called = true
52
+ 0
53
+ end
54
+
55
+ easy = Ethon::Easy.new
56
+ easy.url = "http://localhost:3001/?delay=1"
57
+ multi.add(easy)
58
+ 5.times do
59
+ multi.socket_action
60
+ break if called
61
+ sleep 0.1
62
+ end
63
+ multi.delete(easy)
64
+
65
+ expect(called).to be_truthy
66
+ end
67
+
68
+ it "errors on invalid return codes" do
69
+ called = false
70
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
71
+ called = true
72
+ "hi"
73
+ end
74
+
75
+ easy = Ethon::Easy.new
76
+ easy.url = "http://localhost:3001/?delay=1"
77
+ multi.add(easy)
78
+ expect {
79
+ 5.times do
80
+ multi.socket_action
81
+ break if called
82
+ sleep 0.1
83
+ end
84
+ }.to raise_error(ArgumentError)
85
+ expect { multi.delete(easy) }.to raise_error(ArgumentError)
86
+ end
87
+ end
88
+
89
+ describe "#timerfunction callbacks" do
90
+ it "allows multi_code return values" do
91
+ calls = []
92
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
93
+ calls << timeout_ms
94
+ :ok
95
+ end
96
+
97
+ easy = Ethon::Easy.new
98
+ easy.url = "http://localhost:3001/?delay=1"
99
+ multi.add(easy)
100
+ expect(calls.last).to be >= 0 # adds an immediate timeout
101
+
102
+ multi.delete(easy)
103
+ expect(calls.last).to eq(-1) # cancels the timer
104
+ end
105
+
106
+ it "allows integer return values (compatibility)" do
107
+ called = false
108
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
109
+ called = true
110
+ 0
111
+ end
112
+
113
+ easy = Ethon::Easy.new
114
+ easy.url = "http://localhost:3001/?delay=1"
115
+ multi.add(easy)
116
+ multi.socket_action
117
+ multi.delete(easy)
118
+
119
+ expect(called).to be_truthy
120
+ end
121
+
122
+ it "errors on invalid return codes" do
123
+ called = false
124
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
125
+ called = true
126
+ "hi"
127
+ end
128
+
129
+ easy = Ethon::Easy.new
130
+ easy.url = "http://localhost:3001/?delay=1"
131
+ expect { multi.add(easy) }.to raise_error(ArgumentError)
132
+ end
133
+ end
134
+ end
135
+
23
136
  describe "#value_for" do
24
137
  context "when option in bool" do
25
138
  context "when value true" do
@@ -8,6 +8,16 @@ describe Ethon::Multi do
8
8
  Ethon::Multi.new
9
9
  end
10
10
 
11
+ context "with default options" do
12
+ it "allows running #perform with the default execution_mode" do
13
+ Ethon::Multi.new.perform
14
+ end
15
+
16
+ it "refuses to run #socket_action" do
17
+ expect { Ethon::Multi.new.socket_action }.to raise_error(ArgumentError)
18
+ end
19
+ end
20
+
11
21
  context "when options not empty" do
12
22
  context "when pipelining is set" do
13
23
  let(:options) { { :pipelining => true } }
@@ -17,6 +27,126 @@ describe Ethon::Multi do
17
27
  Ethon::Multi.new(options)
18
28
  end
19
29
  end
30
+
31
+ context "when execution_mode option is :socket_action" do
32
+ let(:options) { { :execution_mode => :socket_action } }
33
+ let(:multi) { Ethon::Multi.new(options) }
34
+
35
+ it "refuses to run #perform" do
36
+ expect { multi.perform }.to raise_error(ArgumentError)
37
+ end
38
+
39
+ it "allows running #socket_action" do
40
+ multi.socket_action
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#socket_action" do
47
+ let(:options) { { :execution_mode => :socket_action } }
48
+ let(:select_state) { { :readers => [], :writers => [], :timeout => 0 } }
49
+ let(:multi) {
50
+ multi = Ethon::Multi.new(options)
51
+ multi.timerfunction = proc do |handle, timeout_ms, userp|
52
+ timeout_ms = nil if timeout_ms == -1
53
+ select_state[:timeout] = timeout_ms
54
+ :ok
55
+ end
56
+ multi.socketfunction = proc do |handle, sock, what, userp, socketp|
57
+ case what
58
+ when :remove
59
+ select_state[:readers].delete(sock)
60
+ select_state[:writers].delete(sock)
61
+ when :in
62
+ select_state[:readers].push(sock) unless select_state[:readers].include? sock
63
+ select_state[:writers].delete(sock)
64
+ when :out
65
+ select_state[:readers].delete(sock)
66
+ select_state[:writers].push(sock) unless select_state[:writers].include? sock
67
+ when :inout
68
+ select_state[:readers].push(sock) unless select_state[:readers].include? sock
69
+ select_state[:writers].push(sock) unless select_state[:writers].include? sock
70
+ else
71
+ raise ArgumentError, "invalid value for 'what' in socketfunction callback"
72
+ end
73
+ :ok
74
+ end
75
+ multi
76
+ }
77
+
78
+ def fds_to_ios(fds)
79
+ fds.map do |fd|
80
+ IO.for_fd(fd).tap { |io| io.autoclose = false }
81
+ end
82
+ end
83
+
84
+ def perform_socket_action_until_complete
85
+ multi.socket_action # start things off
86
+
87
+ while multi.ongoing?
88
+ readers, writers, _ = IO.select(
89
+ fds_to_ios(select_state[:readers]),
90
+ fds_to_ios(select_state[:writers]),
91
+ [],
92
+ select_state[:timeout]
93
+ )
94
+
95
+ to_notify = Hash.new { |hash, key| hash[key] = [] }
96
+ unless readers.nil?
97
+ readers.each do |reader|
98
+ to_notify[reader] << :in
99
+ end
100
+ end
101
+ unless writers.nil?
102
+ writers.each do |writer|
103
+ to_notify[writer] << :out
104
+ end
105
+ end
106
+
107
+ to_notify.each do |io, readiness|
108
+ multi.socket_action(io, readiness)
109
+ end
110
+
111
+ # if we didn't have anything to notify, then we timed out
112
+ multi.socket_action if to_notify.empty?
113
+ end
114
+ ensure
115
+ multi.easy_handles.dup.each do |h|
116
+ multi.delete(h)
117
+ end
118
+ end
119
+
120
+ it "supports an end-to-end request" do
121
+ easy = Ethon::Easy.new
122
+ easy.url = "http://localhost:3001/"
123
+ multi.add(easy)
124
+
125
+ perform_socket_action_until_complete
126
+
127
+ expect(multi.ongoing?).to eq(false)
128
+ end
129
+
130
+ it "supports multiple concurrent requests" do
131
+ handles = []
132
+ 10.times do
133
+ easy = Ethon::Easy.new
134
+ easy.url = "http://localhost:3001/?delay=1"
135
+ multi.add(easy)
136
+ handles << easy
137
+ end
138
+
139
+ start = Time.now
140
+ perform_socket_action_until_complete
141
+ duration = Time.now - start
142
+
143
+ # these should have happened concurrently
144
+ expect(duration).to be < 2
145
+ expect(multi.ongoing?).to eq(false)
146
+
147
+ handles.each do |handle|
148
+ expect(handle.response_code).to eq(200)
149
+ end
20
150
  end
21
151
  end
22
152
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans Hasselberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-26 00:00:00.000000000 Z
11
+ date: 2021-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi