httpx 1.4.4 → 1.5.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_5_0.md +126 -0
  3. data/lib/httpx/adapters/datadog.rb +24 -3
  4. data/lib/httpx/adapters/webmock.rb +1 -0
  5. data/lib/httpx/buffer.rb +16 -5
  6. data/lib/httpx/connection/http1.rb +8 -9
  7. data/lib/httpx/connection/http2.rb +48 -24
  8. data/lib/httpx/connection.rb +36 -19
  9. data/lib/httpx/errors.rb +2 -11
  10. data/lib/httpx/headers.rb +24 -23
  11. data/lib/httpx/io/ssl.rb +2 -1
  12. data/lib/httpx/io/tcp.rb +9 -7
  13. data/lib/httpx/io/unix.rb +1 -1
  14. data/lib/httpx/loggable.rb +13 -1
  15. data/lib/httpx/options.rb +63 -48
  16. data/lib/httpx/parser/http1.rb +1 -1
  17. data/lib/httpx/plugins/aws_sigv4.rb +1 -0
  18. data/lib/httpx/plugins/callbacks.rb +19 -6
  19. data/lib/httpx/plugins/circuit_breaker.rb +4 -3
  20. data/lib/httpx/plugins/cookies/jar.rb +0 -2
  21. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +7 -4
  22. data/lib/httpx/plugins/cookies.rb +4 -4
  23. data/lib/httpx/plugins/follow_redirects.rb +4 -2
  24. data/lib/httpx/plugins/grpc/call.rb +1 -1
  25. data/lib/httpx/plugins/h2c.rb +7 -1
  26. data/lib/httpx/plugins/persistent.rb +22 -1
  27. data/lib/httpx/plugins/proxy/http.rb +3 -1
  28. data/lib/httpx/plugins/query.rb +35 -0
  29. data/lib/httpx/plugins/response_cache/file_store.rb +115 -15
  30. data/lib/httpx/plugins/response_cache/store.rb +7 -67
  31. data/lib/httpx/plugins/response_cache.rb +179 -29
  32. data/lib/httpx/plugins/retries.rb +26 -14
  33. data/lib/httpx/plugins/stream.rb +4 -2
  34. data/lib/httpx/plugins/stream_bidi.rb +315 -0
  35. data/lib/httpx/pool.rb +58 -5
  36. data/lib/httpx/request/body.rb +1 -1
  37. data/lib/httpx/request.rb +6 -2
  38. data/lib/httpx/resolver/https.rb +10 -4
  39. data/lib/httpx/resolver/native.rb +13 -13
  40. data/lib/httpx/resolver/resolver.rb +4 -0
  41. data/lib/httpx/resolver/system.rb +37 -14
  42. data/lib/httpx/resolver.rb +2 -2
  43. data/lib/httpx/response/body.rb +10 -21
  44. data/lib/httpx/response/buffer.rb +36 -12
  45. data/lib/httpx/response.rb +11 -1
  46. data/lib/httpx/selector.rb +16 -12
  47. data/lib/httpx/session.rb +79 -19
  48. data/lib/httpx/timers.rb +24 -16
  49. data/lib/httpx/transcoder/multipart/decoder.rb +4 -2
  50. data/lib/httpx/transcoder/multipart/encoder.rb +2 -1
  51. data/lib/httpx/version.rb +1 -1
  52. data/sig/buffer.rbs +1 -1
  53. data/sig/chainable.rbs +5 -2
  54. data/sig/connection/http2.rbs +11 -2
  55. data/sig/connection.rbs +4 -4
  56. data/sig/errors.rbs +0 -3
  57. data/sig/headers.rbs +15 -10
  58. data/sig/httpx.rbs +5 -1
  59. data/sig/io/tcp.rbs +6 -0
  60. data/sig/loggable.rbs +2 -0
  61. data/sig/options.rbs +7 -1
  62. data/sig/plugins/cookies/cookie.rbs +1 -3
  63. data/sig/plugins/cookies/jar.rbs +4 -4
  64. data/sig/plugins/cookies/set_cookie_parser.rbs +22 -0
  65. data/sig/plugins/cookies.rbs +2 -0
  66. data/sig/plugins/h2c.rbs +4 -0
  67. data/sig/plugins/proxy/http.rbs +3 -0
  68. data/sig/plugins/proxy.rbs +4 -0
  69. data/sig/plugins/query.rbs +18 -0
  70. data/sig/plugins/response_cache/file_store.rbs +19 -0
  71. data/sig/plugins/response_cache/store.rbs +13 -0
  72. data/sig/plugins/response_cache.rbs +41 -19
  73. data/sig/plugins/retries.rbs +4 -3
  74. data/sig/plugins/stream.rbs +5 -1
  75. data/sig/plugins/stream_bidi.rbs +68 -0
  76. data/sig/plugins/upgrade/h2.rbs +9 -0
  77. data/sig/plugins/upgrade.rbs +5 -0
  78. data/sig/pool.rbs +5 -0
  79. data/sig/punycode.rbs +5 -0
  80. data/sig/request.rbs +2 -0
  81. data/sig/resolver/https.rbs +3 -2
  82. data/sig/resolver/native.rbs +1 -2
  83. data/sig/resolver/resolver.rbs +11 -3
  84. data/sig/resolver/system.rbs +19 -2
  85. data/sig/resolver.rbs +11 -7
  86. data/sig/response/body.rbs +3 -4
  87. data/sig/response/buffer.rbs +2 -3
  88. data/sig/response.rbs +2 -2
  89. data/sig/selector.rbs +20 -10
  90. data/sig/session.rbs +14 -6
  91. data/sig/timers.rbs +5 -7
  92. data/sig/transcoder/multipart.rbs +4 -3
  93. metadata +13 -2
