httpx 0.20.1 → 0.20.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d7bcf0e6c97e369caf28c209d551d4f9667745b6a6a530ae2efd328c611e0e5
4
- data.tar.gz: 822c0437afcefb35e7d4319f91e71c5b4c81720bd0d7a7763db1d1f8bfbc4b1a
3
+ metadata.gz: 3ae7fc4057762abf47253c487544c4e3cd07e31326adec158235f1b53130a8db
4
+ data.tar.gz: 0a09ef99c59c1b3fd1e516066b656b78d888ffd4eed1160c6c9d96f001669410
5
5
  SHA512:
6
- metadata.gz: 747e7d2ca2a4aee92f1774473e9b38565fec3879ee6b6d0ab3c4eeb1313740c6bbd3648c8c6f21b40903f4bf9bc842647b531968debcda747652afb483577b11
7
- data.tar.gz: 88d3bafe38437bebb42960d980dddc85e9e12a917c492203744ded3f93c79736feedf839604cd1a4b080a59c64c96c26c545808cc29d19bcdaa93fa55154bbef
6
+ metadata.gz: 61c786041e8e27c55d45c30e5f8558b1423470254c5be819c2a9daaa61b8bb049e7e892e0a2673799d26339435fd237fbe772bcb02a0373969e7f382eaa76186
7
+ data.tar.gz: f24c113213513d847aa693242902d334e1052deaacdfd944def81d91ea9545113ad8a6a973c5b97ec141fc6474f5513b2a3228c9ba644f8560c29806c10c2886
@@ -0,0 +1,7 @@
1
+ # 0.20.2
2
+
3
+ ## Bugfixes
4
+
5
+ * fix for selector timeout errors closing all connections and ignoring resolvers.
6
+
7
+ Timeout errors on select were being propagated to all pooled connections, although not all of them were being selected on, and not all of them having timed out. plus, resolver timeouts were doing the same, making connections fail with connection timeout error, rather than resolve timeout error. A patch was implemented, where the selector now yields an error to the selected connections, rather than plain raising exception.
@@ -0,0 +1,6 @@
1
+ # 0.20.3
2
+
3
+ ## Bugfixes
4
+
5
+ * DoH resolver wasn't working for non-absolute (the large majority) of domains since v0.19.
6
+ * Allowing a single IP string to be passed to the resolver option `:nameserver` (just like the `resolv` library does), besides the already supported list of IPs.
@@ -0,0 +1,17 @@
1
+ # 0.20.4
2
+
3
+ ## Improvements
4
+
5
+ The `:response_cache` plugin is now more compliant with how the RFC 2616 defines which behaviour caches shall have:
6
+
7
+ * it caches only responses with one of the following status codes: 200, 203, 300, 301, 410.
8
+ * it discards cached responses which become stale.
9
+ * it supports "cache-control" header directives to decided when to cache, to store, what the response "age" is.
10
+ * it can cache more than one response for the same request, provided that the request presents different header values for the headers declared in the "vary" response header (previously, it was only caching the first response, and discarding the remainder).
11
+
12
+
13
+
14
+ ## Bugfixes
15
+
16
+ * fixed DNS resolution bug which caused a loop when a failed connection attempt would cause a new DNS request to be triggered for the same domain, filling up and giving preference to the very IP which failed the attempt.
17
+ * response_cache: request verb is now taken into account, not causing HEAD/GET confusion for the same URL.
@@ -276,6 +276,12 @@ module HTTPX
276
276
  @state == :open || @state == :inactive
277
277
  end
278
278
 
279
+ def raise_timeout_error(interval)
280
+ error = HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
281
+ error.set_backtrace(caller)
282
+ on_error(error)
283
+ end
284
+
279
285
  private
280
286
 
281
287
  def connect
@@ -83,22 +83,41 @@ module HTTPX
83
83
  end
84
84
 
85
85
  module ArrayExtensions
86
- refine Array do
86
+ module FilterMap
87
+ refine Array do
87
88
 
88
- def filter_map
89
- return to_enum(:filter_map) unless block_given?
89
+ def filter_map
90
+ return to_enum(:filter_map) unless block_given?
90
91
 
