ethon 0.17.0 → 0.19.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +698 -2
  3. data/README.md +183 -40
  4. data/ethon.gemspec +9 -1
  5. data/lib/ethon/curl.rb +22 -0
  6. data/lib/ethon/curls/classes.rb +284 -12
  7. data/lib/ethon/curls/codes.rb +28 -9
  8. data/lib/ethon/curls/constants.rb +61 -4
  9. data/lib/ethon/curls/form_options.rb +2 -1
  10. data/lib/ethon/curls/functions.rb +60 -11
  11. data/lib/ethon/curls/infos.rb +104 -5
  12. data/lib/ethon/curls/introspection.rb +139 -0
  13. data/lib/ethon/curls/introspection_constants.rb +42 -0
  14. data/lib/ethon/curls/mime.rb +187 -0
  15. data/lib/ethon/curls/optional.rb +14 -0
  16. data/lib/ethon/curls/options.rb +298 -71
  17. data/lib/ethon/curls/settings.rb +12 -3
  18. data/lib/ethon/curls/share.rb +165 -0
  19. data/lib/ethon/curls/share_constants.rb +47 -0
  20. data/lib/ethon/curls/url.rb +171 -0
  21. data/lib/ethon/curls/url_constants.rb +78 -0
  22. data/lib/ethon/easy/callbacks.rb +178 -46
  23. data/lib/ethon/easy/features.rb +4 -1
  24. data/lib/ethon/easy/files.rb +113 -0
  25. data/lib/ethon/easy/form.rb +65 -6
  26. data/lib/ethon/easy/header.rb +57 -15
  27. data/lib/ethon/easy/headers_api.rb +79 -0
  28. data/lib/ethon/easy/http/actionable.rb +25 -3
  29. data/lib/ethon/easy/http/get.rb +5 -1
  30. data/lib/ethon/easy/http/post.rb +1 -0
  31. data/lib/ethon/easy/http/postable.rb +9 -2
  32. data/lib/ethon/easy/http/putable.rb +19 -4
  33. data/lib/ethon/easy/http.rb +16 -4
  34. data/lib/ethon/easy/informations.rb +51 -2
  35. data/lib/ethon/easy/mime.rb +29 -0
  36. data/lib/ethon/easy/mirror.rb +17 -4
  37. data/lib/ethon/easy/operations.rb +259 -11
  38. data/lib/ethon/easy/options.rb +327 -8
  39. data/lib/ethon/easy/queryable.rb +59 -14
  40. data/lib/ethon/easy/response_callbacks.rb +33 -12
  41. data/lib/ethon/easy/share.rb +28 -0
  42. data/lib/ethon/easy/url.rb +32 -0
  43. data/lib/ethon/easy/util.rb +19 -0
  44. data/lib/ethon/easy.rb +185 -10
  45. data/lib/ethon/errors/fork_safety.rb +10 -0
  46. data/lib/ethon/errors/form_add.rb +12 -0
  47. data/lib/ethon/errors/global_init.rb +1 -1
  48. data/lib/ethon/errors/header.rb +12 -0
  49. data/lib/ethon/errors/invalid_option.rb +3 -1
  50. data/lib/ethon/errors/invalid_value.rb +3 -1
  51. data/lib/ethon/errors/mime.rb +17 -0
  52. data/lib/ethon/errors/multi_add.rb +1 -1
  53. data/lib/ethon/errors/multi_assign.rb +12 -0
  54. data/lib/ethon/errors/multi_fdset.rb +1 -1
  55. data/lib/ethon/errors/multi_perform.rb +12 -0
  56. data/lib/ethon/errors/multi_poll.rb +12 -0
  57. data/lib/ethon/errors/multi_remove.rb +1 -1
  58. data/lib/ethon/errors/multi_timeout.rb +2 -2
  59. data/lib/ethon/errors/multi_wait.rb +12 -0
  60. data/lib/ethon/errors/multi_wakeup.rb +12 -0
  61. data/lib/ethon/errors/select.rb +1 -1
  62. data/lib/ethon/errors/set_option.rb +27 -0
  63. data/lib/ethon/errors/share.rb +17 -0
  64. data/lib/ethon/errors/unsupported_option.rb +23 -0
  65. data/lib/ethon/errors/url.rb +17 -0
  66. data/lib/ethon/errors.rb +13 -0
  67. data/lib/ethon/libc.rb +30 -1
  68. data/lib/ethon/loggable.rb +1 -2
  69. data/lib/ethon/multi/operations.rb +269 -25
  70. data/lib/ethon/multi/options.rb +86 -12
  71. data/lib/ethon/multi/stack.rb +175 -10
  72. data/lib/ethon/multi.rb +44 -12
  73. data/lib/ethon/version.rb +1 -1
  74. data/lib/ethon.rb +8 -9
  75. metadata +52 -4