data/lib/httpx/headers.rb CHANGED
@@ -11,20 +11,32 @@ module HTTPX
11
11
  end
12
12
 
13
13
  def initialize(headers = nil)
14
+ if headers.nil? || headers.empty?
15
+ @headers = headers.to_h
16
+ return
17
+ end
18
+
14
19
  @headers = {}
15
- return unless headers
16
20
 
17
21
  headers.each do |field, value|
18
- array_value(value).each do |v|
19
- add(downcased(field), v)
22
+ field = downcased(field)
23
+
24
+ value = array_value(value)
25
+
26
+ current = @headers[field]
27
+
28
+ if current.nil?
29
+ @headers[field] = value
30
+ else
31
+ current.concat(value)
20
32
  end
21
33
  end
22
34
  end
23
35
 
24
36
  # cloned initialization
25
- def initialize_clone(orig)
37
+ def initialize_clone(orig, **kwargs)
26
38
  super
27
- @headers = orig.instance_variable_get(:@headers).clone
39
+ @headers = orig.instance_variable_get(:@headers).clone(**kwargs)
28
40
  end
29
41
 
30
42
  # dupped initialization
@@ -39,17 +51,6 @@ module HTTPX
39
51
  super
40
52
  end
41
53
 
42
- def same_headers?(headers)
43
- @headers.empty? || begin
44
- headers.each do |k, v|
45
- next unless key?(k)
46
-
47
- return false unless v == self[k]
48
- end
49
- true
50
- end
51
- end
52
-
53
54
  # merges headers with another header-quack.
54
55
  # the merge rule is, if the header already exists,
55
56
  # ignore what the +other+ headers has. Otherwise, set
@@ -119,6 +120,10 @@ module HTTPX
119
120
  other == to_hash
120
121
  end
121
122
 
123
+ def empty?
124
+ @headers.empty?
125
+ end
126
+
122
127
  # the headers store in Hash format
123
128
  def to_hash
124
129
  Hash[to_a]
@@ -137,7 +142,8 @@ module HTTPX
137
142
 
138
143
  # :nocov:
139
144
  def inspect
140
- to_hash.inspect
145
+ "#<#{self.class}:#{object_id} " \
146
+ "#{to_hash.inspect}>"
141
147
  end
142
148
  # :nocov:
143
149
 
@@ -160,12 +166,7 @@ module HTTPX
160
166
  private