91
- each_with_object([]) do |item, res|
92
- processed = yield(item)
93
- res << processed if processed
92
+ each_with_object([]) do |item, res|
93
+ processed = yield(item)
94
+ res << processed if processed
95
+ end
94
96
  end
95
97
  end unless Array.method_defined?(:filter_map)
98
+ end
96
99
 
97
- def sum(accumulator = 0, &block)
98
- values = block_given? ? map(&block) : self
99
- values.inject(accumulator, :+)
100
+ module Sum
101
+ refine Array do
102
+ def sum(accumulator = 0, &block)
103
+ values = block_given? ? map(&block) : self
104
+ values.inject(accumulator, :+)
105
+ end
100
106
  end unless Array.method_defined?(:sum)
101
107
  end
108
+
109
+ module Intersect
110
+ refine Array do
111
+ def intersect?(arr)
112
+ if size < arr.size
113
+ smaller = self
114
+ else
115
+ smaller, arr = arr, self
116
+ end
117
+ (array & smaller).size > 0
118
+ end
119
+ end unless Array.method_defined?(:intersect?)
120
+ end
102
121
  end
103
122
 
104
123
  module IOExtensions
data/lib/httpx/io/ssl.rb CHANGED
@@ -145,12 +145,12 @@ module HTTPX
145
145
  "#{super}\n\n" \
146
146
  "SSL connection using #{@io.ssl_version} / #{Array(@io.cipher).first}\n" \
147
147
  "ALPN, server accepted to use #{protocol}\n" \
148
- "Server certificate:\n" \
149
- " subject: #{server_cert.subject}\n" \
150
- " start date: #{server_cert.not_before}\n" \
151
- " expire date: #{server_cert.not_after}\n" \
152
- " issuer: #{server_cert.issuer}\n" \
153
- " SSL certificate verify ok."
148
+ "Server certificate:\n " \
149
+ "subject: #{server_cert.subject}\n " \
150
+ "start date: #{server_cert.not_before}\n " \
151
+ "expire date: #{server_cert.not_after}\n " \
152
+ "issuer: #{server_cert.issuer}\n " \
153
+ "SSL certificate verify ok."
154
154
  end
155
155
  end
156
156
  end
@@ -72,7 +72,7 @@ module HTTPX
72
72
  end
73
73
 
74
74
  module ResponseBodyMethods
75
- using ArrayExtensions
75
+ using ArrayExtensions::FilterMap
76
76
 
77
77
  attr_reader :encodings
78
78
 
@@ -13,35 +13,38 @@ module HTTPX::Plugins
13
13
  @store = {}
14
14
  end
15
15
 
16
- def lookup(uri)
17
- @store[uri]
16
+ def lookup(request)
17
+ responses = @store[request.response_cache_key]
18
+
19
+ return unless responses
20
+
21
+ response = responses.find(&method(:match_by_vary?).curry(2)[request])
22
+
23
+ return unless response && response.fresh?
24
+
25
+ response
18
26
  end
19
27
 
20
- def cached?(uri)
21
- @store.key?(uri)
28
+ def cached?(request)
29
+ lookup(request)
22
30
  end
23
31
 
24
- def cache(uri, response)
25
- @store[uri] = response
32
+ def cache(request, response)
33
+ return unless ResponseCache.cacheable_request?(request) && ResponseCache.cacheable_response?(response)
34
+
35
+ responses = (@store[request.response_cache_key] ||= [])
36
+
37
+ responses.reject!(&method(:match_by_vary?).curry(2)[request])
38
+
39
+ responses << response
26
40
  end
27
41
 
28
42
  def prepare(request)
29
- cached_response = @store[request.uri]
43
+ cached_response = lookup(request)
30
44
 
31
45
  return unless cached_response
32
46
 
33
- original_request = cached_response.instance_variable_get(:@request)
34
-
35
- if (vary = cached_response.headers["vary"])
36
- if vary == "*"
37
- return unless request.headers.same_headers?(original_request.headers)
38
- else
39
- return unless vary.split(/ *, */).all? do |cache_field|
40
- cache_field.downcase!
41
- !original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
42
- end
43
- end
44
- end
47
+ return unless match_by_vary?(request, cached_response)
45
48
 
46
49
  if !request.headers.key?("if-modified-since") && (last_modified = cached_response.headers["last-modified"])
