ethon 0.14.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -2
- data/CHANGELOG.md +8 -1
- data/README.md +23 -0
- data/lib/ethon/curls/classes.rb +12 -2
- data/lib/ethon/curls/constants.rb +17 -0
- data/lib/ethon/curls/functions.rb +1 -0
- data/lib/ethon/curls/options.rb +36 -4
- data/lib/ethon/curls/settings.rb +2 -0
- data/lib/ethon/easy/callbacks.rb +2 -1
- data/lib/ethon/easy/informations.rb +22 -1
- data/lib/ethon/easy/operations.rb +1 -15
- data/lib/ethon/easy/response_callbacks.rb +6 -1
- 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/callbacks_spec.rb +22 -0
- data/spec/ethon/easy/informations_spec.rb +28 -0
- data/spec/ethon/easy/mirror_spec.rb +2 -1
- data/spec/ethon/easy/operations_spec.rb +3 -0
- data/spec/ethon/multi/options_spec.rb +113 -0
- data/spec/ethon/multi_spec.rb +130 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30f75feeae963db1b4648a0b88e028361c78fda7aec43524a35f93b287ab2305
|
4
|
+
data.tar.gz: eb0ccd002c324dedaaceaa001172a61c520cd58e34d1a92440182faada17df74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6ec09378cd37ec552caee9a9153fb9e0582a770b18da882ec27f59028799885e1a9d9d7c496f1cfb4803ba3eb14bdd5e508f930cdc9523d16747d2a87ba1dfb
|
7
|
+
data.tar.gz: 77a50827108e8c6bc44f293bae5c2bfe6e871c94f40f93904ff611245143ef7900cf4cc2aa8a1511d0797dd80c03578e06b4e256f929deab544c73f9ef0d9e33
|
data/.github/workflows/ruby.yml
CHANGED
@@ -20,7 +20,7 @@ jobs:
|
|
20
20
|
fail-fast: false
|
21
21
|
matrix:
|
22
22
|
os: [ubuntu, macos]
|
23
|
-
ruby-version: [2.5, 2.6, 2.7, 3.0, head, debug, truffleruby
|
23
|
+
ruby-version: [2.5, 2.6, 2.7, 3.0, head, debug, truffleruby]
|
24
24
|
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
|
25
25
|
steps:
|
26
26
|
- uses: actions/checkout@v2
|
@@ -30,7 +30,7 @@ jobs:
|
|
30
30
|
then
|
31
31
|
brew install curl
|
32
32
|
else
|
33
|
-
sudo apt install -y libcurl4-openssl-dev
|
33
|
+
sudo apt update && sudo apt install -y --no-install-recommends libcurl4-openssl-dev
|
34
34
|
fi
|
35
35
|
- name: Set up Ruby
|
36
36
|
uses: ruby/setup-ruby@v1
|
data/CHANGELOG.md
CHANGED
@@ -2,7 +2,14 @@
|
|
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
|
+
* Added `redirect_url` value to available informations and `Easy::Mirror`.
|
8
|
+
([Adrien Rey-Jarthon](https://github.com/jarthod)
|
9
|
+
|
10
|
+
## 0.15.0
|
11
|
+
|
12
|
+
[Full Changelog](https://github.com/typhoeus/ethon/compare/v0.14.0...v0.15.0)
|
6
13
|
|
7
14
|
## 0.12.0
|
8
15
|
|
data/README.md
CHANGED
@@ -70,6 +70,29 @@ easy.perform
|
|
70
70
|
This is really handy when making requests since you don't have to care about setting
|
71
71
|
everything up correctly.
|
72
72
|
|
73
|
+
## Http2
|
74
|
+
Standard http2 servers require the client to connect once and create a session (multi) and then add simple requests to the multi handler.
|
75
|
+
The `perform` method then takes all the requests in the multi handler and sends them to the server.
|
76
|
+
|
77
|
+
See the following example
|
78
|
+
```ruby
|
79
|
+
multi = Ethon::Multi.new
|
80
|
+
easy = Ethon::Easy.new
|
81
|
+
|
82
|
+
easy.http_request("www.example.com/get", :get, { http_version: :httpv2_0 })
|
83
|
+
|
84
|
+
# Sending a request with http version 2 will send an Upgrade header to the server, which many older servers will not support
|
85
|
+
# See below for more info: https://everything.curl.dev/http/http2
|
86
|
+
# If this is a problem, send the below:
|
87
|
+
easy.http_request("www.example.com/get", :get, { http_version: :httpv2_prior_knowledge })
|
88
|
+
|
89
|
+
# To set the server to use http2 with https and http1 with http, send the following:
|
90
|
+
easy.http_request("www.example.com/get", :get, { http_version: :httpv2_tls }
|
91
|
+
|
92
|
+
multi.add(easy)
|
93
|
+
multi.perform
|
94
|
+
```
|
95
|
+
|
73
96
|
## LICENSE
|
74
97
|
|
75
98
|
(The MIT License)
|
data/lib/ethon/curls/classes.rb
CHANGED
@@ -32,8 +32,18 @@ module Ethon
|
|
32
32
|
|
33
33
|
def clear; self[:fd_count] = 0; end
|
34
34
|
else
|
35
|
-
#
|
36
|
-
FD_SETSIZE =
|
35
|
+
# https://github.com/typhoeus/ethon/issues/182
|
36
|
+
FD_SETSIZE = begin
|
37
|
+
# Allow to override the (new) default cap
|
38
|
+
if ENV['ETHON_FD_SIZE']
|
39
|
+
ENV['ETHON_FD_SIZE']
|
40
|
+
|
41
|
+
# auto-detect ulimit, but cap at 2^16
|
42
|
+
else
|
43
|
+
[::Ethon::Libc.getdtablesize, 65_536].min
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
37
47
|
layout :fds_bits, [:long, FD_SETSIZE / ::FFI::Type::LONG.size]
|
38
48
|
|
39
49
|
# :nodoc:
|
@@ -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
|
@@ -299,6 +307,7 @@ module Ethon
|
|
299
307
|
option :easy, :port, :int, 3
|
300
308
|
option :easy, :tcp_nodelay, :bool, 121
|
301
309
|
option :easy, :address_scope, :int, 171
|
310
|
+
option :easy, :tcp_fastopen, :bool, 212
|
302
311
|
option :easy, :tcp_keepalive, :bool, 213
|
303
312
|
option :easy, :tcp_keepidle, :int, 214
|
304
313
|
option :easy, :tcp_keepintvl, :int, 215
|
@@ -344,7 +353,7 @@ module Ethon
|
|
344
353
|
option :easy, :cookiesession, :bool, 96
|
345
354
|
option :easy, :cookielist, :string, 135
|
346
355
|
option :easy, :httpget, :bool, 80
|
347
|
-
option :easy, :http_version, :enum, 84, [:none, :httpv1_0, :httpv1_1, :httpv2_0]
|
356
|
+
option :easy, :http_version, :enum, 84, [:none, :httpv1_0, :httpv1_1, :httpv2_0, :httpv2_tls, :httpv2_prior_knowledge]
|
348
357
|
option :easy, :ignore_content_length, :bool, 136
|
349
358
|
option :easy, :http_content_decoding, :bool, 158
|
350
359
|
option :easy, :http_transfer_decoding, :bool, 157
|
@@ -432,7 +441,7 @@ module Ethon
|
|
432
441
|
option_alias :easy, :keypasswd, :sslkeypasswd
|
433
442
|
option :easy, :sslengine, :string, 89
|
434
443
|
option :easy, :sslengine_default, :none, 90
|
435
|
-
option :easy, :sslversion, :enum, 32, [:default, :tlsv1, :sslv2, :sslv3, :tlsv1_0, :tlsv1_1, :tlsv1_2]
|
444
|
+
option :easy, :sslversion, :enum, 32, [:default, :tlsv1, :sslv2, :sslv3, :tlsv1_0, :tlsv1_1, :tlsv1_2, :tlsv1_3]
|
436
445
|
option :easy, :ssl_verifypeer, :bool, 64
|
437
446
|
option :easy, :cainfo, :string, 65
|
438
447
|
option :easy, :issuercert, :string, 170
|
@@ -450,6 +459,29 @@ module Ethon
|
|
450
459
|
option :easy, :gssapi_delegation, :bitmask, 210, [:none, :policy_flag, :flag]
|
451
460
|
option :easy, :pinnedpublickey, :string, 230
|
452
461
|
option_alias :easy, :pinnedpublickey, :pinned_public_key
|
462
|
+
## PROXY SSL OPTIONS
|
463
|
+
option :easy, :proxy_cainfo, :string, 246
|
464
|
+
option :easy, :proxy_capath, :string, 247
|
465
|
+
option :easy, :proxy_ssl_verifypeer, :bool, 248
|
466
|
+
option :easy, :proxy_ssl_verifyhost, :int, 249
|
467
|
+
option :easy, :proxy_sslversion, :enum, 250, [:default, :tlsv1, :sslv2, :sslv3, :tlsv1_0, :tlsv1_1, :tlsv1_2, :tlsv1_3]
|
468
|
+
option :easy, :proxy_tlsauth_username, :string, 251
|
469
|
+
option :easy, :proxy_tlsauth_password, :string, 252
|
470
|
+
option :easy, :proxy_tlsauth_type, :enum, 253, [:none, :srp]
|
471
|
+
option :easy, :proxy_sslcert, :string, 254
|
472
|
+
option :easy, :proxy_sslcerttype, :string, 255
|
473
|
+
option :easy, :proxy_sslkey, :string, 256
|
474
|
+
option :easy, :proxy_sslkeytype, :string, 257
|
475
|
+
option :easy, :proxy_keypasswd, :string, 258
|
476
|
+
option_alias :easy, :proxy_keypasswd, :proxy_sslcertpasswd
|
477
|
+
option_alias :easy, :proxy_keypasswd, :proxy_sslkeypasswd
|
478
|
+
option :easy, :proxy_ssl_cipher_list, :string, 259
|
479
|
+
option :easy, :proxy_crlfile, :string, 260
|
480
|
+
option :easy, :proxy_ssl_options, :bitmask, 261, [nil, :allow_beast]
|
481
|
+
option :easy, :pre_proxy, :string, 262
|
482
|
+
option :easy, :proxy_pinnedpublickey, :string, 263
|
483
|
+
option_alias :easy, :proxy_pinnedpublickey, :proxy_pinned_public_key
|
484
|
+
option :easy, :proxy_issuercert, :string, 296
|
453
485
|
## SSH OPTIONS
|
454
486
|
option :easy, :ssh_auth_types, :bitmask, 151, [:none, :publickey, :password, :host, :keyboard, :agent, {:any => [:all], :default => [:any]}]
|
455
487
|
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
|
data/lib/ethon/easy/callbacks.rb
CHANGED
@@ -52,8 +52,9 @@ module Ethon
|
|
52
52
|
# @return [ Proc ] The callback.
|
53
53
|
def header_write_callback
|
54
54
|
@header_write_callback ||= proc {|stream, size, num, object|
|
55
|
+
result = headers
|
55
56
|
@response_headers << stream.read_string(size * num)
|
56
|
-
size * num
|
57
|
+
result != :abort ? size * num : -1
|
57
58
|
}
|
58
59
|
end
|
59
60
|
|
@@ -71,7 +71,28 @@ 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
|
+
# URL a redirect would take you to, had you enabled redirects (Added in 7.18.2)
|
77
|
+
:redirect_url => :string,
|
78
|
+
|
79
|
+
# Return the bytes, the total amount of bytes that were uploaded
|
80
|
+
:size_upload => :double,
|
81
|
+
|
82
|
+
# Return the bytes, the total amount of bytes that were downloaded.
|
83
|
+
# The amount is only for the latest transfer and will be reset again
|
84
|
+
# for each new transfer. This counts actual payload data, what's
|
85
|
+
# also commonly called body. All meta and header data are excluded
|
86
|
+
# and will not be counted in this number.
|
87
|
+
:size_download => :double,
|
88
|
+
|
89
|
+
# Return the bytes/second, the average upload speed that curl
|
90
|
+
# measured for the complete upload
|
91
|
+
:speed_upload => :double,
|
92
|
+
|
93
|
+
# Return the bytes/second, the average download speed that curl
|
94
|
+
# measured for the complete download
|
95
|
+
:speed_download => :double
|
75
96
|
}
|
76
97
|
|
77
98
|
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.
|
@@ -43,7 +43,12 @@ module Ethon
|
|
43
43
|
return if @headers_called
|
44
44
|
@headers_called = true
|
45
45
|
if defined?(@on_headers) and not @on_headers.nil?
|
46
|
-
|
46
|
+
result = nil
|
47
|
+
@on_headers.each do |callback|
|
48
|
+
result = callback.call(self)
|
49
|
+
break if result == :abort
|
50
|
+
end
|
51
|
+
result
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
@@ -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
@@ -56,4 +56,26 @@ describe Ethon::Easy::Callbacks do
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
|
+
|
60
|
+
describe "#header_write_callback" do
|
61
|
+
let(:header_write_callback) { easy.instance_variable_get(:@header_write_callback) }
|
62
|
+
let(:stream) { double(:read_string => "") }
|
63
|
+
context "when header returns not :abort" do
|
64
|
+
it "returns number bigger than 0" do
|
65
|
+
expect(header_write_callback.call(stream, 1, 1, nil) > 0).to be(true)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when header returns :abort" do
|
70
|
+
before do
|
71
|
+
easy.on_headers.clear
|
72
|
+
easy.on_headers { :abort }
|
73
|
+
end
|
74
|
+
let(:header_write_callback) { easy.instance_variable_get(:@header_write_callback) }
|
75
|
+
|
76
|
+
it "returns -1 to indicate abort to libcurl" do
|
77
|
+
expect(header_write_callback.call(stream, 1, 1, nil)).to eq(-1)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
59
81
|
end
|
@@ -81,6 +81,12 @@ describe Ethon::Easy::Informations do
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
describe "#redirect_url" do
|
85
|
+
it "returns nil as there is no redirect" do
|
86
|
+
expect(easy.redirect_url).to be(nil)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
84
90
|
describe "#request_size" do
|
85
91
|
it "returns 53" do
|
86
92
|
expect(easy.request_size).to eq(53)
|
@@ -94,5 +100,27 @@ describe Ethon::Easy::Informations do
|
|
94
100
|
end
|
95
101
|
end
|
96
102
|
|
103
|
+
describe "#size_upload" do
|
104
|
+
it "returns float" do
|
105
|
+
expect(easy.size_upload).to be_a(Float)
|
106
|
+
end
|
107
|
+
end
|
97
108
|
|
109
|
+
describe "#size_download" do
|
110
|
+
it "returns float" do
|
111
|
+
expect(easy.size_download).to be_a(Float)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#speed_upload" do
|
116
|
+
it "returns float" do
|
117
|
+
expect(easy.speed_upload).to be_a(Float)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#speed_download" do
|
122
|
+
it "returns float" do
|
123
|
+
expect(easy.speed_download).to be_a(Float)
|
124
|
+
end
|
125
|
+
end
|
98
126
|
end
|
@@ -10,7 +10,8 @@ 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
|
-
:
|
13
|
+
:size_upload, :size_download, :speed_upload, :speed_upload,
|
14
|
+
:effective_url, :primary_ip, :redirect_count, :redirect_url, :debug_info
|
14
15
|
].each do |name|
|
15
16
|
it "contains #{name}" do
|
16
17
|
expect(described_class::INFORMATIONS_TO_MIRROR).to include(name)
|
@@ -106,6 +106,7 @@ describe Ethon::Easy::Operations do
|
|
106
106
|
|
107
107
|
it "doesn't follow" do
|
108
108
|
expect(easy.response_code).to eq(302)
|
109
|
+
expect(easy.redirect_url).to eq("http://localhost:3001/")
|
109
110
|
end
|
110
111
|
end
|
111
112
|
|
@@ -115,6 +116,7 @@ describe Ethon::Easy::Operations do
|
|
115
116
|
|
116
117
|
it "follows" do
|
117
118
|
expect(easy.response_code).to eq(200)
|
119
|
+
expect(easy.redirect_url).to eq(nil)
|
118
120
|
end
|
119
121
|
|
120
122
|
context "when infinite redirect loop" do
|
@@ -124,6 +126,7 @@ describe Ethon::Easy::Operations do
|
|
124
126
|
context "when max redirect set" do
|
125
127
|
it "follows only x times" do
|
126
128
|
expect(easy.response_code).to eq(302)
|
129
|
+
expect(easy.redirect_url).to eq("http://localhost:3001/bad_redirect")
|
127
130
|
end
|
128
131
|
end
|
129
132
|
end
|
@@ -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.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hans Hasselberg
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -137,7 +137,7 @@ homepage: https://github.com/typhoeus/ethon
|
|
137
137
|
licenses:
|
138
138
|
- MIT
|
139
139
|
metadata: {}
|
140
|
-
post_install_message:
|
140
|
+
post_install_message:
|
141
141
|
rdoc_options: []
|
142
142
|
require_paths:
|
143
143
|
- lib
|
@@ -152,8 +152,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: 1.3.6
|
154
154
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
156
|
-
signing_key:
|
155
|
+
rubygems_version: 3.3.7
|
156
|
+
signing_key:
|
157
157
|
specification_version: 4
|
158
158
|
summary: Libcurl wrapper.
|
159
159
|
test_files:
|