161
167
 
162
168
  def array_value(value)
163
- case value
164
- when Array
165
- value.map { |val| String(val).strip }
166
- else
167
- [String(value).strip]
168
- end
169
+ Array(value)
169
170
  end
170
171
 
171
172
  def downcased(field)
data/lib/httpx/io/ssl.rb CHANGED
@@ -9,7 +9,8 @@ module HTTPX
9
9
  # rubocop:disable Style/MutableConstant
10
10
  TLS_OPTIONS = { alpn_protocols: %w[h2 http/1.1].freeze }
11
11
  # https://github.com/jruby/jruby-openssl/issues/284
12
- TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby"
12
+ # TODO: remove when dropping support for jruby-openssl < 0.15.4
13
+ TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby" && JOpenSSL::VERSION < "0.15.4"
13
14
  # rubocop:enable Style/MutableConstant
14
15
  TLS_OPTIONS.freeze
15
16
 
data/lib/httpx/io/tcp.rb CHANGED
@@ -167,7 +167,12 @@ module HTTPX
167
167
 
168
168
  # :nocov:
169
169
  def inspect
170
- "#<#{self.class}: #{@ip}:#{@port} (state: #{@state})>"
170
+ "#<#{self.class}:#{object_id} " \
171
+ "#{@ip}:#{@port} " \
172
+ "@state=#{@state} " \
173
+ "@hostname=#{@hostname} " \
174
+ "@addresses=#{@addresses} " \
175
+ "@state=#{@state}>"
171
176
  end
172
177
  # :nocov:
173
178
 
@@ -195,12 +200,9 @@ module HTTPX
195
200
  end
196
201
 
197
202
  def log_transition_state(nextstate)
198
- case nextstate
199
- when :connected
200
- "Connected to #{host} (##{@io.fileno})"
201
- else
202
- "#{host} #{@state} -> #{nextstate}"
203
- end
203
+ label = host
204
+ label = "#{label}(##{@io.fileno})" if nextstate == :connected
205
+ "#{label} #{@state} -> #{nextstate}"
204
206
  end
205
207
  end
206
208
  end
data/lib/httpx/io/unix.rb CHANGED
@@ -57,7 +57,7 @@ module HTTPX
57
57
 
58
58
  # :nocov:
59
59
  def inspect
60
- "#<#{self.class}(path: #{@path}): (state: #{@state})>"
60
+ "#<#{self.class}:#{object_id} @path=#{@path}) @state=#{@state})>"
61
61
  end
62
62
  # :nocov:
63
63
 
@@ -15,7 +15,13 @@ module HTTPX
15
15
 
16
16
  USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
17
17
 
18
- def log(level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug, &msg)
18
+ def log(
19
+ level: @options.debug_level,
20
+ color: nil,
21
+ debug_level: @options.debug_level,
22
+ debug: @options.debug,
23
+ &msg
24
+ )
19
25
  return unless debug_level >= level
20
26
 
21
27
  debug_stream = debug || ($stderr if USE_DEBUG_LOG)
@@ -37,5 +43,11 @@ module HTTPX
37
43
  def log_exception(ex, level: @options.debug_level, color: nil, debug_level: @options.debug_level, debug: @options.debug)
38
44
  log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
39
45
  end
46
+
47
+ def log_redact(text, should_redact = @options.debug_redact)
48
+ return text.to_s unless should_redact
49
+
50
+ "[REDACTED]"
51
+ end
40
52
  end
41
53
  end
data/lib/httpx/options.rb CHANGED
@@ -27,10 +27,19 @@ module HTTPX
27
27
  [Socket::AF_INET]
28
28
  end.freeze
29
29
 