47
50
  request.headers.add("if-modified-since", last_modified)
@@ -51,6 +54,23 @@ module HTTPX::Plugins
51
54
  request.headers.add("if-none-match", etag)
52
55
  end
53
56
  end
57
+
58
+ private
59
+
60
+ def match_by_vary?(request, response)
61
+ vary = response.vary
62
+
63
+ return true unless vary
64
+
65
+ original_request = response.instance_variable_get(:@request)
66
+
67
+ return request.headers.same_headers?(original_request.headers) if vary == %w[*]
68
+
69
+ vary.all? do |cache_field|
70
+ cache_field.downcase!
71
+ !original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
72
+ end
73
+ end
54
74
  end
55
75
  end
56
76
  end
@@ -9,7 +9,9 @@ module HTTPX
9
9
  #
10
10
  module ResponseCache
11
11
  CACHEABLE_VERBS = %i[get head].freeze
12
+ CACHEABLE_STATUS_CODES = [200, 203, 206, 300, 301, 410].freeze
12
13
  private_constant :CACHEABLE_VERBS
14
+ private_constant :CACHEABLE_STATUS_CODES
13
15
 
14
16
  class << self
15
17
  def load_dependencies(*)
@@ -17,14 +19,28 @@ module HTTPX
17
19
  end
18
20
 
19
21
  def cacheable_request?(request)
20
- CACHEABLE_VERBS.include?(request.verb)
22
+ CACHEABLE_VERBS.include?(request.verb) &&
23
+ (
24
+ !request.headers.key?("cache-control") || !request.headers.get("cache-control").include?("no-store")
25
+ )
21
26
  end
22
27
 
23
28
  def cacheable_response?(response)
24
29
  response.is_a?(Response) &&
25
- # partial responses shall not be cached, only full ones.
30
+ (
31
+ response.cache_control.nil? ||
32
+ # TODO: !response.cache_control.include?("private") && is shared cache
33
+ !response.cache_control.include?("no-store")
34
+ ) &&
35
+ CACHEABLE_STATUS_CODES.include?(response.status) &&
36
+ # RFC 2616 13.4 - A response received with a status code of 200, 203, 206, 300, 301 or
37
+ # 410 MAY be stored by a cache and used in reply to a subsequent
38
+ # request, subject to the expiration mechanism, unless a cache-control
39
+ # directive prohibits caching. However, a cache that does not support
40
+ # the Range and Content-Range headers MUST NOT cache 206 (Partial
41
+ # Content) responses.
26
42
  response.status != 206 && (
27
- response.headers.key?("etag") || response.headers.key?("last-modified-at")
43
+ response.headers.key?("etag") || response.headers.key?("last-modified-at") || response.fresh?
28
44
  )
29
45
  end
30
46
 
@@ -52,7 +68,7 @@ module HTTPX
52
68
 
53
69
  def build_request(*)
54
70
  request = super
55
- return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request.uri)
71
+ return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request)
56
72
 
57
73
  @options.response_cache_store.prepare(request)
58
74
 
@@ -62,25 +78,99 @@ module HTTPX
62
78
  def fetch_response(request, *)
63
79
  response = super
64
80
 
65
- if response && ResponseCache.cached_response?(response)
81
+ return unless response
82
+
83
+ if ResponseCache.cached_response?(response)
66
84
  log { "returning cached response for #{request.uri}" }
67
- cached_response = @options.response_cache_store.lookup(request.uri)
85
+ cached_response = @options.response_cache_store.lookup(request)
68
86
 
69
87
  response.copy_from_cached(cached_response)
70
- end
71
88
 
72
- @options.response_cache_store.cache(request.uri, response) if response && ResponseCache.cacheable_response?(response)
89
+ else
90
+ @options.response_cache_store.cache(request, response)
91
+ end
73
92
 
74
93
  response
75
94
  end
76
95
  end
77
96
 
97
+ module RequestMethods
98
+ def response_cache_key
99
+ @response_cache_key ||= Digest::SHA1.hexdigest("httpx-response-cache-#{@verb}#{@uri}")
100
+ end
101
+ end
102
+
78
103
  module ResponseMethods
79
104
  def copy_from_cached(other)
80
105
  @body = other.body
81
106
 