@@ -9,13 +9,25 @@ module Ethon
9
9
  # Sets max_total_connections option.
10
10
  #
11
11
  # @example Set max_total_connections option.
12
- # multi.max_total_conections = $value
12
+ # multi.max_total_connections = $value
13
13
  #
14
- # @param [ String ] value The value to set.
14
+ # @param [ Integer ] value The value to set.
15
15
  #
16
16
  # @return [ void ]
17
17
  def max_total_connections=(value)
18
- Curl.set_option(:max_total_connections, value_for(value, :int), handle, :multi)
18
+ Curl.set_option(:max_total_connections, value_for(value, :int, :max_total_connections), handle, :multi)
19
+ end
20
+
21
+ # Sets max_concurrent_streams option.
22
+ #
23
+ # @example Set max_concurrent_streams option.
24
+ # multi.max_concurrent_streams = $value
25
+ #
26
+ # @param [ Integer ] value The value to set.
27
+ #
28
+ # @return [ void ]
29
+ def max_concurrent_streams=(value)
30
+ Curl.set_option(:max_concurrent_streams, value_for(value, :int, :max_concurrent_streams), handle, :multi)
19
31
  end
20
32
 
21
33
  # Sets maxconnects option.
@@ -23,11 +35,59 @@ module Ethon
23
35
  # @example Set maxconnects option.
24
36
  # multi.maxconnects = $value
25
37
  #
26
- # @param [ String ] value The value to set.
38
+ # @param [ Integer ] value The value to set.
27
39
  #
28
40
  # @return [ void ]
29
41
  def maxconnects=(value)
30
- Curl.set_option(:maxconnects, value_for(value, :int), handle, :multi)
42
+ Curl.set_option(:maxconnects, value_for(value, :int, :maxconnects), handle, :multi)
43
+ end
44
+
45
+ # Sets max_host_connections option.
46
+ #
47
+ # @example Set max_host_connections option.
48
+ # multi.max_host_connections = $value
49
+ #
50
+ # @param [ Integer ] value The value to set.
51
+ #
52
+ # @return [ void ]
53
+ def max_host_connections=(value)
54
+ Curl.set_option(:max_host_connections, value_for(value, :int, :max_host_connections), handle, :multi)
55
+ end
56
+
57
+ # Sets max_pipeline_length option.
58
+ #
59
+ # @example Set max_pipeline_length option.
60
+ # multi.max_pipeline_length = $value
61
+ #
62
+ # @param [ Integer ] value The value to set.
63
+ #
64
+ # @return [ void ]
65
+ def max_pipeline_length=(value)
66
+ Curl.set_option(:max_pipeline_length, value_for(value, :int, :max_pipeline_length), handle, :multi)
67
+ end
68
+
69
+ # Sets content_length_penalty_size option.
70
+ #
71
+ # @example Set content_length_penalty_size option.
72
+ # multi.content_length_penalty_size = $value
73
+ #
74
+ # @param [ Integer ] value The value to set.
75
+ #
76
+ # @return [ void ]
77
+ def content_length_penalty_size=(value)
78
+ Curl.set_option(:content_length_penalty_size, value, handle, :multi)
79
+ end
80
+
81
+ # Sets chunk_length_penalty_size option.
82
+ #
83
+ # @example Set chunk_length_penalty_size option.
84
+ # multi.chunk_length_penalty_size = $value
85
+ #
86
+ # @param [ Integer ] value The value to set.
87
+ #
88
+ # @return [ void ]
89
+ def chunk_length_penalty_size=(value)
90
+ Curl.set_option(:chunk_length_penalty_size, value, handle, :multi)
31
91
  end