30
+ SET_TEMPORARY_NAME = ->(mod, pl = nil) do
31
+ if mod.respond_to?(:set_temporary_name) # ruby 3.4 only
32
+ name = mod.name || "#{mod.superclass.name}(plugin)"
33
+ name = "#{name}/#{pl}" if pl
34
+ mod.set_temporary_name(name)
35
+ end
36
+ end
37
+
30
38
  DEFAULT_OPTIONS = {
31
39
  :max_requests => Float::INFINITY,
32
40
  :debug => nil,
33
41
  :debug_level => (ENV["HTTPX_DEBUG"] || 1).to_i,
42
+ :debug_redact => ENV.key?("HTTPX_DEBUG_REDACT"),
34
43
  :ssl => EMPTY_HASH,
35
44
  :http2_settings => { settings_enable_push: 0 }.freeze,
36
45
  :fallback_protocol => "http/1.1",
@@ -47,18 +56,18 @@ module HTTPX
47
56
  write_timeout: WRITE_TIMEOUT,
48
57
  request_timeout: REQUEST_TIMEOUT,
49
58
  },
50
- :headers_class => Class.new(Headers),
59
+ :headers_class => Class.new(Headers, &SET_TEMPORARY_NAME),
51
60
  :headers => {},
52
61
  :window_size => WINDOW_SIZE,
53
62
  :buffer_size => BUFFER_SIZE,
54
63
  :body_threshold_size => MAX_BODY_THRESHOLD_SIZE,
55
- :request_class => Class.new(Request),
56
- :response_class => Class.new(Response),
57
- :request_body_class => Class.new(Request::Body),
58
- :response_body_class => Class.new(Response::Body),
59
- :pool_class => Class.new(Pool),
60
- :connection_class => Class.new(Connection),
61
- :options_class => Class.new(self),
64
+ :request_class => Class.new(Request, &SET_TEMPORARY_NAME),
65
+ :response_class => Class.new(Response, &SET_TEMPORARY_NAME),
66
+ :request_body_class => Class.new(Request::Body, &SET_TEMPORARY_NAME),
67
+ :response_body_class => Class.new(Response::Body, &SET_TEMPORARY_NAME),
68
+ :pool_class => Class.new(Pool, &SET_TEMPORARY_NAME),
69
+ :connection_class => Class.new(Connection, &SET_TEMPORARY_NAME),
70
+ :options_class => Class.new(self, &SET_TEMPORARY_NAME),
62
71
  :transport => nil,
63
72
  :addresses => nil,
64
73
  :persistent => false,
@@ -66,6 +75,7 @@ module HTTPX
66
75
  :resolver_options => { cache: true }.freeze,
67
76
  :pool_options => EMPTY_HASH,
68
77
  :ip_families => ip_address_families,
78
+ :close_on_fork => false,
69
79
  }.freeze
70
80
 
71
81
  class << self
@@ -92,6 +102,7 @@ module HTTPX
92
102
  #
93
103
  # :debug :: an object which log messages are written to (must respond to <tt><<</tt>)
94
104
  # :debug_level :: the log level of messages (can be 1, 2, or 3).
105
+ # :debug_redact :: whether header/body payload should be redacted (defaults to <tt>false</tt>).
95
106
  # :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::IO::SSL)
96
107
  # :http2_settings :: a hash of options to be passed to a HTTP2::Connection (ex: <tt>{ max_concurrent_streams: 2 }</tt>)
97
108
  # :fallback_protocol :: version of HTTP protocol to use by default in the absence of protocol negotiation
@@ -128,6 +139,8 @@ module HTTPX
128
139
  # :base_path :: path to prefix given relative paths with (ex: "/v2")
129
140
  # :max_concurrent_requests :: max number of requests which can be set concurrently
130
141
  # :max_requests :: max number of requests which can be made on socket before it reconnects.
142
+ # :close_on_fork :: whether the session automatically closes when the process is fork (defaults to <tt>false</tt>).
143
+ # it only works if the session is persistent (and ruby 3.1 or higher is used).
131
144
  #
132
145
  # This list of options are enhanced with each loaded plugin, see the plugin docs for details.
133
146
  def initialize(options = {})
@@ -136,13 +149,18 @@ module HTTPX
136
149
  end
137
150
 
138
151
  def freeze
139
- super
140
152
  @origin.freeze
141
153
  @base_path.freeze
142
154
  @timeout.freeze
143
155
  @headers.freeze