82
107
  @body.__send__(:rewind)
83
108
  end
109
+
110
+ # A response is fresh if its age has not yet exceeded its freshness lifetime.
111
+ def fresh?
112
+ if cache_control
113
+ return false if cache_control.include?("no-cache")
114
+
115
+ # check age: max-age
116
+ max_age = cache_control.find { |directive| directive.start_with?("s-maxage") }
117
+
118
+ max_age ||= cache_control.find { |directive| directive.start_with?("max-age") }
119
+
120
+ max_age = max_age[/age=(\d+)/, 1] if max_age
121
+
122
+ max_age = max_age.to_i if max_age
123
+
124
+ return max_age > age if max_age
125
+ end
126
+
127
+ # check age: expires
128
+ if @headers.key?("expires")
129
+ begin
130
+ expires = Time.httpdate(@headers["expires"])
131
+ rescue ArgumentError
132
+ return true
133
+ end
134
+
135
+ return (expires - Time.now).to_i.positive?
136
+ end
137
+
138
+ true
139
+ end
140
+
141
+ def cache_control
142
+ return @cache_control if defined?(@cache_control)
143
+
144
+ @cache_control = begin
145
+ return unless @headers.key?("cache-control")
146
+
147
+ @headers["cache-control"].split(/ *, */)
148
+ end
149
+ end
150
+
151
+ def vary
152
+ return @vary if defined?(@vary)
153
+
154
+ @vary = begin
155
+ return unless @headers.key?("vary")
156
+
157
+ @headers["vary"].split(/ *, */)
158
+ end
159
+ end
160
+
161
+ private
162
+
163
+ def age
164
+ return @headers["age"].to_i if @headers.key?("age")
165
+
166
+ (Time.now - date).to_i
167
+ end
168
+
169
+ def date
170
+ @date ||= Time.httpdate(@headers["date"])
171
+ rescue NoMethodError, ArgumentError
172
+ Time.now.httpdate
173
+ end
84
174
  end
85
175
  end
86
176
  register_plugin :response_cache, ResponseCache
data/lib/httpx/pool.rb CHANGED
@@ -7,7 +7,7 @@ require "httpx/resolver"
7
7
 
8
8
  module HTTPX
9
9
  class Pool
10
- using ArrayExtensions
10
+ using ArrayExtensions::FilterMap
11
11
  extend Forwardable
12
12
 
13
13
  def_delegator :@timers, :after
@@ -39,6 +39,7 @@ module HTTPX
39
39
  @uri_addresses = nil
40
40
  @resolver = Resolv::DNS.new
41
41
  @resolver.timeouts = @resolver_options.fetch(:timeouts, Resolver::RESOLVE_TIMEOUT)
42
+ @resolver.lazy_initialize
42
43
  end
43
44
 
44
45
  def <<(connection)
@@ -6,7 +6,7 @@ require "resolv"
6
6
  module HTTPX
7
7
  class Resolver::Multi
8
8
  include Callbacks
9
- using ArrayExtensions
9
+ using ArrayExtensions::FilterMap
10
10
 
11
11
  attr_reader :resolvers
12
12
 
@@ -45,7 +45,7 @@ module HTTPX
45
45
  super
46
46
  @ns_index = 0
47
47
  @resolver_options = DEFAULTS.merge(@options.resolver_options)
48
- @nameserver = @resolver_options[:nameserver]
48
+ @nameserver = Array(@resolver_options[:nameserver]) if @resolver_options[:nameserver]
49
49
  @ndots = @resolver_options[:ndots]
50
50
  @search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
51
51
  @_timeouts = Array(@resolver_options[:timeouts])
@@ -118,6 +118,10 @@ module HTTPX
118
118
  @timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
119
119
  end
120
120
 
121
+ def raise_timeout_error(interval)
122
+ do_retry(interval)
123
+ end
124
+
121
125
  private
122
126
 
123
127
  def calculate_interests
@@ -134,10 +138,10 @@ module HTTPX
134
138
  dwrite if calculate_interests == :w
135
139
  end
136
140
 
137
- def do_retry
141
+ def do_retry(loop_time = nil)
138
142
  return if @queries.empty? || !@start_timeout
139
143
 