32
92
 
33
93
  # Sets pipelining option.
@@ -35,11 +95,11 @@ module Ethon
35
95
  # @example Set pipelining option.
36
96
  # multi.pipelining = $value
37
97
  #
38
- # @param [ String ] value The value to set.
98
+ # @param [ Integer ] value The value to set.
39
99
  #
40
100
  # @return [ void ]
41
101
  def pipelining=(value)
42
- Curl.set_option(:pipelining, value_for(value, :int), handle, :multi)
102
+ Curl.set_option(:pipelining, value_for(value, :int, :pipelining), handle, :multi)
43
103
  end
44
104
 
45
105
  # Sets socketdata option.
@@ -47,7 +107,7 @@ module Ethon
47
107
  # @example Set socketdata option.
48
108
  # multi.socketdata = $value
49
109
  #
50
- # @param [ String ] value The value to set.
110
+ # @param [ FFI::Pointer, nil ] value The value to set.
51
111
  #
52
112
  # @return [ void ]
53
113
  def socketdata=(value)
@@ -59,11 +119,13 @@ module Ethon
59
119
  # @example Set socketfunction option.
60
120
  # multi.socketfunction = $value
61
121
  #
62
- # @param [ String ] value The value to set.
122
+ # @param [ Proc, nil ] value The value to set.
63
123
  #
64
124
  # @return [ void ]
65
125
  def socketfunction=(value)
66
126
  Curl.set_option(:socketfunction, value_for(value, :string), handle, :multi)
127
+ retain_callback(:socketfunction, value)
128
+ value
67
129
  end
68
130
 
69
131
  # Sets timerdata option.
@@ -71,7 +133,7 @@ module Ethon
71
133
  # @example Set timerdata option.
72
134
  # multi.timerdata = $value
73
135
  #
74
- # @param [ String ] value The value to set.
136
+ # @param [ FFI::Pointer, nil ] value The value to set.
75
137
  #
76
138
  # @return [ void ]
77
139
  def timerdata=(value)
@@ -83,15 +145,26 @@ module Ethon
83
145
  # @example Set timerfunction option.
84
146
  # multi.timerfunction = $value
85
147
  #
86
- # @param [ String ] value The value to set.
148
+ # @param [ Proc, nil ] value The value to set.
87
149
  #
88
150
  # @return [ void ]
89
151
  def timerfunction=(value)
90
152
  Curl.set_option(:timerfunction, value_for(value, :string), handle, :multi)
153
+ retain_callback(:timerfunction, value)
154
+ value
91
155
  end
92
156
 
93
157
  private
94
158
 
159
+ def retain_callback(option, callback)
160
+ if callback.nil?
161
+ @procs.delete(option) if @procs
162
+ else
163
+ (@procs ||= {})[option] = callback
164
+ end
165
+ Curl::MultiHandle.retain_callback(handle, option, callback)
166
+ end
167
+
95
168
  # Return the value to set to multi handle. It is converted with the help
96
169
  # of bool_options, enum_options and int_options.
97
170
  #
@@ -105,13 +178,14 @@ module Ethon
105
178
  if type == :bool
106
179
  value ? 1 : 0
107
180
  elsif type == :int
108
- value.to_i
181
+ Ethon::Curls::Options.integer_value(option, value)
109
182
  elsif value.is_a?(String)
110
183
  Ethon::Easy::Util.escape_zero_byte(value)
111
184
  else
112
185
  value
113
186
  end
114
187
  end
188
+
115
189
  end
116
190
  end
117
191
  end
@@ -5,14 +5,18 @@ module Ethon
5
5
  # This module provides the multi stack behaviour.
6
6
  module Stack
7
7
 
8
- # Return easy handles.
8
+ # Return the easy handles currently added to this multi.
9
+ #
10
+ # The returned array is a snapshot. Adding or deleting entries there does
11
+ # not change the multi, because libcurl would still reference the handles.
12
+ # Use #add and #delete instead.
9
13
  #
10
14
  # @example Return easy handles.
11
15
  # multi.easy_handles
12
16
  #