144
156
  @addresses.freeze
145
157
  @supported_compression_formats.freeze
158
+ @ssl.freeze
159
+ @http2_settings.freeze
160
+ @pool_options.freeze
161
+ @resolver_options.freeze
162
+ @ip_families.freeze
163
+ super
146
164
  end
147
165
 
148
166
  def option_origin(value)
@@ -165,41 +183,6 @@ module HTTPX
165
183
  Array(value).map(&:to_s)
166
184
  end
167
185
 
168
- def option_max_concurrent_requests(value)
169
- raise TypeError, ":max_concurrent_requests must be positive" unless value.positive?
170
-
171
- value
172
- end
173
-
174
- def option_max_requests(value)
175
- raise TypeError, ":max_requests must be positive" unless value.positive?
176
-
177
- value
178
- end
179
-
180
- def option_window_size(value)
181
- value = Integer(value)
182
-
183
- raise TypeError, ":window_size must be positive" unless value.positive?
184
-
185
- value
186
- end
187
-
188
- def option_buffer_size(value)
189
- value = Integer(value)
190
-
191
- raise TypeError, ":buffer_size must be positive" unless value.positive?
192
-
193
- value
194
- end
195
-
196
- def option_body_threshold_size(value)
197
- bytes = Integer(value)
198
- raise TypeError, ":body_threshold_size must be positive" unless bytes.positive?
199
-
200
- bytes
201
- end
202
-
203
186
  def option_transport(value)
204
187
  transport = value.to_s
205
188
  raise TypeError, "#{transport} is an unsupported transport type" unless %w[unix].include?(transport)
@@ -215,17 +198,42 @@ module HTTPX
215
198
  Array(value)
216
199
  end
217
200
 
201
+ # number options
202
+ %i[
203
+ max_concurrent_requests max_requests window_size buffer_size
204
+ body_threshold_size debug_level
205
+ ].each do |option|
206
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
207
+ # converts +v+ into an Integer before setting the +#{option}+ option.
208
+ def option_#{option}(value) # def option_max_requests(v)
209
+ value = Integer(value) unless value.infinite?
210
+ raise TypeError, ":#{option} must be positive" unless value.positive? # raise TypeError, ":max_requests must be positive" unless value.positive?
211
+
212
+ value
213
+ end
214
+ OUT
215
+ end
216
+
217
+ # hashable options
218
+ %i[ssl http2_settings resolver_options pool_options].each do |option|
219
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
220
+ # converts +v+ into an Hash before setting the +#{option}+ option.
221
+ def option_#{option}(value) # def option_ssl(v)
222
+ Hash[value]
223
+ end
224
+ OUT
225
+ end
226
+
218
227
  %i[
219
- ssl http2_settings
220
228
  request_class response_class headers_class request_body_class
221
229
  response_body_class connection_class options_class
222
230
  pool_class pool_options
223
- io fallback_protocol debug debug_level resolver_class resolver_options
231
+ io fallback_protocol debug debug_redact resolver_class
224
232
  compress_request_body decompress_response_body
225
- persistent
233
+ persistent close_on_fork
226
234
  ].each do |method_name|
227
235
  class_eval(<<-OUT, __FILE__, __LINE__ + 1)
228
- # sets +v+ as the value of #{method_name}
236
+ # sets +v+ as the value of the +#{method_name}+ option
229
237
  def option_#{method_name}(v); v; end # def option_smth(v); v; end
230
238
  OUT
231
239
  end
@@ -296,35 +304,42 @@ module HTTPX
296
304
  def extend_with_plugin_classes(pl)
297
305
  if defined?(pl::RequestMethods) || defined?(pl::RequestClassMethods)
298
306
  @request_class = @request_class.dup
307
+ SET_TEMPORARY_NAME[@request_class, pl]
299
308
  @request_class.__send__(:include, pl::RequestMethods) if defined?(pl::RequestMethods)
300
309
  @request_class.extend(pl::RequestClassMethods) if defined?(pl::RequestClassMethods)
301
310
  end
302
311
  if defined?(pl::ResponseMethods) || defined?(pl::ResponseClassMethods)