140
- loop_time = Utils.elapsed_time(@start_timeout)
144
+ loop_time ||= Utils.elapsed_time(@start_timeout)
141
145
 
142
146
  query = @queries.first
143
147
 
@@ -8,6 +8,8 @@ module HTTPX
8
8
  include Callbacks
9
9
  include Loggable
10
10
 
11
+ using ArrayExtensions::Intersect
12
+
11
13
  RECORD_TYPES = {
12
14
  Socket::AF_INET6 => Resolv::DNS::Resource::IN::AAAA,
13
15
  Socket::AF_INET => Resolv::DNS::Resource::IN::A,
@@ -48,6 +50,10 @@ module HTTPX
48
50
  addresses.map! do |address|
49
51
  address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
50
52
  end
53
+
54
+ # double emission check
55
+ return if connection.addresses && !addresses.intersect?(connection.addresses)
56
+
51
57
  log { "resolver: answer #{connection.origin.host}: #{addresses.inspect}" }
52
58
  if @pool && # if triggered by early resolve, pool may not be here yet
53
59
  !connection.io &&
@@ -56,8 +62,12 @@ module HTTPX
56
62
  addresses.first.to_s != connection.origin.host.to_s
57
63
  log { "resolver: A response, applying resolution delay..." }
58
64
  @pool.after(0.05) do
59
- connection.addresses = addresses
60
- emit(:resolve, connection)
65
+ # double emission check
66
+ unless connection.addresses && addresses.intersect?(connection.addresses)
67
+
68
+ connection.addresses = addresses
69
+ emit(:resolve, connection)
70
+ end
61
71
  end
62
72
  else
63
73
  connection.addresses = addresses
@@ -56,7 +56,7 @@ module HTTPX
56
56
 
57
57
  # :nocov:
58
58
  def inspect
59
- "#<Response:#{object_id} "\
59
+ "#<Response:#{object_id} " \
60
60
  "HTTP/#{version} " \
61
61
  "@status=#{@status} " \
62
62
  "@headers=#{@headers} " \
@@ -310,9 +310,12 @@ module HTTPX
310
310
 
311
311
  class ErrorResponse
312
312
  include Loggable
313
+ extend Forwardable
313
314
 
314
315
  attr_reader :request, :error
315
316
 
317
+ def_delegator :@request, :uri
318
+
316
319
  def initialize(request, error, options)
317
320
  @request = request
318
321
  @error = error
@@ -74,7 +74,10 @@ class HTTPX::Selector
74
74
 
75
75
  readers, writers = IO.select(r, w, nil, interval)
76
76
 
77
- raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") if readers.nil? && writers.nil? && interval
77
+ if readers.nil? && writers.nil? && interval
78
+ [*r, *w].each { |io| io.raise_timeout_error(interval) }
79
+ return
80
+ end
78
81
  rescue IOError, SystemCallError
79
82
  @selectables.reject!(&:closed?)
80
83
  retry
@@ -108,7 +111,11 @@ class HTTPX::Selector
108
111
  when nil then return
109
112
  end
110
113
 
111
- raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select") unless result || interval.nil?
114
+ unless result || interval.nil?
115
+ io.raise_timeout_error(interval)
116
+ return
117
+ end
118
+ # raise HTTPX::TimeoutError.new(interval, "timed out while waiting on select")
112
119
 
113
120
  yield io
114
121
  rescue IOError, SystemCallError
@@ -9,7 +9,7 @@ module HTTPX::Transcoder
9
9
  module_function
10
10
 
11
11
  class Encoder
12
- using HTTPX::ArrayExtensions
12
+ using HTTPX::ArrayExtensions::Sum
13
13
  extend Forwardable
14
14
 
15
15
  def_delegator :@raw, :to_s
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.20.1"
4
+ VERSION = "0.20.4"
5
5
  end
data/sig/connection.rbs CHANGED
@@ -72,6 +72,9 @@ module HTTPX
72
72
  def timeout: () -> Numeric?
73
73
 
74
74
  def deactivate: () -> void
75
+
76
+ def raise_timeout_error: (Numeric interval) -> void
77
+
75
78
  private
76
79
 
77
80
  def initialize: (String, URI::Generic, options) -> untyped
@@ -8,15 +8,19 @@ module HTTPX
8
8
  def self?.cached_response?: (response response) -> bool
9
9
 
10
10
  class Store
11
- @store: Hash[URI::Generic, Response]
11
+ @store: Hash[String, Array[Response]]
12
12
 
13
- def lookup: (URI::Generic uri) -> Response?
13
+ def lookup: (Request request) -> Response?
14
14
 
15
- def cached?: (URI::Generic uri) -> bool
15
+ def cached?: (Request request) -> boolish
16
16
 
17
- def cache: (URI::Generic uri, Response response) -> void
17
+ def cache: (Request request, Response response) -> void
18
18
 
19
19
  def prepare: (Request request) -> void
20
+
21
+ private
22
+
23
+ def match_by_vary?: (Request request, Response response) -> bool
20
24
  end
21
25
 
22
26
  module InstanceMethods
@@ -25,8 +29,24 @@ module HTTPX
25
29
  def clear_response_cache: () -> void
26
30
  end
27
31
 
32
+ module RequestMethods
33
+ def response_cache_key: () -> String
34
+ end
35
+
28
36
  module ResponseMethods
29
37
  def copy_from_cached: (Response other) -> void
38
+
39
+ def fresh?: () -> bool
40
+
41
+ def cache_control: () -> Array[String]?
42
+
43
+ def vary: () -> Array[String]?
44
+
45
+ private
46
+
47
+ def age: () -> Integer
48
+
49
+ def date: () -> Time
30
50
  end
31
51
  end
32
52
 
@@ -27,6 +27,8 @@ module HTTPX
27
27
 
28
28
  def timeout: () -> Numeric?
29
29
 
30
+ def raise_timeout_error: (Numeric interval) -> void
31
+
30
32
  private
31
33
 
32
34
  def initialize: (ip_family family, options options) -> void
@@ -35,7 +37,7 @@ module HTTPX
35
37
 
36
38
  def consume: () -> void
37
39
 
38
- def do_retry: () -> void
40
+ def do_retry: (?Numeric loop_time) -> void
39
41
 
40
42
  def dread: (Integer) -> void
41
43
  | () -> void
data/sig/response.rbs CHANGED
@@ -96,6 +96,7 @@ module HTTPX
96
96
  class ErrorResponse
97
97
  include _Response
98
98
  include Loggable
99
+ extend Forwardable
99
100
 
100
101
  @options: Options
101
102
  @error: Exception
@@ -104,6 +105,8 @@ module HTTPX
104
105
 
105
106
  def status: () -> (Integer | _ToS)
106
107
 
108
+ def uri: () -> URI::Generic
109
+
107
110
  private
108
111
 
109
112
  def initialize: (Request, Exception, options) -> untyped
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.1
4
+ version: 0.20.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-21 00:00:00.000000000 Z
11
+ date: 2022-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -82,6 +82,9 @@ extra_rdoc_files:
82
82
  - doc/release_notes/0_1_0.md
83
83
  - doc/release_notes/0_20_0.md
84
84
  - doc/release_notes/0_20_1.md
85
+ - doc/release_notes/0_20_2.md
86
+ - doc/release_notes/0_20_3.md
87
+ - doc/release_notes/0_24_4.md
85
88
  - doc/release_notes/0_2_0.md
86
89
  - doc/release_notes/0_2_1.md
87
90
  - doc/release_notes/0_3_0.md
@@ -156,6 +159,9 @@ files:
156
159
  - doc/release_notes/0_1_0.md
157
160
  - doc/release_notes/0_20_0.md
158
161
  - doc/release_notes/0_20_1.md
162
+ - doc/release_notes/0_20_2.md
163
+ - doc/release_notes/0_20_3.md
164
+ - doc/release_notes/0_24_4.md
159
165
  - doc/release_notes/0_2_0.md
160
166
  - doc/release_notes/0_2_1.md
161
167
  - doc/release_notes/0_3_0.md
@@ -360,7 +366,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
360
366
  - !ruby/object:Gem::Version
361
367
  version: '0'
362
368
  requirements: []
363
- rubygems_version: 3.2.32
369
+ rubygems_version: 3.3.7
364
370
  signing_key:
365
371
  specification_version: 4
366
372
  summary: HTTPX, to the future, and beyond