13
- # @return [ Array ] The easy handles.
17
+ # @return [ Array<Easy> ] The easy handles.
14
18
  def easy_handles
15
- @easy_handles ||= []
19
+ stack.dup.freeze
16
20
  end
17
21
 
18
22
  # Add an easy to the stack.
@@ -22,13 +26,64 @@ module Ethon
22
26
  #
23
27
  # @param [ Easy ] easy The easy to add.
24
28
  #
29
+ # A failed add leaves the Easy untouched: its previous response and any
30
+ # streamed response file stay as they were.
31
+ #
32
+ # @raise [ TypeError ] If easy is not an Ethon::Easy.
25
33
  # @raise [ Ethon::Errors::MultiAdd ] If adding an easy failed.
26
34
  def add(easy)
27
- return nil if easy_handles.include?(easy)
35
+ return nil if stack.include?(easy)
36
+ unless easy.is_a?(Ethon::Easy)
37
+ raise TypeError, "expected an Ethon::Easy, got #{easy.class}"
38
+ end
39
+
40
+ # Another Multi may still own the handle natively. Reject before
41
+ # resetting its transfer state or touching its response stream.
42
+ if easy.instance_variable_get(:@multi)
43
+ raise Errors::EthonError, "Easy is still attached to a Multi; remove it first"
44
+ end
45
+
46
+ # Resolve every handle before touching the easy so a cleaned up or
47
+ # forked Multi or Easy cannot be changed while raising.
48
+ multi_handle = handle
49
+ easy_handle = easy.handle
50
+
51
+ stack << easy
52
+ begin
53
+ code = Curl.multi_add_handle(multi_handle, easy_handle)
54
+ rescue Exception
55
+ # The native add may have completed before a socket or timer callback
56
+ # raised, so make sure libcurl does not keep an untracked handle.
57
+ detach_added_handle(easy, multi_handle, easy_handle)
58
+ raise
59
+ end
60
+
61
+ unless code == :ok
62
+ stack.delete(easy)
63
+ raise Errors::MultiAdd.new(code, easy)
64
+ end
65
+
66
+ # Cleanup must not free an easy that is still attached to this multi.
67
+ easy.instance_variable_set(:@multi, self)
28
68
 
29
- code = Curl.multi_add_handle(handle, easy.handle)
30
- raise Errors::MultiAdd.new(code, easy) unless code == :ok
31
- easy_handles << easy
69
+ # Only a successful add starts a new transfer. Resetting the previous
70
+ # response and opening its output stream before libcurl accepted the
71
+ # handle would make a failed add discard the response body and truncate
72
+ # a streamed response file.
73
+ begin
74
+ # A completion callback can add the Easy to this Multi before the
75
+ # previous transfer's destination is finished. Close or flush it
76
+ # first so prepare opens a fresh destination for this transfer.
77
+ easy.finish_response_body_io
78
+ easy.prepare_response_body_io
79
+ easy.reset_transfer_state
80
+ easy.clear_callback_error
81
+ rescue Exception
82
+ rollback_add(easy, multi_handle, easy_handle)
83
+ raise
84
+ end
85
+
86
+ easy_handles
32
87
  end
33
88
 
34
89
  # Delete an easy from stack.
@@ -39,10 +94,120 @@ module Ethon
39
94
  #
40
95
  # @raise [ Ethon::Errors::MultiRemove ] If removing an easy failed.
41
96
  def delete(easy)