303
312
  @response_class = @response_class.dup
313
+ SET_TEMPORARY_NAME[@response_class, pl]
304
314
  @response_class.__send__(:include, pl::ResponseMethods) if defined?(pl::ResponseMethods)
305
315
  @response_class.extend(pl::ResponseClassMethods) if defined?(pl::ResponseClassMethods)
306
316
  end
307
317
  if defined?(pl::HeadersMethods) || defined?(pl::HeadersClassMethods)
308
318
  @headers_class = @headers_class.dup
319
+ SET_TEMPORARY_NAME[@headers_class, pl]
309
320
  @headers_class.__send__(:include, pl::HeadersMethods) if defined?(pl::HeadersMethods)
310
321
  @headers_class.extend(pl::HeadersClassMethods) if defined?(pl::HeadersClassMethods)
311
322
  end
312
323
  if defined?(pl::RequestBodyMethods) || defined?(pl::RequestBodyClassMethods)
313
324
  @request_body_class = @request_body_class.dup
325
+ SET_TEMPORARY_NAME[@request_body_class, pl]
314
326
  @request_body_class.__send__(:include, pl::RequestBodyMethods) if defined?(pl::RequestBodyMethods)
315
327
  @request_body_class.extend(pl::RequestBodyClassMethods) if defined?(pl::RequestBodyClassMethods)
316
328
  end
317
329
  if defined?(pl::ResponseBodyMethods) || defined?(pl::ResponseBodyClassMethods)
318
330
  @response_body_class = @response_body_class.dup
331
+ SET_TEMPORARY_NAME[@response_body_class, pl]
319
332
  @response_body_class.__send__(:include, pl::ResponseBodyMethods) if defined?(pl::ResponseBodyMethods)
320
333
  @response_body_class.extend(pl::ResponseBodyClassMethods) if defined?(pl::ResponseBodyClassMethods)
321
334
  end
322
335
  if defined?(pl::PoolMethods)
323
336
  @pool_class = @pool_class.dup
337
+ SET_TEMPORARY_NAME[@pool_class, pl]
324
338
  @pool_class.__send__(:include, pl::PoolMethods)
325
339
  end
326
340
  if defined?(pl::ConnectionMethods)
327
341
  @connection_class = @connection_class.dup
342
+ SET_TEMPORARY_NAME[@connection_class, pl]
328
343
  @connection_class.__send__(:include, pl::ConnectionMethods)
329
344
  end
330
345
  return unless defined?(pl::OptionsMethods)
@@ -23,7 +23,7 @@ module HTTPX
23
23
 
24
24
  def reset!
25
25
  @state = :idle
26
- @headers.clear
26
+ @headers = {}
27
27
  @content_length = nil
28
28
  @_has_trailers = nil
29
29
  end
@@ -158,6 +158,7 @@ module HTTPX
158
158
  def load_dependencies(*)
159
159
  require "set"
160
160
  require "digest/sha2"
161
+ require "cgi/escape"
161
162
  end
162
163
 
163
164
  def configure(klass)
@@ -8,6 +8,13 @@ module HTTPX
8
8
  # https://gitlab.com/os85/httpx/-/wikis/Events
9
9
  #
10
10
  module Callbacks
11
+ CALLBACKS = %i[
12
+ connection_opened connection_closed
13
+ request_error
14
+ request_started request_body_chunk request_completed
15
+ response_started response_body_chunk response_completed
16
+ ].freeze
17
+
11
18
  # connection closed user-space errors happen after errors can be surfaced to requests,
12
19
  # so they need to pierce through the scheduler, which is only possible by simulating an
13
20
  # interrupt.
@@ -16,12 +23,7 @@ module HTTPX
16
23
  module InstanceMethods
17
24
  include HTTPX::Callbacks
18
25
 
19
- %i[
20
- connection_opened connection_closed
21
- request_error
22
- request_started request_body_chunk request_completed
23
- response_started response_body_chunk response_completed
24
- ].each do |meth|
26
+ CALLBACKS.each do |meth|
25
27
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
26
28
  def on_#{meth}(&blk) # def on_connection_opened(&blk)
