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 +4 -4
- data/CHANGELOG.md +5 -1
- data/lib/ethon/curls/constants.rb +17 -0
- data/lib/ethon/curls/functions.rb +1 -0
- data/lib/ethon/curls/options.rb +33 -2
- data/lib/ethon/curls/settings.rb +2 -0
- data/lib/ethon/easy/informations.rb +19 -1
- data/lib/ethon/easy/operations.rb +1 -15
- data/lib/ethon/multi/operations.rb +45 -8
- data/lib/ethon/multi.rb +22 -0
- data/lib/ethon/version.rb +1 -1
- data/spec/ethon/easy/informations_spec.rb +22 -0
- data/spec/ethon/easy/mirror_spec.rb +1 -0
- data/spec/ethon/multi/options_spec.rb +113 -0
- data/spec/ethon/multi_spec.rb +130 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04b0080762fbe70bea3b3d5717749a8afb60feb1d843543fc313705dc245b2b8
|
4
|
+
data.tar.gz: fab2787f5a0eebd10ed2d6004ba9b6e3cdc5b60f776e9e5ee0d811529a9a50c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
data/lib/ethon/curls/options.rb
CHANGED
@@ -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, :
|
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, :
|
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
|
data/lib/ethon/curls/settings.rb
CHANGED
@@ -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,
|
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
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
@@ -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
|
data/spec/ethon/multi_spec.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2021-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|