42
- if easy_handles.delete(easy)
43
- code = Curl.multi_remove_handle(handle, easy.handle)
44
- raise Errors::MultiRemove.new(code, handle) unless code == :ok
97
+ if (index = stack.index(easy))
98
+ easy_handle = nil
99
+ stack.delete_at(index)
100
+ begin
101
+ easy_handle = easy.handle
102
+ code = Curl.multi_remove_handle(handle, easy_handle)
103
+ rescue Exception
104
+ # A socket callback can raise after libcurl has already detached
105
+ # the handle. Keep the stack in sync with the native multi instead
106
+ # of tracking a handle libcurl no longer owns.
107
+ if native_detached?(easy_handle)
108
+ begin
109
+ finish_removed_easy(easy)
110
+ rescue StandardError
111
+ # Keep the original remove error.
112
+ end
113
+ else
114
+ stack.insert(index, easy)
115
+ end
116
+ raise
117
+ end
118
+ unless code == :ok
119
+ stack.insert(index, easy)
120
+ raise Errors::MultiRemove.new(code, easy)
121
+ end
122
+
123
+ finish_removed_easy(easy)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # The mutable stack. Only this Multi may change it; every native change
130
+ # has to go through #add and #delete.
131
+ def stack
132
+ @easy_handles ||= []
133
+ end
134
+
135
+ # Finish an Easy that is no longer attached to this Multi.
136
+ def finish_removed_easy(easy)
137
+ if easy.instance_variable_get(:@multi).equal?(self)
138
+ easy.instance_variable_set(:@multi, nil)
139
+ end
140
+
141
+ begin
142
+ easy.finish_response_body_io
143
+ ensure
144
+ @running_count = nil if stack.empty?
145
+ end
146
+ end
147
+
148
+ # Whether libcurl detached the handle before a callback raised. Older
149
+ # libcurl cannot answer, so assume it is still attached and keep it
150
+ # tracked.
151
+ def native_detached?(easy_handle)
152
+ return false unless easy_handle
153
+ return false unless Curl.respond_to?(:multi_get_handles)
154
+
155
+ !native_handle?(easy_handle)
156
+ rescue StandardError
157
+ false
158
+ end
159
+
160
+ # Undo a native add whose Easy could not be prepared for the transfer.
161
+ def rollback_add(easy, multi_handle, easy_handle)
162
+ begin
163
+ easy.finish_response_body_io
164
+ rescue StandardError
165
+ # Best effort; the add is already being rolled back.
45
166
  end
167
+
168
+ detach_added_handle(easy, multi_handle, easy_handle)
169
+ end
170
+
171
+ # Detach a handle that libcurl may have attached before an exception
172
+ # interrupted the add. The easy stays tracked when it may still be
173
+ # attached so that Multi#cleanup can retry instead of leaving libcurl
174
+ # with an untracked attached handle.
175
+ def detach_added_handle(easy, multi_handle, easy_handle)
176
+ result = begin
177
+ Curl.multi_remove_handle(multi_handle, easy_handle)
178
+ rescue Exception
179
+ nil
180
+ end
181
+
182
+ attached = case result
183
+ when :ok, :bad_easy_handle
184
+ false
185
+ when nil
186
+ # libcurl detaches the easy before it invokes a socket or
187
+ # timer callback, so a callback exception is not enough to
188
+ # assume it is still attached.
189
+ native_handle?(easy_handle)
190
+ else
191
+ true
192
+ end
193
+
194
+ if attached
195
+ easy.instance_variable_set(:@multi, self)
196
+ else
197
+ stack.delete(easy)
198
+ easy.instance_variable_set(:@multi, nil)
199
+ end
200
+ end
201
+
202
+ # Whether libcurl still knows the native easy handle. libcurl can only
203
+ # answer that question from 7.88.0 on; older versions assume the handle
204
+ # was detached, which matches the order inside Multi#delete.
205
+ def native_handle?(easy_handle)
206
+ return false unless Curl.respond_to?(:multi_get_handles)
207
+
208
+ native_handles.any? { |handle| handle == easy_handle }
209
+ rescue NotImplementedError
210
+ false
46
211
  end
47
212
  end
48
213
  end
data/lib/ethon/multi.rb CHANGED
@@ -12,6 +12,8 @@ module Ethon
12
12
  include Ethon::Multi::Operations
13
13
  include Ethon::Multi::Options
14
14
 
15
+ EXECUTION_MODES = [:perform, :socket_action].freeze
16
+
15
17
  # Create a new multi. Initialize curl in case
16
18
  # it didn't happen before.
17
19
  #
@@ -20,18 +22,19 @@ module Ethon
20
22
  #
21
23
  # @param [ Hash ] options The options.
22
24
  #
23
- # @option options :socketdata [String]
25
+ # @option options :socketdata [FFI::Pointer]
24
26
  # Pass a pointer to whatever you want passed to the
25
27
  # curl_socket_callback's forth argument, the userp pointer. This is not
