ethon 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|