27
29
  on(:#{meth}, &blk) # on(:connection_opened, &blk)
@@ -32,6 +34,17 @@ module HTTPX
32
34
 
33
35
  private
34
36
 
37
+ def branch(options, &blk)
38
+ super(options).tap do |sess|
39
+ CALLBACKS.each do |cb|
40
+ next unless callbacks_for?(cb)
41
+
42
+ sess.callbacks(cb).concat(callbacks(cb))
43
+ end
44
+ sess.wrap(&blk) if blk
45
+ end
46
+ end
47
+
35
48
  def do_init_connection(connection, selector)
36
49
  super
37
50
  connection.on(:open) do
@@ -70,10 +70,11 @@ module HTTPX
70
70
  short_circuit_responses
71
71
  end
72
72
 
73
- def on_response(request, response)
74
- emit(:circuit_open, request) if try_circuit_open(request, response)
75
-
73
+ def set_request_callbacks(request)
76
74
  super
75
+ request.on(:response) do |response|
76
+ emit(:circuit_open, request) if try_circuit_open(request, response)
77
+ end
77
78
  end
78
79
 
79
80
  def try_circuit_open(request, response)
@@ -59,8 +59,6 @@ module HTTPX
59
59
 
60
60
  return @cookies.each(&blk) unless uri
61
61
 
62
- uri = URI(uri)
63
-
64
62
  now = Time.now
65
63
  tpath = uri.path
66
64
 
@@ -83,7 +83,7 @@ module HTTPX
83
83
  scanner.skip(RE_WSP)
84
84
 
85
85
  name, value = scan_name_value(scanner, true)
86
- value = nil if name.empty?
86
+ value = nil if name && name.empty?
87
87
 
88
88
  attrs = {}
89
89
 
@@ -98,15 +98,18 @@ module HTTPX
98
98
 
99
99
  aname, avalue = scan_name_value(scanner, true)
100
100
 
101
- next if aname.empty? || value.nil?
101
+ next if (aname.nil? || aname.empty?) || value.nil?
102
102
 
103
103
  aname.downcase!
104
104
 
105
105
  case aname
106
106
  when "expires"
107
+ next unless avalue
108
+
107
109
  # RFC 6265 5.2.1
108
- (avalue &&= Time.parse(avalue)) || next
110
+ (avalue = Time.parse(avalue)) || next
109
111
  when "max-age"
112
+ next unless avalue
110
113
  # RFC 6265 5.2.2
111
114
  next unless /\A-?\d+\z/.match?(avalue)
112
115
 
@@ -119,7 +122,7 @@ module HTTPX
119
122
  # RFC 6265 5.2.4
120
123
  # A relative path must be ignored rather than normalizing it
121
124
  # to "/".
122
- next unless avalue.start_with?("/")
125
+ next unless avalue && avalue.start_with?("/")
123
126
  when "secure", "httponly"
124
127
  # RFC 6265 5.2.5, 5.2.6
125
128
  avalue = true
@@ -48,15 +48,15 @@ module HTTPX
48
48
 
49
49
  private
50
50
 
51
- def on_response(_request, response)
52
- if response && response.respond_to?(:headers) && (set_cookie = response.headers["set-cookie"])
51
+ def set_request_callbacks(request)
52
+ super
53
+ request.on(:response) do |response|
54
+ next unless response && response.respond_to?(:headers) && (set_cookie = response.headers["set-cookie"])
53
55
 
54
56
  log { "cookies: set-cookie is over #{Cookie::MAX_LENGTH}" } if set_cookie.bytesize > Cookie::MAX_LENGTH
55
57
 
56
58
  @options.cookies.parse(set_cookie)
57
59
  end
58
-
59
- super
60
60
  end
61
61
  end
62
62
 
@@ -149,9 +149,11 @@ module HTTPX
149
149
  retry_start = Utils.now
150
150
  log { "redirecting after #{redirect_after} secs..." }
151
151
  selector.after(redirect_after) do
152
- if request.response
152
+ if (response = request.response)
153
+ response.finish!
154
+ retry_request.response = response
153
155
  # request has terminated abruptly meanwhile
154
- retry_request.emit(:response, request.response)
156
+ retry_request.emit(:response, response)
155
157
  else
156
158
  log { "redirecting (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
157
159
  send_request(retry_request, selector, options)
@@ -15,7 +15,7 @@ module HTTPX
15
15
  end
16
16
 
17
17
  def inspect
18
- "#GRPC::Call(#{grpc_response})"
18
+ "#{self.class}(#{grpc_response})"
19
19
  end
20
20
 
21
21
  def to_s
@@ -42,6 +42,12 @@ module HTTPX
42
42
  end
43
43
  end
44
44
 
45
+ module RequestMethods
46
+ def valid_h2c_verb?
47
+ VALID_H2C_VERBS.include?(@verb)
48
+ end
49
+ end
50
+
45
51
  module ConnectionMethods
46
52
  using URIExtensions
47
53
 
@@ -53,7 +59,7 @@ module HTTPX
53
59
  def send(request)
54
60
  return super if @h2c_handshake
55
61
 
56
- return super unless VALID_H2C_VERBS.include?(request.verb) && request.scheme == "http"
62
+ return super unless request.valid_h2c_verb? && request.scheme == "http"
57
63
 
58
64
  return super if @upgrade_protocol == "h2c"
59
65
 
@@ -24,7 +24,7 @@ module HTTPX
24
24
  else
25
25
  1
26
26
  end
27
- klass.plugin(:retries, max_retries: max_retries, retry_change_requests: true)
27
+ klass.plugin(:retries, max_retries: max_retries)
28
28
  end
29
29
 
30
30
  def self.extra_options(options)
@@ -34,6 +34,27 @@ module HTTPX
34
34
  module InstanceMethods
35
35
  private
36
36
 
37
+ def repeatable_request?(request, _)
38
+ super || begin
39
+ response = request.response
40
+
41
+ return false unless response && response.is_a?(ErrorResponse)
42
+
43
+ error = response.error
44
+
45
+ Retries::RECONNECTABLE_ERRORS.any? { |klass| error.is_a?(klass) }
46
+ end
47
+ end
48
+
49
+ def retryable_error?(ex)
50
+ super &&
51
+ # under the persistent plugin rules, requests are only retried for connection related errors,
52
+ # which do not include request timeout related errors. This only gets overriden if the end user
53
+ # manually changed +:max_retries+ to something else, which means it is aware of the
54
+ # consequences.
55
+ (!ex.is_a?(RequestTimeoutError) || @options.max_retries != 1)
56
+ end
57
+
37
58
  def get_current_selector
38
59
  super(&nil) || begin
39
60
  return unless block_given?
@@ -60,7 +60,7 @@ module HTTPX
60
60
  return unless @io.connected?
61
61
 
62
62
  @parser || begin
63
- @parser = self.class.parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
63
+ @parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
64
64
  parser = @parser
65
65
  parser.extend(ProxyParser)
66
66
  parser.on(:response, &method(:__http_on_connect))
@@ -138,6 +138,8 @@ module HTTPX
138
138
  else
139
139
  pending = @pending + @parser.pending
140
140
  while (req = pending.shift)
141
+ response.finish!
142
+ req.response = response
141
143
  req.emit(:response, response)
142
144
  end
143
145
  reset
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for using the experimental QUERY HTTP method
7
+ #
8
+ # https://gitlab.com/os85/httpx/wikis/Query
9
+ module Query
10
+ def self.subplugins
11
+ {
12
+ retries: QueryRetries,
13
+ }
14
+ end
15
+
16
+ module InstanceMethods
17
+ def query(*uri, **options)
18
+ request("QUERY", uri, **options)
19
+ end
20
+ end
21
+
22
+ module QueryRetries
23
+ module InstanceMethods
24
+ private
25
+
26
+ def repeatable_request?(request, options)
27
+ super || request.verb == "QUERY"
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ register_plugin :query, Query
34
+ end
35
+ end