26
28
  # used by libcurl but only passed-thru as-is. Set the callback pointer
27
- # with CURLMOPT_SOCKETFUNCTION.
28
- # @option options :pipelining [Boolean]
29
- # Pass a long set to 1 to enable or 0 to disable. Enabling pipelining
30
- # on a multi handle will make it attempt to perform HTTP Pipelining as
31
- # far as possible for transfers using this handle. This means that if
32
- # you add a second request that can use an already existing connection,
33
- # the second request will be "piped" on the same connection rather than
34
- # being executed in parallel. (Added in 7.16.0)
29
+ # with CURLMOPT_SOCKETFUNCTION. Keep the pointer alive for as long as the
30
+ # Multi may invoke the callback.
31
+ # @option options :pipelining [Integer]
32
+ # Pass a bitmask: 0 for CURLPIPE_NOTHING, 1 for CURLPIPE_HTTP1, or 2 for
33
+ # CURLPIPE_MULTIPLEX. CURLPIPE_HTTP1 is deprecated and has no effect
34
+ # since libcurl 7.62.0. CURLPIPE_MULTIPLEX allows libcurl to multiplex a
35
+ # new transfer over an existing HTTP/2 or HTTP/3 connection when
36
+ # possible, and is the default since libcurl 7.62.0. (Added in 7.16.0;
37
+ # multiplex support added in 7.43.0.)
35
38
  # @option options :timerfunction [Proc]
36
39
  # Pass a pointer to a function matching the curl_multi_timer_callback
37
40
  # prototype. This function will then be called when the timeout value
@@ -44,11 +47,12 @@ module Ethon
44
47
  # only when the fixed future timeout time actually changes. See also
45
48
  # CURLMOPT_TIMERDATA. This callback can be used instead of, or in
46
49
  # addition to, curl_multi_timeout(3). (Added in 7.16.0)
47
- # @option options :timerdata [String]
50
+ # @option options :timerdata [FFI::Pointer]
48
51
  # Pass a pointer to whatever you want passed to the
49
52
  # curl_multi_timer_callback's third argument, the userp pointer. This
50
53
  # is not used by libcurl but only passed-thru as-is. Set the callback
51
- # pointer with CURLMOPT_TIMERFUNCTION. (Added in 7.16.0)
54
+ # pointer with CURLMOPT_TIMERFUNCTION. Keep the pointer alive for as long
55
+ # as the Multi may invoke the callback. (Added in 7.16.0)
52
56
  # @option options :maxconnects [Integer]
53
57
  # Pass a long. The set number will be used as the maximum amount of
54
58
  # simultaneously open connections that libcurl may cache. Default is
@@ -61,6 +65,23 @@ module Ethon
61
65
  # This option is for the multi handle's use only, when using the easy
62
66
  # interface you should instead use the CURLOPT_MAXCONNECTS option.
63
67
  # (Added in 7.16.3)
68
+ # @option options :max_host_connections [Integer]
69
+ # Pass a long. The set number will be used as the maximum amount of
70
+ # simultaneously open connections to a single host.
71
+ # (Added in 7.30.0)
72
+ # @option options :max_pipeline_length [Integer]
73
+ # Pass a long. The set number will be used as the maximum amount of
74
+ # requests in a pipeline. This option is obsolete in libcurl 7.62.0 and
75
+ # later, where HTTP/1 pipelining has no effect.
76
+ # (Added in 7.16.0)
77
+ # @option options :content_length_penalty_size [Integer]
78
+ # Pass a curl_off_t. This option sets the size of the content length
79
+ # penalty in bytes. It is used by the pipelining code.
80
+ # (Added in 7.16.0)
81
+ # @option options :chunk_length_penalty_size [Integer]
82
+ # Pass a curl_off_t. This option sets the size of the chunk length penalty
83
+ # in bytes. It is used by the pipelining code.
84
+ # (Added in 7.16.0)
64
85
  # @option options :max_total_connections [Integer]
65
86
  # Pass a long. The set number will be used as the maximum amount of
66
87
  # simultaneously open connections in total. For each new session,
@@ -74,7 +95,7 @@ module Ethon
74
95
  # is 1 will not be treated as unlimited. Instead it will open only 1
75
96
  # connection and try to pipeline on it.
76
97
  # (Added in 7.30.0)
77
- # @option options :execution_mode [Boolean]
98
+ # @option options :execution_mode [Symbol]
78
99
  # Either :perform (default) or :socket_action, specifies the usage
79
100
  # method that will be used on this multi object. The default :perform
80
101
  # mode provides a #perform function that uses curl_multi_perform
@@ -88,7 +109,11 @@ module Ethon
88
109
  # @return [ Multi ] The new multi.
89
110
  def initialize(options = {})
90
111
  Curl.init
112
+ options = options.dup
91
113
  @execution_mode = options.delete(:execution_mode) || :perform
114
+ unless EXECUTION_MODES.include?(@execution_mode)
115
+ raise ArgumentError, "Unknown execution mode #{@execution_mode.inspect}"
116
+ end
92
117
  set_attributes(options)
93
118
  init_vars
94
119
  end
@@ -114,6 +139,13 @@ module Ethon
114
139
 
115
140
  private
116
141
 
142
+ # Ruby cannot duplicate the native multi handle or its Ruby-side
143
+ # bookkeeping safely. A shallow copy would share the easy-handle stack and
144
+ # the native handle, so cleaning up one copy would invalidate the other.
145
+ def initialize_copy(_other)
146
+ raise TypeError, "cannot duplicate an Ethon::Multi"
147
+ end
148
+
117
149
  # Internal function to gate functions to a specific execution mode
118
150
  #
119
151
  # @raise ArgumentError
data/lib/ethon/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  module Ethon
3
3
 
4
4
  # Ethon version.
5
- VERSION = '0.17.0'
5
+ VERSION = '0.19.0'
6
6
  end
data/lib/ethon.rb CHANGED
@@ -2,23 +2,22 @@
2
2
  require 'logger'
3
3
  require 'ffi'
4
4
  require 'thread'
5
- begin
6
- require 'mime/types/columnar'
7
- rescue LoadError
8
- begin
9
- require 'mime/types'
10
- rescue LoadError
11
- end
12
- end
13
5
  require 'tempfile'
14
6
 
15
7
  require 'ethon/libc'
16
8
  require 'ethon/curl'
9
+ require 'ethon/curls/mime'
17
10
  require 'ethon/easy'
11
+ require 'ethon/easy/url'
12
+ require 'ethon/easy/mime'
18
13
  require 'ethon/errors'
14
+ require 'ethon/curls/introspection'
15
+ require 'ethon/easy/headers_api'
19
16
  require 'ethon/loggable'
20
17
  require 'ethon/multi'
21
18
  require 'ethon/version'
19
+ require 'ethon/curls/share'
20
+ require 'ethon/easy/share'
22
21
 
23
22
  # Ethon is a very simple libcurl.
24
23
  # It provides direct access to libcurl functionality
@@ -31,6 +30,6 @@ require 'ethon/version'
31
30
  #
32
31
  # @note Please update to the latest libcurl version in order
33
32
  # to benefit from all features and bugfixes.
34
- # http://curl.haxx.se/download.html
33
+ # https://curl.se/download.html
35
34
  module Ethon
36
35
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans Hasselberg
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-10-28 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: ffi
@@ -23,6 +24,20 @@ dependencies:
23
24
  - - ">="
24
25
  - !ruby/object:Gem::Version
25
26
  version: 1.15.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
26
41
  description: Very lightweight libcurl wrapper.
27
42
  email:
28
43
  - me@hans.io
@@ -42,15 +57,25 @@ files:
42
57
  - lib/ethon/curls/form_options.rb
43
58
  - lib/ethon/curls/functions.rb
44
59
  - lib/ethon/curls/infos.rb
60
+ - lib/ethon/curls/introspection.rb
61
+ - lib/ethon/curls/introspection_constants.rb
45
62
  - lib/ethon/curls/messages.rb
63
+ - lib/ethon/curls/mime.rb
64
+ - lib/ethon/curls/optional.rb
46
65
  - lib/ethon/curls/options.rb
47
66
  - lib/ethon/curls/settings.rb
67
+ - lib/ethon/curls/share.rb
68
+ - lib/ethon/curls/share_constants.rb
69
+ - lib/ethon/curls/url.rb
70
+ - lib/ethon/curls/url_constants.rb
48
71
  - lib/ethon/easy.rb
49
72
  - lib/ethon/easy/callbacks.rb
50
73
  - lib/ethon/easy/debug_info.rb
51
74
  - lib/ethon/easy/features.rb
75
+ - lib/ethon/easy/files.rb
52
76
  - lib/ethon/easy/form.rb
53
77
  - lib/ethon/easy/header.rb
78
+ - lib/ethon/easy/headers_api.rb
54
79
  - lib/ethon/easy/http.rb
55
80
  - lib/ethon/easy/http/actionable.rb
56
81
  - lib/ethon/easy/http/custom.rb
@@ -64,23 +89,39 @@ files:
64
89
  - lib/ethon/easy/http/put.rb
65
90
  - lib/ethon/easy/http/putable.rb
66
91
  - lib/ethon/easy/informations.rb
92
+ - lib/ethon/easy/mime.rb
67
93
  - lib/ethon/easy/mirror.rb
68
94
  - lib/ethon/easy/operations.rb
69
95
  - lib/ethon/easy/options.rb
70
96
  - lib/ethon/easy/params.rb
71
97
  - lib/ethon/easy/queryable.rb
72
98
  - lib/ethon/easy/response_callbacks.rb
99
+ - lib/ethon/easy/share.rb
100
+ - lib/ethon/easy/url.rb
73
101
  - lib/ethon/easy/util.rb
74
102
  - lib/ethon/errors.rb
75
103
  - lib/ethon/errors/ethon_error.rb
104
+ - lib/ethon/errors/fork_safety.rb
105
+ - lib/ethon/errors/form_add.rb
76
106
  - lib/ethon/errors/global_init.rb
107
+ - lib/ethon/errors/header.rb
77
108
  - lib/ethon/errors/invalid_option.rb
78
109
  - lib/ethon/errors/invalid_value.rb
110
+ - lib/ethon/errors/mime.rb
79
111
  - lib/ethon/errors/multi_add.rb
112
+ - lib/ethon/errors/multi_assign.rb
80
113
  - lib/ethon/errors/multi_fdset.rb
114
+ - lib/ethon/errors/multi_perform.rb
115
+ - lib/ethon/errors/multi_poll.rb
81
116
  - lib/ethon/errors/multi_remove.rb
82
117
  - lib/ethon/errors/multi_timeout.rb
118
+ - lib/ethon/errors/multi_wait.rb
119
+ - lib/ethon/errors/multi_wakeup.rb
83
120
  - lib/ethon/errors/select.rb
121
+ - lib/ethon/errors/set_option.rb
122
+ - lib/ethon/errors/share.rb
123
+ - lib/ethon/errors/unsupported_option.rb
124
+ - lib/ethon/errors/url.rb
84
125
  - lib/ethon/libc.rb
85
126
  - lib/ethon/loggable.rb
86
127
  - lib/ethon/multi.rb
@@ -91,7 +132,13 @@ files:
91
132
  homepage: https://github.com/typhoeus/ethon
92
133
  licenses:
93
134
  - MIT
94
- metadata: {}
135
+ metadata:
136
+ bug_tracker_uri: https://github.com/typhoeus/ethon/issues
137
+ changelog_uri: https://github.com/typhoeus/ethon/blob/v0.19.0/CHANGELOG.md
138
+ documentation_uri: https://www.rubydoc.info/gems/ethon/0.19.0
139
+ rubygems_mfa_required: 'true'
140
+ source_code_uri: https://github.com/typhoeus/ethon/tree/v0.19.0
141
+ post_install_message:
95
142
  rdoc_options: []
96
143
  require_paths:
97
144
  - lib
@@ -106,7 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
153
  - !ruby/object:Gem::Version
107
154
  version: '0'
108
155
  requirements: []
109
- rubygems_version: 3.7.1
156
+ rubygems_version: 3.4.19
157
+ signing_key:
110
158
  specification_version: 4
111
159
  summary: Libcurl wrapper.
112
160
  test_files: []