Open
Conversation
Bumps [httpx](https://gitlab.com/os85/httpx) from 1.7.2 to 1.7.3. - [Commits](https://gitlab.com/os85/httpx/compare/v1.7.2...v1.7.3) --- updated-dependencies: - dependency-name: httpx dependency-version: 1.7.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
3 similar comments
Contributor
Contributor
Contributor
Contributor
gem compare http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT date:
1.1.1: 2025-04-16 00:00:00 UTC
1.1.3: 1980-01-02 00:00:00 UTC
DIFFERENT rubygems_version:
1.1.1: 3.6.2
1.1.3: 3.6.9
DIFFERENT version:
1.1.1: 1.1.1
1.1.3: 1.1.3
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb +2/-1
lib/http/2/extensions.rb +1/-1
lib/http/2/flow_buffer.rb +5/-0
lib/http/2/framer.rb +2/-2
lib/http/2/header/encoding_context.rb +16/-13
lib/http/2/header/huffman.rb +1/-1
lib/http/2/version.rb +1/-1
sig/frame_buffer.rbs +2/-0
sig/header/encoding_context.rbs +3/-1 |
3 similar comments
Contributor
gem compare http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT date:
1.1.1: 2025-04-16 00:00:00 UTC
1.1.3: 1980-01-02 00:00:00 UTC
DIFFERENT rubygems_version:
1.1.1: 3.6.2
1.1.3: 3.6.9
DIFFERENT version:
1.1.1: 1.1.1
1.1.3: 1.1.3
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb +2/-1
lib/http/2/extensions.rb +1/-1
lib/http/2/flow_buffer.rb +5/-0
lib/http/2/framer.rb +2/-2
lib/http/2/header/encoding_context.rb +16/-13
lib/http/2/header/huffman.rb +1/-1
lib/http/2/version.rb +1/-1
sig/frame_buffer.rbs +2/-0
sig/header/encoding_context.rbs +3/-1 |
Contributor
gem compare http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT date:
1.1.1: 2025-04-16 00:00:00 UTC
1.1.3: 1980-01-02 00:00:00 UTC
DIFFERENT rubygems_version:
1.1.1: 3.6.2
1.1.3: 3.6.9
DIFFERENT version:
1.1.1: 1.1.1
1.1.3: 1.1.3
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb +2/-1
lib/http/2/extensions.rb +1/-1
lib/http/2/flow_buffer.rb +5/-0
lib/http/2/framer.rb +2/-2
lib/http/2/header/encoding_context.rb +16/-13
lib/http/2/header/huffman.rb +1/-1
lib/http/2/version.rb +1/-1
sig/frame_buffer.rbs +2/-0
sig/header/encoding_context.rbs +3/-1 |
Contributor
gem compare http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT date:
1.1.1: 2025-04-16 00:00:00 UTC
1.1.3: 1980-01-02 00:00:00 UTC
DIFFERENT rubygems_version:
1.1.1: 3.6.2
1.1.3: 3.6.9
DIFFERENT version:
1.1.1: 1.1.1
1.1.3: 1.1.3
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb +2/-1
lib/http/2/extensions.rb +1/-1
lib/http/2/flow_buffer.rb +5/-0
lib/http/2/framer.rb +2/-2
lib/http/2/header/encoding_context.rb +16/-13
lib/http/2/header/huffman.rb +1/-1
lib/http/2/version.rb +1/-1
sig/frame_buffer.rbs +2/-0
sig/header/encoding_context.rbs +3/-1 |
Contributor
Contributor
gem compare --diff http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/connection.rb 2026-03-06 03:33:25.406506122 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/connection.rb 2026-03-06 03:33:25.410506079 +0000
@@ -528 +528,2 @@
- connection_error
+ # 6.8. GOAWAY
+ # An endpoint MAY send multiple GOAWAY frames if circumstances change.
lib/http/2/extensions.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/extensions.rb 2026-03-06 03:33:25.406506122 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/extensions.rb 2026-03-06 03:33:25.411506068 +0000
@@ -28 +28 @@
- chunk = str.byteslice(0..n - 1)
+ chunk = str.byteslice(0..(n - 1))
lib/http/2/flow_buffer.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/flow_buffer.rb 2026-03-06 03:33:25.406506122 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/flow_buffer.rb 2026-03-06 03:33:25.411506068 +0000
@@ -129,0 +130,5 @@
+ def clear
+ @buffer.clear
+ @bytesize = 0
+ end
+
lib/http/2/framer.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/framer.rb 2026-03-06 03:33:25.406506122 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/framer.rb 2026-03-06 03:33:25.411506068 +0000
@@ -188 +188 @@
- name if flags.anybits?((1 << pos))
+ name if flags.anybits?(1 << pos)
@@ -362 +362 @@
- append_str(bytes, ("\0" * padlen))
+ append_str(bytes, "\0" * padlen)
lib/http/2/header/encoding_context.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:25.407506111 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:25.411506068 +0000
@@ -214 +214 @@
- if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
+ if type == :incremental && size_check?(name.bytesize + value.bytesize + 32)
@@ -305 +305 @@
- size_check(0)
+ resize_table(0)
@@ -315,0 +316,12 @@
+ def resize_table(cmdsize)
+ return if @table.empty?
+
+ while @current_table_size + cmdsize > @limit
+
+ name, value = @table.pop
+ @current_table_size -= name.bytesize + value.bytesize + 32
+ break if @table.empty?
+
+ end
+ end
+
@@ -321,11 +333,2 @@
- def size_check(cmdsize)
- unless @table.empty?
- while @current_table_size + cmdsize > @limit
-
- name, value = @table.pop
- @current_table_size -= name.bytesize + value.bytesize + 32
- break if @table.empty?
-
- end
- end
-
+ def size_check?(cmdsize)
+ resize_table(cmdsize)
lib/http/2/header/huffman.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/header/huffman.rb 2026-03-06 03:33:25.407506111 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/header/huffman.rb 2026-03-06 03:33:25.411506068 +0000
@@ -32 +32 @@
- append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
+ append_str(bitstring, "1" * ((8 - bitstring.size) % 8))
lib/http/2/version.rb
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/lib/http/2/version.rb 2026-03-06 03:33:25.408506100 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/lib/http/2/version.rb 2026-03-06 03:33:25.412506057 +0000
@@ -4 +4 @@
- VERSION = "1.1.1"
+ VERSION = "1.1.3"
sig/frame_buffer.rbs
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/sig/frame_buffer.rbs 2026-03-06 03:33:25.408506100 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/sig/frame_buffer.rbs 2026-03-06 03:33:25.413506046 +0000
@@ -6,0 +7,2 @@
+ def clear: () -> void
+
sig/header/encoding_context.rbs
--- /tmp/d20260306-475-bg9229/http-2-1.1.1/sig/header/encoding_context.rbs 2026-03-06 03:33:25.409506089 +0000
+++ /tmp/d20260306-475-bg9229/http-2-1.1.3/sig/header/encoding_context.rbs 2026-03-06 03:33:25.413506046 +0000
@@ -49 +49,3 @@
- def size_check: (Integer cmdsize) -> bool
+ def resize_table: (Integer cmdsize) -> void
+
+ def size_check?: (Integer cmdsize) -> bool |
Contributor
gem compare --diff http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/connection.rb 2026-03-06 03:33:26.466814403 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/connection.rb 2026-03-06 03:33:26.470814450 +0000
@@ -528 +528,2 @@
- connection_error
+ # 6.8. GOAWAY
+ # An endpoint MAY send multiple GOAWAY frames if circumstances change.
lib/http/2/extensions.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/extensions.rb 2026-03-06 03:33:26.466814403 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/extensions.rb 2026-03-06 03:33:26.471814462 +0000
@@ -28 +28 @@
- chunk = str.byteslice(0..n - 1)
+ chunk = str.byteslice(0..(n - 1))
lib/http/2/flow_buffer.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/flow_buffer.rb 2026-03-06 03:33:26.466814403 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/flow_buffer.rb 2026-03-06 03:33:26.471814462 +0000
@@ -129,0 +130,5 @@
+ def clear
+ @buffer.clear
+ @bytesize = 0
+ end
+
lib/http/2/framer.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/framer.rb 2026-03-06 03:33:26.466814403 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/framer.rb 2026-03-06 03:33:26.471814462 +0000
@@ -188 +188 @@
- name if flags.anybits?((1 << pos))
+ name if flags.anybits?(1 << pos)
@@ -362 +362 @@
- append_str(bytes, ("\0" * padlen))
+ append_str(bytes, "\0" * padlen)
lib/http/2/header/encoding_context.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:26.467814414 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:26.471814462 +0000
@@ -214 +214 @@
- if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
+ if type == :incremental && size_check?(name.bytesize + value.bytesize + 32)
@@ -305 +305 @@
- size_check(0)
+ resize_table(0)
@@ -315,0 +316,12 @@
+ def resize_table(cmdsize)
+ return if @table.empty?
+
+ while @current_table_size + cmdsize > @limit
+
+ name, value = @table.pop
+ @current_table_size -= name.bytesize + value.bytesize + 32
+ break if @table.empty?
+
+ end
+ end
+
@@ -321,11 +333,2 @@
- def size_check(cmdsize)
- unless @table.empty?
- while @current_table_size + cmdsize > @limit
-
- name, value = @table.pop
- @current_table_size -= name.bytesize + value.bytesize + 32
- break if @table.empty?
-
- end
- end
-
+ def size_check?(cmdsize)
+ resize_table(cmdsize)
lib/http/2/header/huffman.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/header/huffman.rb 2026-03-06 03:33:26.467814414 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/header/huffman.rb 2026-03-06 03:33:26.471814462 +0000
@@ -32 +32 @@
- append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
+ append_str(bitstring, "1" * ((8 - bitstring.size) % 8))
lib/http/2/version.rb
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/lib/http/2/version.rb 2026-03-06 03:33:26.467814414 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/lib/http/2/version.rb 2026-03-06 03:33:26.472814474 +0000
@@ -4 +4 @@
- VERSION = "1.1.1"
+ VERSION = "1.1.3"
sig/frame_buffer.rbs
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/sig/frame_buffer.rbs 2026-03-06 03:33:26.469814438 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/sig/frame_buffer.rbs 2026-03-06 03:33:26.473814486 +0000
@@ -6,0 +7,2 @@
+ def clear: () -> void
+
sig/header/encoding_context.rbs
--- /tmp/d20260306-429-vh94i2/http-2-1.1.1/sig/header/encoding_context.rbs 2026-03-06 03:33:26.469814438 +0000
+++ /tmp/d20260306-429-vh94i2/http-2-1.1.3/sig/header/encoding_context.rbs 2026-03-06 03:33:26.474814498 +0000
@@ -49 +49,3 @@
- def size_check: (Integer cmdsize) -> bool
+ def resize_table: (Integer cmdsize) -> void
+
+ def size_check?: (Integer cmdsize) -> bool |
Contributor
gem compare --diff http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/connection.rb 2026-03-06 03:33:26.742401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/connection.rb 2026-03-06 03:33:26.747401625 +0000
@@ -528 +528,2 @@
- connection_error
+ # 6.8. GOAWAY
+ # An endpoint MAY send multiple GOAWAY frames if circumstances change.
lib/http/2/extensions.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/extensions.rb 2026-03-06 03:33:26.742401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/extensions.rb 2026-03-06 03:33:26.747401625 +0000
@@ -28 +28 @@
- chunk = str.byteslice(0..n - 1)
+ chunk = str.byteslice(0..(n - 1))
lib/http/2/flow_buffer.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/flow_buffer.rb 2026-03-06 03:33:26.742401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/flow_buffer.rb 2026-03-06 03:33:26.747401625 +0000
@@ -129,0 +130,5 @@
+ def clear
+ @buffer.clear
+ @bytesize = 0
+ end
+
lib/http/2/framer.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/framer.rb 2026-03-06 03:33:26.742401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/framer.rb 2026-03-06 03:33:26.747401625 +0000
@@ -188 +188 @@
- name if flags.anybits?((1 << pos))
+ name if flags.anybits?(1 << pos)
@@ -362 +362 @@
- append_str(bytes, ("\0" * padlen))
+ append_str(bytes, "\0" * padlen)
lib/http/2/header/encoding_context.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:26.742401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:26.748401624 +0000
@@ -214 +214 @@
- if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
+ if type == :incremental && size_check?(name.bytesize + value.bytesize + 32)
@@ -305 +305 @@
- size_check(0)
+ resize_table(0)
@@ -315,0 +316,12 @@
+ def resize_table(cmdsize)
+ return if @table.empty?
+
+ while @current_table_size + cmdsize > @limit
+
+ name, value = @table.pop
+ @current_table_size -= name.bytesize + value.bytesize + 32
+ break if @table.empty?
+
+ end
+ end
+
@@ -321,11 +333,2 @@
- def size_check(cmdsize)
- unless @table.empty?
- while @current_table_size + cmdsize > @limit
-
- name, value = @table.pop
- @current_table_size -= name.bytesize + value.bytesize + 32
- break if @table.empty?
-
- end
- end
-
+ def size_check?(cmdsize)
+ resize_table(cmdsize)
lib/http/2/header/huffman.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/header/huffman.rb 2026-03-06 03:33:26.743401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/header/huffman.rb 2026-03-06 03:33:26.748401624 +0000
@@ -32 +32 @@
- append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
+ append_str(bitstring, "1" * ((8 - bitstring.size) % 8))
lib/http/2/version.rb
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/lib/http/2/version.rb 2026-03-06 03:33:26.743401628 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/lib/http/2/version.rb 2026-03-06 03:33:26.749401624 +0000
@@ -4 +4 @@
- VERSION = "1.1.1"
+ VERSION = "1.1.3"
sig/frame_buffer.rbs
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/sig/frame_buffer.rbs 2026-03-06 03:33:26.745401626 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/sig/frame_buffer.rbs 2026-03-06 03:33:26.750401623 +0000
@@ -6,0 +7,2 @@
+ def clear: () -> void
+
sig/header/encoding_context.rbs
--- /tmp/d20260306-680-d8jjym/http-2-1.1.1/sig/header/encoding_context.rbs 2026-03-06 03:33:26.745401626 +0000
+++ /tmp/d20260306-680-d8jjym/http-2-1.1.3/sig/header/encoding_context.rbs 2026-03-06 03:33:26.750401623 +0000
@@ -49 +49,3 @@
- def size_check: (Integer cmdsize) -> bool
+ def resize_table: (Integer cmdsize) -> void
+
+ def size_check?: (Integer cmdsize) -> bool |
Contributor
gem compare http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT date:
1.1.1: 2025-04-16 00:00:00 UTC
1.1.3: 1980-01-02 00:00:00 UTC
DIFFERENT rubygems_version:
1.1.1: 3.6.2
1.1.3: 3.6.9
DIFFERENT version:
1.1.1: 1.1.1
1.1.3: 1.1.3
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb +2/-1
lib/http/2/extensions.rb +1/-1
lib/http/2/flow_buffer.rb +5/-0
lib/http/2/framer.rb +2/-2
lib/http/2/header/encoding_context.rb +16/-13
lib/http/2/header/huffman.rb +1/-1
lib/http/2/version.rb +1/-1
sig/frame_buffer.rbs +2/-0
sig/header/encoding_context.rbs +3/-1 |
Contributor
gem compare httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT version:
1.7.2: 1.7.2
1.7.3: 1.7.3
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
lib/httpx/adapters/webmock.rb +3/-4
lib/httpx/connection.rb +18/-4
lib/httpx/connection/http1.rb +0/-1
lib/httpx/connection/http2.rb +41/-30
lib/httpx/plugins/auth/digest.rb +2/-1
lib/httpx/plugins/cookies.rb +7/-3
lib/httpx/plugins/cookies/cookie.rb +34/-11
lib/httpx/plugins/cookies/jar.rb +93/-18
lib/httpx/plugins/expect.rb +26/-2
lib/httpx/plugins/fiber_concurrency.rb +2/-4
lib/httpx/plugins/follow_redirects.rb +3/-1
lib/httpx/plugins/rate_limiter.rb +19/-19
lib/httpx/plugins/retries.rb +11/-7
lib/httpx/plugins/ssrf_filter.rb +1/-0
lib/httpx/plugins/stream_bidi.rb +6/-0
lib/httpx/request.rb +1/-1
lib/httpx/resolver/resolver.rb +5/-0
lib/httpx/selector.rb +4/-4
lib/httpx/session.rb +6/-5
lib/httpx/version.rb +1/-1
sig/chainable.rbs +1/-1
sig/connection.rbs +3/-0
sig/connection/http2.rbs +8/-4
sig/plugins/cookies.rbs +2/-0
sig/plugins/cookies/cookie.rbs +3/-2
sig/plugins/cookies/jar.rbs +11/-0
sig/plugins/expect.rbs +17/-2
sig/plugins/proxy/socks4.rbs +4/-0
sig/plugins/rate_limiter.rbs +2/-2
sig/plugins/response_cache.rbs +3/-3
sig/plugins/retries.rbs +17/-13
sig/request.rbs +1/-0
sig/resolver/native.rbs +2/-0
sig/resolver/resolver.rbs +2/-2
sig/selector.rbs +4/-0
DIFFERENT extra_rdoc_files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
DIFFERENT runtime dependencies:
1.7.2->1.7.3:
* Updated:
http-2 from: [">= 1.0.0"] to: [">= 1.1.3"] |
2 similar comments
Contributor
gem compare httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT version:
1.7.2: 1.7.2
1.7.3: 1.7.3
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
lib/httpx/adapters/webmock.rb +3/-4
lib/httpx/connection.rb +18/-4
lib/httpx/connection/http1.rb +0/-1
lib/httpx/connection/http2.rb +41/-30
lib/httpx/plugins/auth/digest.rb +2/-1
lib/httpx/plugins/cookies.rb +7/-3
lib/httpx/plugins/cookies/cookie.rb +34/-11
lib/httpx/plugins/cookies/jar.rb +93/-18
lib/httpx/plugins/expect.rb +26/-2
lib/httpx/plugins/fiber_concurrency.rb +2/-4
lib/httpx/plugins/follow_redirects.rb +3/-1
lib/httpx/plugins/rate_limiter.rb +19/-19
lib/httpx/plugins/retries.rb +11/-7
lib/httpx/plugins/ssrf_filter.rb +1/-0
lib/httpx/plugins/stream_bidi.rb +6/-0
lib/httpx/request.rb +1/-1
lib/httpx/resolver/resolver.rb +5/-0
lib/httpx/selector.rb +4/-4
lib/httpx/session.rb +6/-5
lib/httpx/version.rb +1/-1
sig/chainable.rbs +1/-1
sig/connection.rbs +3/-0
sig/connection/http2.rbs +8/-4
sig/plugins/cookies.rbs +2/-0
sig/plugins/cookies/cookie.rbs +3/-2
sig/plugins/cookies/jar.rbs +11/-0
sig/plugins/expect.rbs +17/-2
sig/plugins/proxy/socks4.rbs +4/-0
sig/plugins/rate_limiter.rbs +2/-2
sig/plugins/response_cache.rbs +3/-3
sig/plugins/retries.rbs +17/-13
sig/request.rbs +1/-0
sig/resolver/native.rbs +2/-0
sig/resolver/resolver.rbs +2/-2
sig/selector.rbs +4/-0
DIFFERENT extra_rdoc_files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
DIFFERENT runtime dependencies:
1.7.2->1.7.3:
* Updated:
http-2 from: [">= 1.0.0"] to: [">= 1.1.3"] |
Contributor
gem compare httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT version:
1.7.2: 1.7.2
1.7.3: 1.7.3
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
lib/httpx/adapters/webmock.rb +3/-4
lib/httpx/connection.rb +18/-4
lib/httpx/connection/http1.rb +0/-1
lib/httpx/connection/http2.rb +41/-30
lib/httpx/plugins/auth/digest.rb +2/-1
lib/httpx/plugins/cookies.rb +7/-3
lib/httpx/plugins/cookies/cookie.rb +34/-11
lib/httpx/plugins/cookies/jar.rb +93/-18
lib/httpx/plugins/expect.rb +26/-2
lib/httpx/plugins/fiber_concurrency.rb +2/-4
lib/httpx/plugins/follow_redirects.rb +3/-1
lib/httpx/plugins/rate_limiter.rb +19/-19
lib/httpx/plugins/retries.rb +11/-7
lib/httpx/plugins/ssrf_filter.rb +1/-0
lib/httpx/plugins/stream_bidi.rb +6/-0
lib/httpx/request.rb +1/-1
lib/httpx/resolver/resolver.rb +5/-0
lib/httpx/selector.rb +4/-4
lib/httpx/session.rb +6/-5
lib/httpx/version.rb +1/-1
sig/chainable.rbs +1/-1
sig/connection.rbs +3/-0
sig/connection/http2.rbs +8/-4
sig/plugins/cookies.rbs +2/-0
sig/plugins/cookies/cookie.rbs +3/-2
sig/plugins/cookies/jar.rbs +11/-0
sig/plugins/expect.rbs +17/-2
sig/plugins/proxy/socks4.rbs +4/-0
sig/plugins/rate_limiter.rbs +2/-2
sig/plugins/response_cache.rbs +3/-3
sig/plugins/retries.rbs +17/-13
sig/request.rbs +1/-0
sig/resolver/native.rbs +2/-0
sig/resolver/resolver.rbs +2/-2
sig/selector.rbs +4/-0
DIFFERENT extra_rdoc_files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
DIFFERENT runtime dependencies:
1.7.2->1.7.3:
* Updated:
http-2 from: [">= 1.0.0"] to: [">= 1.1.3"] |
Contributor
gem compare --diff http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/connection.rb 2026-03-06 03:33:47.836373828 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/connection.rb 2026-03-06 03:33:47.840373854 +0000
@@ -528 +528,2 @@
- connection_error
+ # 6.8. GOAWAY
+ # An endpoint MAY send multiple GOAWAY frames if circumstances change.
lib/http/2/extensions.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/extensions.rb 2026-03-06 03:33:47.836373828 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/extensions.rb 2026-03-06 03:33:47.841373861 +0000
@@ -28 +28 @@
- chunk = str.byteslice(0..n - 1)
+ chunk = str.byteslice(0..(n - 1))
lib/http/2/flow_buffer.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/flow_buffer.rb 2026-03-06 03:33:47.836373828 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/flow_buffer.rb 2026-03-06 03:33:47.841373861 +0000
@@ -129,0 +130,5 @@
+ def clear
+ @buffer.clear
+ @bytesize = 0
+ end
+
lib/http/2/framer.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/framer.rb 2026-03-06 03:33:47.836373828 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/framer.rb 2026-03-06 03:33:47.841373861 +0000
@@ -188 +188 @@
- name if flags.anybits?((1 << pos))
+ name if flags.anybits?(1 << pos)
@@ -362 +362 @@
- append_str(bytes, ("\0" * padlen))
+ append_str(bytes, "\0" * padlen)
lib/http/2/header/encoding_context.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:47.837373834 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:47.841373861 +0000
@@ -214 +214 @@
- if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
+ if type == :incremental && size_check?(name.bytesize + value.bytesize + 32)
@@ -305 +305 @@
- size_check(0)
+ resize_table(0)
@@ -315,0 +316,12 @@
+ def resize_table(cmdsize)
+ return if @table.empty?
+
+ while @current_table_size + cmdsize > @limit
+
+ name, value = @table.pop
+ @current_table_size -= name.bytesize + value.bytesize + 32
+ break if @table.empty?
+
+ end
+ end
+
@@ -321,11 +333,2 @@
- def size_check(cmdsize)
- unless @table.empty?
- while @current_table_size + cmdsize > @limit
-
- name, value = @table.pop
- @current_table_size -= name.bytesize + value.bytesize + 32
- break if @table.empty?
-
- end
- end
-
+ def size_check?(cmdsize)
+ resize_table(cmdsize)
lib/http/2/header/huffman.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/header/huffman.rb 2026-03-06 03:33:47.837373834 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/header/huffman.rb 2026-03-06 03:33:47.841373861 +0000
@@ -32 +32 @@
- append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
+ append_str(bitstring, "1" * ((8 - bitstring.size) % 8))
lib/http/2/version.rb
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/lib/http/2/version.rb 2026-03-06 03:33:47.837373834 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/lib/http/2/version.rb 2026-03-06 03:33:47.842373868 +0000
@@ -4 +4 @@
- VERSION = "1.1.1"
+ VERSION = "1.1.3"
sig/frame_buffer.rbs
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/sig/frame_buffer.rbs 2026-03-06 03:33:47.839373848 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/sig/frame_buffer.rbs 2026-03-06 03:33:47.843373874 +0000
@@ -6,0 +7,2 @@
+ def clear: () -> void
+
sig/header/encoding_context.rbs
--- /tmp/d20260306-690-uxx6tk/http-2-1.1.1/sig/header/encoding_context.rbs 2026-03-06 03:33:47.839373848 +0000
+++ /tmp/d20260306-690-uxx6tk/http-2-1.1.3/sig/header/encoding_context.rbs 2026-03-06 03:33:47.843373874 +0000
@@ -49 +49,3 @@
- def size_check: (Integer cmdsize) -> bool
+ def resize_table: (Integer cmdsize) -> void
+
+ def size_check?: (Integer cmdsize) -> bool |
Contributor
gem compare --diff http-2 1.1.1 1.1.3Compared versions: ["1.1.1", "1.1.3"]
DIFFERENT files:
1.1.1->1.1.3:
* Changed:
lib/http/2/connection.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/connection.rb 2026-03-06 03:33:49.813065876 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/connection.rb 2026-03-06 03:33:49.818065867 +0000
@@ -528 +528,2 @@
- connection_error
+ # 6.8. GOAWAY
+ # An endpoint MAY send multiple GOAWAY frames if circumstances change.
lib/http/2/extensions.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/extensions.rb 2026-03-06 03:33:49.814065874 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/extensions.rb 2026-03-06 03:33:49.819065865 +0000
@@ -28 +28 @@
- chunk = str.byteslice(0..n - 1)
+ chunk = str.byteslice(0..(n - 1))
lib/http/2/flow_buffer.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/flow_buffer.rb 2026-03-06 03:33:49.814065874 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/flow_buffer.rb 2026-03-06 03:33:49.819065865 +0000
@@ -129,0 +130,5 @@
+ def clear
+ @buffer.clear
+ @bytesize = 0
+ end
+
lib/http/2/framer.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/framer.rb 2026-03-06 03:33:49.814065874 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/framer.rb 2026-03-06 03:33:49.819065865 +0000
@@ -188 +188 @@
- name if flags.anybits?((1 << pos))
+ name if flags.anybits?(1 << pos)
@@ -362 +362 @@
- append_str(bytes, ("\0" * padlen))
+ append_str(bytes, "\0" * padlen)
lib/http/2/header/encoding_context.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:49.814065874 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/header/encoding_context.rb 2026-03-06 03:33:49.819065865 +0000
@@ -214 +214 @@
- if type == :incremental && size_check(name.bytesize + value.bytesize + 32)
+ if type == :incremental && size_check?(name.bytesize + value.bytesize + 32)
@@ -305 +305 @@
- size_check(0)
+ resize_table(0)
@@ -315,0 +316,12 @@
+ def resize_table(cmdsize)
+ return if @table.empty?
+
+ while @current_table_size + cmdsize > @limit
+
+ name, value = @table.pop
+ @current_table_size -= name.bytesize + value.bytesize + 32
+ break if @table.empty?
+
+ end
+ end
+
@@ -321,11 +333,2 @@
- def size_check(cmdsize)
- unless @table.empty?
- while @current_table_size + cmdsize > @limit
-
- name, value = @table.pop
- @current_table_size -= name.bytesize + value.bytesize + 32
- break if @table.empty?
-
- end
- end
-
+ def size_check?(cmdsize)
+ resize_table(cmdsize)
lib/http/2/header/huffman.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/header/huffman.rb 2026-03-06 03:33:49.814065874 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/header/huffman.rb 2026-03-06 03:33:49.820065863 +0000
@@ -32 +32 @@
- append_str(bitstring, ("1" * ((8 - bitstring.size) % 8)))
+ append_str(bitstring, "1" * ((8 - bitstring.size) % 8))
lib/http/2/version.rb
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/lib/http/2/version.rb 2026-03-06 03:33:49.815065872 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/lib/http/2/version.rb 2026-03-06 03:33:49.820065863 +0000
@@ -4 +4 @@
- VERSION = "1.1.1"
+ VERSION = "1.1.3"
sig/frame_buffer.rbs
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/sig/frame_buffer.rbs 2026-03-06 03:33:49.817065869 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/sig/frame_buffer.rbs 2026-03-06 03:33:49.821065861 +0000
@@ -6,0 +7,2 @@
+ def clear: () -> void
+
sig/header/encoding_context.rbs
--- /tmp/d20260306-757-q5npf0/http-2-1.1.1/sig/header/encoding_context.rbs 2026-03-06 03:33:49.817065869 +0000
+++ /tmp/d20260306-757-q5npf0/http-2-1.1.3/sig/header/encoding_context.rbs 2026-03-06 03:33:49.822065859 +0000
@@ -49 +49,3 @@
- def size_check: (Integer cmdsize) -> bool
+ def resize_table: (Integer cmdsize) -> void
+
+ def size_check?: (Integer cmdsize) -> bool |
Contributor
gem compare --diff httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md
--- /tmp/20260306-2794-jwjeb9 2026-03-06 03:33:55.165388113 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/doc/release_notes/1_7_3.md 2026-03-06 03:33:55.139388124 +0000
@@ -0,0 +1,29 @@
+# 1.7.3
+
+## Improvements
+
+### cookies plugin: Jar as CookieStore
+
+While previously an implementation detail, the cookie jar from a `:cookie` plugin-enabled session can now be manipulated by the end user:
+
+```ruby
+cookies_sess = HTTPX.plugin(:cookies)
+
+jar = cookies.make_jar
+
+sess = cookies_ses.with(cookies: jar)
+
+# perform requests using sess, get/set/delete cookies in jar
+```
+
+The jar API now closely follows the [Web Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore), by providing the same set of functions.
+
+Some API backwards compatibility is maintained, however since this was an internal implementation detail, this effort isn't meant to be thorough.
+
+## Bugfixes
+
+* `http-2`: clear buffered data chunks when receiving a `GOAWAY` stream frame; without this, the client kept sending the corresponding `DATA` frames, despite the peer server making it known that it wouldn't process it. While this is valid HTTP/2, this could increase the connection window until a point where it'd go over the max frame size. this issue was observed during large file uploads where the first request could fail and make the client renegotiate.
+* `webmock` adapter: fixed response body length accounting which was making `response.body.empty?` return true for responses with payload.
+* `:rate_limiter` plugin relies on an internal refactoring to be able to wait for the time suggested by the peer server instead of the potentially relying on custom user logic via own `:retry_after`.
+* `:fiber_concurrency`: fix wrong names for native/system resolver overrides.
+* connection: fix for race condition when closing the connection, where the state only transitions to `closed` after checking the connection back in to the pool, potentially corrupting it if another session meanwhile has picked it up and manipulated it.
\ No newline at end of file
* Changed:
README.md
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/README.md 2026-03-06 03:33:55.085388147 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/README.md 2026-03-06 03:33:55.125388131 +0000
@@ -49 +49,3 @@
-http.patch("http://example.com/file", body: File.open("path/to/file")) # request body is streamed
+File.open("path/to/file") do |file|
+ http.patch("http://example.com/file", body: file) # request body is streamed
+end
lib/httpx/adapters/webmock.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/adapters/webmock.rb 2026-03-06 03:33:55.100388141 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/adapters/webmock.rb 2026-03-06 03:33:55.139388124 +0000
@@ -84,0 +85 @@
+ @body.mock!
@@ -93,4 +94,2 @@
- def decode_chunk(chunk)
- return chunk if @response.mocked?
-
- super
+ def mock!
+ @inflaters = nil
lib/httpx/connection.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/connection.rb 2026-03-06 03:33:55.101388141 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/connection.rb 2026-03-06 03:33:55.140388124 +0000
@@ -229,0 +230,3 @@
+ rescue IOError => e
+ @write_buffer.clear
+ on_io_error(e)
@@ -377,0 +381,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
@@ -496 +504 @@
- # buffer has been drainned, mark and exit the write loop.
+ # buffer has been drained, mark and exit the write loop.
@@ -588,0 +597,2 @@
+
+ parser.pending.clear
@@ -724,2 +733,0 @@
-
- disconnect
@@ -743 +750,0 @@
- disconnect if @pending.empty?
@@ -744,0 +752,2 @@
+ # TODO: should this raise an error instead?
+ return unless @pending.empty?
@@ -760,0 +770,5 @@
+ # post state change
+ case nextstate
+ when :closed, :inactive
+ disconnect
+ end
lib/httpx/connection/http1.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/connection/http1.rb 2026-03-06 03:33:55.101388141 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/connection/http1.rb 2026-03-06 03:33:55.140388124 +0000
@@ -283 +282,0 @@
- return if @requests.empty?
lib/httpx/connection/http2.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/connection/http2.rb 2026-03-06 03:33:55.101388141 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/connection/http2.rb 2026-03-06 03:33:55.141388124 +0000
@@ -6,2 +5,0 @@
-HTTP2::Connection.__send__(:public, :send_buffer) if HTTP2::VERSION < "1.1.1"
-
@@ -218,3 +216 @@
- stream.on(:half_close) do
- log(level: 2) { "#{stream.id}: waiting for response..." }
- end
+ stream.on(:half_close) { on_stream_half_close(stream, request) }
@@ -305 +301 @@
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
@@ -333,0 +330,10 @@
+ def on_stream_half_close(stream, _request)
+ unless stream.send_buffer.empty?
+ stream.send_buffer.clear
+ stream.data("", end_stream: true)
+ end
+
+ # TODO: omit log line if response already here
+ log(level: 2) { "#{stream.id}: waiting for response..." }
+ end
+
@@ -407,12 +413 @@
- log(level: 2, color: :blue) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :blue) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
@@ -423,12 +418,28 @@
- log(level: 2, color: :magenta) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :magenta) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
+ end
+
+ def frame_with_extra_info(frame)
+ case frame[:type]
+ when :data
+ frame.merge(payload: frame[:payload].bytesize)
+ when :headers, :ping
+ frame.merge(payload: log_redact_headers(frame[:payload]))
+ when :window_update
+ connection_or_stream = if (id = frame[:stream]).zero?
+ @connection
+ else
+ @streams.each_value.find { |s| s.id == id }
+ end
+ if connection_or_stream
+ frame.merge(
+ local_window: connection_or_stream.local_window,
+ remote_window: connection_or_stream.remote_window,
+ buffered_amount: connection_or_stream.buffered_amount,
+ stream_state: connection_or_stream.state,
+ )
+ else
+ frame
+ end
+ else
+ frame
+ end.merge(connection_state: @connection.state)
lib/httpx/plugins/auth/digest.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:33:55.103388140 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:33:55.143388123 +0000
@@ -11 +11,2 @@
- Error = Class.new(Error)
+ class Error < Error
+ end
lib/httpx/plugins/cookies.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/cookies.rb 2026-03-06 03:33:55.105388139 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/cookies.rb 2026-03-06 03:33:55.144388122 +0000
@@ -10,2 +9,0 @@
- # It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
- #
@@ -48,0 +47,6 @@
+ # factory method to return a Jar to the user, which can then manipulate
+ # externally to the session.
+ def make_jar(*args)
+ Jar.new(*args)
+ end
+
@@ -99 +103 @@
- jar.add(Cookie.new(name, value))
+ jar.set(name, value)
lib/httpx/plugins/cookies/cookie.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:33:55.105388139 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:33:55.145388122 +0000
@@ -16,0 +17 @@
+ # assigns a new +path+ to this cookie.
@@ -18,0 +20 @@
+ @for_domain = false
@@ -22 +24 @@
- # See #domain.
+ # assigns a new +domain+ to this cookie.
@@ -39,0 +42,7 @@
+ # checks whether +other+ is the same cookie, i.e. name, value, domain and path are
+ # the same.
+ def ==(other)
+ @name == other.name && @value == other.value &&
+ @path == other.path && @domain == other.domain
+ end
+
@@ -49,0 +59,11 @@
+ def match?(name_or_options)
+ case name_or_options
+ when String
+ @name == name_or_options
+ when Hash, Array
+ name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
+ else
+ false
+ end
+ end
+
@@ -52 +72,7 @@
- return cookie if cookie.is_a?(self)
+ case cookie
+ when self
+ cookie
+ when Array, Hash
+ options = Hash[cookie] #: cookie_attributes
+ super(options[:name], options[:value], options)
+ else
@@ -54 +80,2 @@
- super
+ super
+ end
@@ -87 +114 @@
- def initialize(arg, *attrs)
+ def initialize(arg, value, attrs = nil)
@@ -90,7 +117,3 @@
- if attrs.empty?
- attr_hash = Hash.try_convert(arg)
- else
- @name = arg
- @value, attr_hash = attrs
- attr_hash = Hash.try_convert(attr_hash)
- end
+ @name = arg
+ @value = value
+ attr_hash = Hash.try_convert(attrs)
lib/httpx/plugins/cookies/jar.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:33:55.105388139 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:33:55.145388122 +0000
@@ -7 +7,6 @@
- # It holds a bunch of cookies.
+ # It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
+ # initialization from parsing `Set-Cookie` HTTP header values.
+ #
+ # It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
+ # by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
+ #
@@ -14,0 +20 @@
+ @mtx = orig.instance_variable_get(:@mtx).dup
@@ -17,0 +24,2 @@
+ # initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
+ # can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
@@ -18,0 +27 @@
+ @mtx = Thread::Mutex.new
@@ -34,0 +44 @@
+ # parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
@@ -37 +47 @@
- add(Cookie.new(name, value, attrs))
+ set(Cookie.new(name, value, attrs))
@@ -40,0 +51,42 @@
+ # returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get(name_or_options)
+ each.find { |ck| ck.match?(name_or_options) }
+ end
+
+ # returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get_all(name_or_options)
+ each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
+ end
+
+ # when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
+ # it creates a cookie with it and the value-or-attributes passed to +value_or_options+.
+
+ # optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
+ def set(name, value_or_options = nil)
+ cookie = case name
+ when Cookie
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ name
+ when Array, Hash
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ Cookie.new(name)
+ else
+ raise ArgumentError, "the second argument is required" unless value_or_options
+
+ Cookie.new(name, value_or_options)
+ end
+
+ synchronize do
+ # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
+ # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
+ @cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }
+
+ @cookies << cookie
+ end
+ end
+
+ # @deprecated
@@ -41,0 +94 @@
+ warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
@@ -43 +95,0 @@
-
@@ -44,0 +97,2 @@
+ set(c)
+ end
@@ -46,5 +100,13 @@
- # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
- # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
- @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
-
- @cookies << c
+ # deletes all cookies in the store which match either the name (when String) or all attributes (when a Hash
+ # or array of tuples) passed to +name_or_options+.
+ #
+ # alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
+ def delete(name_or_options)
+ synchronize do
+ case name_or_options
+ when Cookie
+ @cookies.delete(name_or_options)
+ else
+ @cookies.delete_if { |ck| ck.match?(name_or_options) }
+ end
+ end
@@ -52,0 +115 @@
+ # returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
@@ -56,0 +120,2 @@
+ # enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
+ # only yield cookies which match its domain and path.
@@ -60 +125 @@
- return @cookies.each(&blk) unless uri
+ return synchronize { @cookies.each(&blk) } unless uri
@@ -65,6 +130,8 @@
- @cookies.delete_if do |cookie|
- if cookie.expired?(now)
- true
- else
- yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
- false
+ synchronize do
+ @cookies.delete_if do |cookie|
+ if cookie.expired?(now)
+ true
+ else
+ yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
+ false
+ end
@@ -76 +143 @@
- cookies_dup = dup
+ jar_dup = dup
@@ -88 +155 @@
- cookies_dup.add(cookie)
+ jar_dup.set(cookie)
@@ -91 +158,9 @@
- cookies_dup
+ jar_dup
+ end
+
+ private
+
+ def synchronize(&block)
+ return yield if @mtx.owned?
+
+ @mtx.synchronize(&block)
lib/httpx/plugins/expect.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/expect.rb 2026-03-06 03:33:55.105388139 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/expect.rb 2026-03-06 03:33:55.145388122 +0000
@@ -11,0 +12,20 @@
+ NOEXPECT_STORE_MUTEX = Thread::Mutex.new
+
+ class Store
+ def initialize
+ @store = []
+ @mutex = Thread::Mutex.new
+ end
+
+ def include?(host)
+ @mutex.synchronize { @store.include?(host) }
+ end
+
+ def add(host)
+ @mutex.synchronize { @store << host }
+ end
+
+ def delete(host)
+ @mutex.synchronize { @store.delete(host) }
+ end
+ end
@@ -15 +35,5 @@
- @no_expect_store ||= []
+ return Ractor.store_if_absent(:httpx_no_expect_store) { Store.new } if Utils.in_ractor?
+
+ @no_expect_store ||= NOEXPECT_STORE_MUTEX.synchronize do
+ @no_expect_store || Store.new
+ end
@@ -92 +116 @@
- Expect.no_expect_store << request.origin
+ Expect.no_expect_store.add(request.origin)
lib/httpx/plugins/fiber_concurrency.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:33:55.106388139 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:33:55.145388122 +0000
@@ -163,3 +163 @@
- module NativeResolverMethods
- private
-
+ module ResolverNativeMethods
@@ -175 +173 @@
- module SystemResolverMethods
+ module ResolverSystemMethods
lib/httpx/plugins/follow_redirects.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:33:55.106388139 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:33:55.145388122 +0000
@@ -4 +4,3 @@
- InsecureRedirectError = Class.new(Error)
+ class InsecureRedirectError < Error
+ end
+
lib/httpx/plugins/rate_limiter.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:33:55.108388138 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:33:55.147388121 +0000
@@ -19,19 +19 @@
- klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
- end
-
- # Servers send the "Retry-After" header field to indicate how long the
- # user agent ought to wait before making a follow-up request. When
- # sent with a 503 (Service Unavailable) response, Retry-After indicates
- # how long the service is expected to be unavailable to the client.
- # When sent with any 3xx (Redirection) response, Retry-After indicates
- # the minimum time that the user agent is asked to wait before issuing
- # the redirected request.
- #
- def retry_after_rate_limit(_, response)
- return unless response.is_a?(Response)
-
- retry_after = response.headers["retry-after"]
-
- return unless retry_after
-
- Utils.parse_retry_after(retry_after)
+ klass.plugin(:retries)
@@ -53,0 +36,18 @@
+ end
+
+ # Servers send the "Retry-After" header field to indicate how long the
+ # user agent ought to wait before making a follow-up request. When
+ # sent with a 503 (Service Unavailable) response, Retry-After indicates
+ # how long the service is expected to be unavailable to the client.
+ # When sent with any 3xx (Redirection) response, Retry-After indicates
+ # the minimum time that the user agent is asked to wait before issuing
+ # the redirected request.
+ #
+ def when_to_retry(_, response, options)
+ return super unless response.is_a?(Response)
+
+ retry_after = response.headers["retry-after"]
+
+ return super unless retry_after
+
+ Utils.parse_retry_after(retry_after)
lib/httpx/plugins/retries.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/retries.rb 2026-03-06 03:33:55.108388138 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/retries.rb 2026-03-06 03:33:55.148388121 +0000
@@ -151,4 +151 @@
- retry_after = options.retry_after
- retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
-
- if retry_after
+ if (retry_after = when_to_retry(request, response, options))
@@ -203,0 +201,6 @@
+ def when_to_retry(request, response, options)
+ retry_after = options.retry_after
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
+ retry_after
+ end
+
@@ -239,0 +243 @@
+ @partial_response = nil
@@ -243 +247 @@
- if @partial_response
+ if (partial_response = @partial_response)
@@ -245 +249 @@
- response.from_partial_response(@partial_response)
+ response.from_partial_response(partial_response)
@@ -247 +251 @@
- @partial_response.close
+ partial_response.close
lib/httpx/plugins/ssrf_filter.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:33:55.108388138 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:33:55.148388121 +0000
@@ -108,0 +109 @@
+ request.response = response
lib/httpx/plugins/stream_bidi.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:33:55.109388137 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:33:55.149388120 +0000
@@ -191,0 +192,4 @@
+ def force_close(*)
+ terminate
+ end
+
@@ -203,0 +208,2 @@
+
+ alias_method :on_io_error, :on_error
lib/httpx/request.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/request.rb 2026-03-06 03:33:55.110388137 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/request.rb 2026-03-06 03:33:55.149388120 +0000
@@ -94 +94 @@
- @response = @peer_address = @informational_status = nil
+ @response = @drainer = @peer_address = @informational_status = nil
lib/httpx/resolver/resolver.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/resolver/resolver.rb 2026-03-06 03:33:55.111388137 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/resolver/resolver.rb 2026-03-06 03:33:55.151388119 +0000
@@ -121,0 +122,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
lib/httpx/selector.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/selector.rb 2026-03-06 03:33:55.112388136 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/selector.rb 2026-03-06 03:33:55.152388119 +0000
@@ -207,2 +207 @@
- sel.on_error(e)
- sel.force_close(true)
+ sel.on_io_error(e)
@@ -252,2 +251,3 @@
- io.on_error(e)
- io.force_close(true)
+ io.on_io_error(e)
+
+ return
lib/httpx/session.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/session.rb 2026-03-06 03:33:55.112388136 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/session.rb 2026-03-06 03:33:55.152388119 +0000
@@ -142,0 +143 @@
+ # do not check-in connections only created for Happy Eyeballs
@@ -145 +146 @@
- return if @closing && connection.state == :closed
+ return if @closing && connection.state == :closed && !connection.used?
@@ -180 +180,0 @@
- log(level: 2) { "finding connection for #{request_uri}..." }
@@ -183 +183 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
@@ -189 +189 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
@@ -243 +243 @@
- log(level: 2) { "response fetched" }
+ log(level: 2) { "response##{response.object_id} fetched" }
@@ -251,0 +252 @@
+ log(level: 2) { "finding connection for request##{request.object_id}..." }
lib/httpx/version.rb
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/lib/httpx/version.rb 2026-03-06 03:33:55.114388135 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/lib/httpx/version.rb 2026-03-06 03:33:55.154388118 +0000
@@ -4 +4 @@
- VERSION = "1.7.2"
+ VERSION = "1.7.3"
sig/chainable.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/chainable.rbs 2026-03-06 03:33:55.115388135 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/chainable.rbs 2026-03-06 03:33:55.154388118 +0000
@@ -32 +32 @@
- | (:rate_limiter, ?options) -> Session
+ | (:rate_limiter, ?options) -> Plugins::sessionRateLimiter
sig/connection.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/connection.rbs 2026-03-06 03:33:55.115388135 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/connection.rbs 2026-03-06 03:33:55.154388118 +0000
@@ -39,0 +40 @@
+ @max_concurrent_requests: Integer?
@@ -120,0 +122,2 @@
+
+ def on_io_error: (IOError error) -> void
sig/connection/http2.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/connection/http2.rbs 2026-03-06 03:33:55.115388135 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/connection/http2.rbs 2026-03-06 03:33:55.155388118 +0000
@@ -81,0 +82,2 @@
+ def on_stream_half_close: (::HTTP2::Stream stream, Request request) -> void
+
@@ -90 +92,3 @@
- def on_frame_sent: (::HTTP2::frame) -> void
+ def on_frame_sent: (::HTTP2::frame frame) -> void
+
+ def frame_with_extra_info: (::HTTP2::frame frame) -> Hash[Symbol, untyped]
@@ -92 +96 @@
- def on_frame_received: (::HTTP2::frame) -> void
+ def on_frame_received: (::HTTP2::frame frame) -> void
@@ -94 +98 @@
- def on_altsvc: (String origin, ::HTTP2::frame) -> void
+ def on_altsvc: (String origin, ::HTTP2::frame frame) -> void
@@ -96 +100 @@
- def on_promise: (::HTTP2::Stream) -> void
+ def on_promise: (::HTTP2::Stream stream) -> void
sig/plugins/cookies.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/cookies.rbs 2026-03-06 03:33:55.118388134 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/cookies.rbs 2026-03-06 03:33:55.158388116 +0000
@@ -17,0 +18,2 @@
+
+ def make_jar: (*untyped) -> Jar
sig/plugins/cookies/cookie.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/cookies/cookie.rbs 2026-03-06 03:33:55.118388134 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/cookies/cookie.rbs 2026-03-06 03:33:55.158388116 +0000
@@ -33,0 +34,2 @@
+ def match?: (String | cookie_attributes name_or_options) -> bool
+
@@ -44,2 +46 @@
- def initialize: (cookie_attributes) -> untyped
- | (_ToS, _ToS, ?cookie_attributes) -> untyped
+ def initialize: (_ToS name, _ToS value, ?cookie_attributes) -> void
sig/plugins/cookies/jar.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/cookies/jar.rbs 2026-03-06 03:33:55.118388134 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/cookies/jar.rbs 2026-03-06 03:33:55.158388116 +0000
@@ -8,0 +9 @@
+ @mtx: Thread::Mutex
@@ -11,0 +13,6 @@
+ def get: (String | cookie_attributes name_or_options) -> Cookie?
+
+ def get_all: (String | cookie_attributes name_or_options) -> Array[Cookie]
+
+ def set: (_ToS | Cookie name, ?(cookie_attributes | _ToS) value_or_options) -> void
+
@@ -13,0 +21,2 @@
+ def delete: (String | Cookie | cookie_attributes name_or_options) -> void
+
@@ -23,0 +33,2 @@
+
+ def synchronize: [T] { () -> T } -> T
sig/plugins/expect.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/expect.rbs 2026-03-06 03:33:55.118388134 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/expect.rbs 2026-03-06 03:33:55.158388116 +0000
@@ -4,0 +5,17 @@
+ NOEXPECT_STORE_MUTEX: Thread::Mutex
+
+ self.@no_expect_store: Store
+ def self.no_expect_store: () -> Store
+
+ def self.extra_options: (Options) -> (Options & _ExpectOptions)
+
+ class Store
+ @store: Array[String]
+ @mutex: Thread::Mutex
+
+ def include?: (String host) -> bool
+
+ def add: (String host) -> void
+
+ def delete: (String host) -> void
+ end
@@ -11,2 +27,0 @@
-
- def self.extra_options: (Options) -> (Options & _ExpectOptions)
sig/plugins/proxy/socks4.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/proxy/socks4.rbs 2026-03-06 03:33:55.120388133 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/proxy/socks4.rbs 2026-03-06 03:33:55.160388116 +0000
@@ -7,0 +8,4 @@
+ VERSION: Integer
+ CONNECT: Integer
+ GRANTED: Integer
+ PROTOCOLS: Array[String]
sig/plugins/rate_limiter.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/rate_limiter.rbs 2026-03-06 03:33:55.120388133 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/rate_limiter.rbs 2026-03-06 03:33:55.160388116 +0000
@@ -8,2 +7,0 @@
- def self.retry_after_rate_limit: (untyped, response) -> Numeric?
-
@@ -13,0 +12,2 @@
+
+ type sessionRateLimiter = Session & RateLimiter::InstanceMethods
sig/plugins/response_cache.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/response_cache.rbs 2026-03-06 03:33:55.120388133 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/response_cache.rbs 2026-03-06 03:33:55.160388116 +0000
@@ -54 +54 @@
- @cache: bool
+ @cached: bool
@@ -69 +69 @@
- def cache_control: () -> Array[String]?
+ %a{pure} def cache_control: () -> Array[String]?
@@ -71 +71 @@
- def vary: () -> Array[String]?
+ %a{pure} def vary: () -> Array[String]?
sig/plugins/retries.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/plugins/retries.rbs 2026-03-06 03:33:55.121388132 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/plugins/retries.rbs 2026-03-06 03:33:55.161388115 +0000
@@ -11 +11 @@
- def self?.retry_after_polynomial_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_polynomial_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -13 +13 @@
- def self?.retry_after_exponential_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_exponential_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -16 +16 @@
- def call: (response response) -> bool?
+ def call: (retriesResponse response) -> bool?
@@ -20 +20 @@
- def retry_after: () -> (^(retriesRequest request, response response) -> Numeric | Numeric)?
+ def retry_after: () -> (^(retriesRequest request, retriesResponse response) -> Numeric | Numeric)?
@@ -38 +38 @@
- def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> (retriesResponse | ErrorResponse)?
+ def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> retriesResponse?
@@ -40 +40 @@
- def retryable_request?: (retriesRequest request, response response, retriesOptions options) -> boolish
+ def retryable_request?: (retriesRequest request, retriesResponse response, retriesOptions options) -> boolish
@@ -42 +42 @@
- def retryable_response?: (response response, retriesOptions options) -> boolish
+ def retryable_response?: (retriesResponse response, retriesOptions options) -> boolish
@@ -46 +46 @@
- def try_partial_retry: (retriesRequest request, (retriesResponse | ErrorResponse) response) -> void
+ def try_partial_retry: (retriesRequest request, retriesResponse response) -> void
@@ -48 +48,3 @@
- def prepare_to_retry: (Request & RequestMethods request, response response) -> void
+ def prepare_to_retry: (Request & RequestMethods request, retriesResponse response) -> void
+
+ def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> void
@@ -56 +58 @@
- attr_writer partial_response: Response?
+ attr_writer partial_response: retriesResponse?
@@ -58 +60 @@
- def response=: (retriesResponse | ErrorResponse response) -> void
+ def response=: (retriesResponse response) -> void
@@ -62 +64 @@
- def from_partial_response: (Response response) -> void
+ def from_partial_response: (retriesHTTPResponse response) -> void
@@ -69 +71,3 @@
- type retriesResponse = Response & ResponseMethods
+ type retriesHTTPResponse = Response & ResponseMethods
+
+ type retriesResponse = retriesHTTPResponse | ErrorResponse
sig/request.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/request.rbs 2026-03-06 03:33:55.122388132 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/request.rbs 2026-03-06 03:33:55.162388115 +0000
@@ -8,0 +9 @@
+ ALLOWED_URI_SCHEMES: Array[String]
sig/resolver/native.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/resolver/native.rbs 2026-03-06 03:33:55.123388131 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/resolver/native.rbs 2026-03-06 03:33:55.163388114 +0000
@@ -39,0 +40,2 @@
+ def on_io_error: (IOError error) -> void
+
sig/resolver/resolver.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/resolver/resolver.rbs 2026-03-06 03:33:55.123388131 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/resolver/resolver.rbs 2026-03-06 03:33:55.163388114 +0000
@@ -30,2 +29,0 @@
- def force_close: (*untyped args) -> void
-
@@ -62,0 +61,2 @@
+
+ def disconnect: () -> void
sig/selector.rbs
--- /tmp/d20260306-2794-kkjkcm/httpx-1.7.2/sig/selector.rbs 2026-03-06 03:33:55.123388131 +0000
+++ /tmp/d20260306-2794-kkjkcm/httpx-1.7.3/sig/selector.rbs 2026-03-06 03:33:55.163388114 +0000
@@ -15,0 +16,4 @@
+
+ def on_io_error: (IOError error) -> void
+
+ def force_close: (?bool delete_pending) -> void |
Contributor
gem compare --diff httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md
--- /tmp/20260306-2578-isibgk 2026-03-06 03:33:58.356147043 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/doc/release_notes/1_7_3.md 2026-03-06 03:33:58.291147751 +0000
@@ -0,0 +1,29 @@
+# 1.7.3
+
+## Improvements
+
+### cookies plugin: Jar as CookieStore
+
+While previously an implementation detail, the cookie jar from a `:cookie` plugin-enabled session can now be manipulated by the end user:
+
+```ruby
+cookies_sess = HTTPX.plugin(:cookies)
+
+jar = cookies.make_jar
+
+sess = cookies_ses.with(cookies: jar)
+
+# perform requests using sess, get/set/delete cookies in jar
+```
+
+The jar API now closely follows the [Web Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore), by providing the same set of functions.
+
+Some API backwards compatibility is maintained, however since this was an internal implementation detail, this effort isn't meant to be thorough.
+
+## Bugfixes
+
+* `http-2`: clear buffered data chunks when receiving a `GOAWAY` stream frame; without this, the client kept sending the corresponding `DATA` frames, despite the peer server making it known that it wouldn't process it. While this is valid HTTP/2, this could increase the connection window until a point where it'd go over the max frame size. this issue was observed during large file uploads where the first request could fail and make the client renegotiate.
+* `webmock` adapter: fixed response body length accounting which was making `response.body.empty?` return true for responses with payload.
+* `:rate_limiter` plugin relies on an internal refactoring to be able to wait for the time suggested by the peer server instead of the potentially relying on custom user logic via own `:retry_after`.
+* `:fiber_concurrency`: fix wrong names for native/system resolver overrides.
+* connection: fix for race condition when closing the connection, where the state only transitions to `closed` after checking the connection back in to the pool, potentially corrupting it if another session meanwhile has picked it up and manipulated it.
\ No newline at end of file
* Changed:
README.md
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/README.md 2026-03-06 03:33:58.118149637 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/README.md 2026-03-06 03:33:58.247148231 +0000
@@ -49 +49,3 @@
-http.patch("http://example.com/file", body: File.open("path/to/file")) # request body is streamed
+File.open("path/to/file") do |file|
+ http.patch("http://example.com/file", body: file) # request body is streamed
+end
lib/httpx/adapters/webmock.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/adapters/webmock.rb 2026-03-06 03:33:58.154149245 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/adapters/webmock.rb 2026-03-06 03:33:58.294147718 +0000
@@ -84,0 +85 @@
+ @body.mock!
@@ -93,4 +94,2 @@
- def decode_chunk(chunk)
- return chunk if @response.mocked?
-
- super
+ def mock!
+ @inflaters = nil
lib/httpx/connection.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/connection.rb 2026-03-06 03:33:58.156149223 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/connection.rb 2026-03-06 03:33:58.295147707 +0000
@@ -229,0 +230,3 @@
+ rescue IOError => e
+ @write_buffer.clear
+ on_io_error(e)
@@ -377,0 +381,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
@@ -496 +504 @@
- # buffer has been drainned, mark and exit the write loop.
+ # buffer has been drained, mark and exit the write loop.
@@ -588,0 +597,2 @@
+
+ parser.pending.clear
@@ -724,2 +733,0 @@
-
- disconnect
@@ -743 +750,0 @@
- disconnect if @pending.empty?
@@ -744,0 +752,2 @@
+ # TODO: should this raise an error instead?
+ return unless @pending.empty?
@@ -760,0 +770,5 @@
+ # post state change
+ case nextstate
+ when :closed, :inactive
+ disconnect
+ end
lib/httpx/connection/http1.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/connection/http1.rb 2026-03-06 03:33:58.157149212 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/connection/http1.rb 2026-03-06 03:33:58.296147697 +0000
@@ -283 +282,0 @@
- return if @requests.empty?
lib/httpx/connection/http2.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/connection/http2.rb 2026-03-06 03:33:58.157149212 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/connection/http2.rb 2026-03-06 03:33:58.297147686 +0000
@@ -6,2 +5,0 @@
-HTTP2::Connection.__send__(:public, :send_buffer) if HTTP2::VERSION < "1.1.1"
-
@@ -218,3 +216 @@
- stream.on(:half_close) do
- log(level: 2) { "#{stream.id}: waiting for response..." }
- end
+ stream.on(:half_close) { on_stream_half_close(stream, request) }
@@ -305 +301 @@
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
@@ -333,0 +330,10 @@
+ def on_stream_half_close(stream, _request)
+ unless stream.send_buffer.empty?
+ stream.send_buffer.clear
+ stream.data("", end_stream: true)
+ end
+
+ # TODO: omit log line if response already here
+ log(level: 2) { "#{stream.id}: waiting for response..." }
+ end
+
@@ -407,12 +413 @@
- log(level: 2, color: :blue) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :blue) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
@@ -423,12 +418,28 @@
- log(level: 2, color: :magenta) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :magenta) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
+ end
+
+ def frame_with_extra_info(frame)
+ case frame[:type]
+ when :data
+ frame.merge(payload: frame[:payload].bytesize)
+ when :headers, :ping
+ frame.merge(payload: log_redact_headers(frame[:payload]))
+ when :window_update
+ connection_or_stream = if (id = frame[:stream]).zero?
+ @connection
+ else
+ @streams.each_value.find { |s| s.id == id }
+ end
+ if connection_or_stream
+ frame.merge(
+ local_window: connection_or_stream.local_window,
+ remote_window: connection_or_stream.remote_window,
+ buffered_amount: connection_or_stream.buffered_amount,
+ stream_state: connection_or_stream.state,
+ )
+ else
+ frame
+ end
+ else
+ frame
+ end.merge(connection_state: @connection.state)
lib/httpx/plugins/auth/digest.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:33:58.162149158 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:33:58.301147642 +0000
@@ -11 +11,2 @@
- Error = Class.new(Error)
+ class Error < Error
+ end
lib/httpx/plugins/cookies.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/cookies.rb 2026-03-06 03:33:58.165149125 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/cookies.rb 2026-03-06 03:33:58.303147620 +0000
@@ -10,2 +9,0 @@
- # It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
- #
@@ -48,0 +47,6 @@
+ # factory method to return a Jar to the user, which can then manipulate
+ # externally to the session.
+ def make_jar(*args)
+ Jar.new(*args)
+ end
+
@@ -99 +103 @@
- jar.add(Cookie.new(name, value))
+ jar.set(name, value)
lib/httpx/plugins/cookies/cookie.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:33:58.165149125 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:33:58.303147620 +0000
@@ -16,0 +17 @@
+ # assigns a new +path+ to this cookie.
@@ -18,0 +20 @@
+ @for_domain = false
@@ -22 +24 @@
- # See #domain.
+ # assigns a new +domain+ to this cookie.
@@ -39,0 +42,7 @@
+ # checks whether +other+ is the same cookie, i.e. name, value, domain and path are
+ # the same.
+ def ==(other)
+ @name == other.name && @value == other.value &&
+ @path == other.path && @domain == other.domain
+ end
+
@@ -49,0 +59,11 @@
+ def match?(name_or_options)
+ case name_or_options
+ when String
+ @name == name_or_options
+ when Hash, Array
+ name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
+ else
+ false
+ end
+ end
+
@@ -52 +72,7 @@
- return cookie if cookie.is_a?(self)
+ case cookie
+ when self
+ cookie
+ when Array, Hash
+ options = Hash[cookie] #: cookie_attributes
+ super(options[:name], options[:value], options)
+ else
@@ -54 +80,2 @@
- super
+ super
+ end
@@ -87 +114 @@
- def initialize(arg, *attrs)
+ def initialize(arg, value, attrs = nil)
@@ -90,7 +117,3 @@
- if attrs.empty?
- attr_hash = Hash.try_convert(arg)
- else
- @name = arg
- @value, attr_hash = attrs
- attr_hash = Hash.try_convert(attr_hash)
- end
+ @name = arg
+ @value = value
+ attr_hash = Hash.try_convert(attrs)
lib/httpx/plugins/cookies/jar.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:33:58.165149125 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:33:58.303147620 +0000
@@ -7 +7,6 @@
- # It holds a bunch of cookies.
+ # It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
+ # initialization from parsing `Set-Cookie` HTTP header values.
+ #
+ # It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
+ # by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
+ #
@@ -14,0 +20 @@
+ @mtx = orig.instance_variable_get(:@mtx).dup
@@ -17,0 +24,2 @@
+ # initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
+ # can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
@@ -18,0 +27 @@
+ @mtx = Thread::Mutex.new
@@ -34,0 +44 @@
+ # parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
@@ -37 +47 @@
- add(Cookie.new(name, value, attrs))
+ set(Cookie.new(name, value, attrs))
@@ -40,0 +51,42 @@
+ # returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get(name_or_options)
+ each.find { |ck| ck.match?(name_or_options) }
+ end
+
+ # returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get_all(name_or_options)
+ each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
+ end
+
+ # when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
+ # it creates a cookie with it and the value-or-attributes passed to +value_or_options+.
+
+ # optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
+ def set(name, value_or_options = nil)
+ cookie = case name
+ when Cookie
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ name
+ when Array, Hash
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ Cookie.new(name)
+ else
+ raise ArgumentError, "the second argument is required" unless value_or_options
+
+ Cookie.new(name, value_or_options)
+ end
+
+ synchronize do
+ # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
+ # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
+ @cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }
+
+ @cookies << cookie
+ end
+ end
+
+ # @deprecated
@@ -41,0 +94 @@
+ warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
@@ -43 +95,0 @@
-
@@ -44,0 +97,2 @@
+ set(c)
+ end
@@ -46,5 +100,13 @@
- # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
- # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
- @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
-
- @cookies << c
+ # deletes all cookies in the store which match either the name (when String) or all attributes (when a Hash
+ # or array of tuples) passed to +name_or_options+.
+ #
+ # alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
+ def delete(name_or_options)
+ synchronize do
+ case name_or_options
+ when Cookie
+ @cookies.delete(name_or_options)
+ else
+ @cookies.delete_if { |ck| ck.match?(name_or_options) }
+ end
+ end
@@ -52,0 +115 @@
+ # returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
@@ -56,0 +120,2 @@
+ # enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
+ # only yield cookies which match its domain and path.
@@ -60 +125 @@
- return @cookies.each(&blk) unless uri
+ return synchronize { @cookies.each(&blk) } unless uri
@@ -65,6 +130,8 @@
- @cookies.delete_if do |cookie|
- if cookie.expired?(now)
- true
- else
- yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
- false
+ synchronize do
+ @cookies.delete_if do |cookie|
+ if cookie.expired?(now)
+ true
+ else
+ yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
+ false
+ end
@@ -76 +143 @@
- cookies_dup = dup
+ jar_dup = dup
@@ -88 +155 @@
- cookies_dup.add(cookie)
+ jar_dup.set(cookie)
@@ -91 +158,9 @@
- cookies_dup
+ jar_dup
+ end
+
+ private
+
+ def synchronize(&block)
+ return yield if @mtx.owned?
+
+ @mtx.synchronize(&block)
lib/httpx/plugins/expect.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/expect.rb 2026-03-06 03:33:58.166149114 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/expect.rb 2026-03-06 03:33:58.304147610 +0000
@@ -11,0 +12,20 @@
+ NOEXPECT_STORE_MUTEX = Thread::Mutex.new
+
+ class Store
+ def initialize
+ @store = []
+ @mutex = Thread::Mutex.new
+ end
+
+ def include?(host)
+ @mutex.synchronize { @store.include?(host) }
+ end
+
+ def add(host)
+ @mutex.synchronize { @store << host }
+ end
+
+ def delete(host)
+ @mutex.synchronize { @store.delete(host) }
+ end
+ end
@@ -15 +35,5 @@
- @no_expect_store ||= []
+ return Ractor.store_if_absent(:httpx_no_expect_store) { Store.new } if Utils.in_ractor?
+
+ @no_expect_store ||= NOEXPECT_STORE_MUTEX.synchronize do
+ @no_expect_store || Store.new
+ end
@@ -92 +116 @@
- Expect.no_expect_store << request.origin
+ Expect.no_expect_store.add(request.origin)
lib/httpx/plugins/fiber_concurrency.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:33:58.166149114 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:33:58.304147610 +0000
@@ -163,3 +163 @@
- module NativeResolverMethods
- private
-
+ module ResolverNativeMethods
@@ -175 +173 @@
- module SystemResolverMethods
+ module ResolverSystemMethods
lib/httpx/plugins/follow_redirects.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:33:58.167149103 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:33:58.304147610 +0000
@@ -4 +4,3 @@
- InsecureRedirectError = Class.new(Error)
+ class InsecureRedirectError < Error
+ end
+
lib/httpx/plugins/rate_limiter.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:33:58.173149038 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:33:58.307147577 +0000
@@ -19,19 +19 @@
- klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
- end
-
- # Servers send the "Retry-After" header field to indicate how long the
- # user agent ought to wait before making a follow-up request. When
- # sent with a 503 (Service Unavailable) response, Retry-After indicates
- # how long the service is expected to be unavailable to the client.
- # When sent with any 3xx (Redirection) response, Retry-After indicates
- # the minimum time that the user agent is asked to wait before issuing
- # the redirected request.
- #
- def retry_after_rate_limit(_, response)
- return unless response.is_a?(Response)
-
- retry_after = response.headers["retry-after"]
-
- return unless retry_after
-
- Utils.parse_retry_after(retry_after)
+ klass.plugin(:retries)
@@ -53,0 +36,18 @@
+ end
+
+ # Servers send the "Retry-After" header field to indicate how long the
+ # user agent ought to wait before making a follow-up request. When
+ # sent with a 503 (Service Unavailable) response, Retry-After indicates
+ # how long the service is expected to be unavailable to the client.
+ # When sent with any 3xx (Redirection) response, Retry-After indicates
+ # the minimum time that the user agent is asked to wait before issuing
+ # the redirected request.
+ #
+ def when_to_retry(_, response, options)
+ return super unless response.is_a?(Response)
+
+ retry_after = response.headers["retry-after"]
+
+ return super unless retry_after
+
+ Utils.parse_retry_after(retry_after)
lib/httpx/plugins/retries.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/retries.rb 2026-03-06 03:33:58.175149016 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/retries.rb 2026-03-06 03:33:58.307147577 +0000
@@ -151,4 +151 @@
- retry_after = options.retry_after
- retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
-
- if retry_after
+ if (retry_after = when_to_retry(request, response, options))
@@ -203,0 +201,6 @@
+ def when_to_retry(request, response, options)
+ retry_after = options.retry_after
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
+ retry_after
+ end
+
@@ -239,0 +243 @@
+ @partial_response = nil
@@ -243 +247 @@
- if @partial_response
+ if (partial_response = @partial_response)
@@ -245 +249 @@
- response.from_partial_response(@partial_response)
+ response.from_partial_response(partial_response)
@@ -247 +251 @@
- @partial_response.close
+ partial_response.close
lib/httpx/plugins/ssrf_filter.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:33:58.176149005 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:33:58.307147577 +0000
@@ -108,0 +109 @@
+ request.response = response
lib/httpx/plugins/stream_bidi.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:33:58.177148994 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:33:58.308147566 +0000
@@ -191,0 +192,4 @@
+ def force_close(*)
+ terminate
+ end
+
@@ -203,0 +208,2 @@
+
+ alias_method :on_io_error, :on_error
lib/httpx/request.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/request.rb 2026-03-06 03:33:58.182148940 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/request.rb 2026-03-06 03:33:58.318147457 +0000
@@ -94 +94 @@
- @response = @peer_address = @informational_status = nil
+ @response = @drainer = @peer_address = @informational_status = nil
lib/httpx/resolver/resolver.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/resolver/resolver.rb 2026-03-06 03:33:58.187148885 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/resolver/resolver.rb 2026-03-06 03:33:58.322147413 +0000
@@ -121,0 +122,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
lib/httpx/selector.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/selector.rb 2026-03-06 03:33:58.189148863 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/selector.rb 2026-03-06 03:33:58.324147391 +0000
@@ -207,2 +207 @@
- sel.on_error(e)
- sel.force_close(true)
+ sel.on_io_error(e)
@@ -252,2 +251,3 @@
- io.on_error(e)
- io.force_close(true)
+ io.on_io_error(e)
+
+ return
lib/httpx/session.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/session.rb 2026-03-06 03:33:58.191148842 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/session.rb 2026-03-06 03:33:58.324147391 +0000
@@ -142,0 +143 @@
+ # do not check-in connections only created for Happy Eyeballs
@@ -145 +146 @@
- return if @closing && connection.state == :closed
+ return if @closing && connection.state == :closed && !connection.used?
@@ -180 +180,0 @@
- log(level: 2) { "finding connection for #{request_uri}..." }
@@ -183 +183 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
@@ -189 +189 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
@@ -243 +243 @@
- log(level: 2) { "response fetched" }
+ log(level: 2) { "response##{response.object_id} fetched" }
@@ -251,0 +252 @@
+ log(level: 2) { "finding connection for request##{request.object_id}..." }
lib/httpx/version.rb
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/lib/httpx/version.rb 2026-03-06 03:33:58.199148754 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/lib/httpx/version.rb 2026-03-06 03:33:58.332147304 +0000
@@ -4 +4 @@
- VERSION = "1.7.2"
+ VERSION = "1.7.3"
sig/chainable.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/chainable.rbs 2026-03-06 03:33:58.199148754 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/chainable.rbs 2026-03-06 03:33:58.333147293 +0000
@@ -32 +32 @@
- | (:rate_limiter, ?options) -> Session
+ | (:rate_limiter, ?options) -> Plugins::sessionRateLimiter
sig/connection.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/connection.rbs 2026-03-06 03:33:58.200148743 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/connection.rbs 2026-03-06 03:33:58.333147293 +0000
@@ -39,0 +40 @@
+ @max_concurrent_requests: Integer?
@@ -120,0 +122,2 @@
+
+ def on_io_error: (IOError error) -> void
sig/connection/http2.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/connection/http2.rbs 2026-03-06 03:33:58.200148743 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/connection/http2.rbs 2026-03-06 03:33:58.336147261 +0000
@@ -81,0 +82,2 @@
+ def on_stream_half_close: (::HTTP2::Stream stream, Request request) -> void
+
@@ -90 +92,3 @@
- def on_frame_sent: (::HTTP2::frame) -> void
+ def on_frame_sent: (::HTTP2::frame frame) -> void
+
+ def frame_with_extra_info: (::HTTP2::frame frame) -> Hash[Symbol, untyped]
@@ -92 +96 @@
- def on_frame_received: (::HTTP2::frame) -> void
+ def on_frame_received: (::HTTP2::frame frame) -> void
@@ -94 +98 @@
- def on_altsvc: (String origin, ::HTTP2::frame) -> void
+ def on_altsvc: (String origin, ::HTTP2::frame frame) -> void
@@ -96 +100 @@
- def on_promise: (::HTTP2::Stream) -> void
+ def on_promise: (::HTTP2::Stream stream) -> void
sig/plugins/cookies.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/cookies.rbs 2026-03-06 03:33:58.217148558 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/cookies.rbs 2026-03-06 03:33:58.342147195 +0000
@@ -17,0 +18,2 @@
+
+ def make_jar: (*untyped) -> Jar
sig/plugins/cookies/cookie.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/cookies/cookie.rbs 2026-03-06 03:33:58.218148547 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/cookies/cookie.rbs 2026-03-06 03:33:58.342147195 +0000
@@ -33,0 +34,2 @@
+ def match?: (String | cookie_attributes name_or_options) -> bool
+
@@ -44,2 +46 @@
- def initialize: (cookie_attributes) -> untyped
- | (_ToS, _ToS, ?cookie_attributes) -> untyped
+ def initialize: (_ToS name, _ToS value, ?cookie_attributes) -> void
sig/plugins/cookies/jar.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/cookies/jar.rbs 2026-03-06 03:33:58.218148547 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/cookies/jar.rbs 2026-03-06 03:33:58.343147184 +0000
@@ -8,0 +9 @@
+ @mtx: Thread::Mutex
@@ -11,0 +13,6 @@
+ def get: (String | cookie_attributes name_or_options) -> Cookie?
+
+ def get_all: (String | cookie_attributes name_or_options) -> Array[Cookie]
+
+ def set: (_ToS | Cookie name, ?(cookie_attributes | _ToS) value_or_options) -> void
+
@@ -13,0 +21,2 @@
+ def delete: (String | Cookie | cookie_attributes name_or_options) -> void
+
@@ -23,0 +33,2 @@
+
+ def synchronize: [T] { () -> T } -> T
sig/plugins/expect.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/expect.rbs 2026-03-06 03:33:58.219148536 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/expect.rbs 2026-03-06 03:33:58.343147184 +0000
@@ -4,0 +5,17 @@
+ NOEXPECT_STORE_MUTEX: Thread::Mutex
+
+ self.@no_expect_store: Store
+ def self.no_expect_store: () -> Store
+
+ def self.extra_options: (Options) -> (Options & _ExpectOptions)
+
+ class Store
+ @store: Array[String]
+ @mutex: Thread::Mutex
+
+ def include?: (String host) -> bool
+
+ def add: (String host) -> void
+
+ def delete: (String host) -> void
+ end
@@ -11,2 +27,0 @@
-
- def self.extra_options: (Options) -> (Options & _ExpectOptions)
sig/plugins/proxy/socks4.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/proxy/socks4.rbs 2026-03-06 03:33:58.223148493 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/proxy/socks4.rbs 2026-03-06 03:33:58.346147152 +0000
@@ -7,0 +8,4 @@
+ VERSION: Integer
+ CONNECT: Integer
+ GRANTED: Integer
+ PROTOCOLS: Array[String]
sig/plugins/rate_limiter.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/rate_limiter.rbs 2026-03-06 03:33:58.224148482 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/rate_limiter.rbs 2026-03-06 03:33:58.348147130 +0000
@@ -8,2 +7,0 @@
- def self.retry_after_rate_limit: (untyped, response) -> Numeric?
-
@@ -13,0 +12,2 @@
+
+ type sessionRateLimiter = Session & RateLimiter::InstanceMethods
sig/plugins/response_cache.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/response_cache.rbs 2026-03-06 03:33:58.225148471 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/response_cache.rbs 2026-03-06 03:33:58.348147130 +0000
@@ -54 +54 @@
- @cache: bool
+ @cached: bool
@@ -69 +69 @@
- def cache_control: () -> Array[String]?
+ %a{pure} def cache_control: () -> Array[String]?
@@ -71 +71 @@
- def vary: () -> Array[String]?
+ %a{pure} def vary: () -> Array[String]?
sig/plugins/retries.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/plugins/retries.rbs 2026-03-06 03:33:58.228148438 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/plugins/retries.rbs 2026-03-06 03:33:58.349147119 +0000
@@ -11 +11 @@
- def self?.retry_after_polynomial_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_polynomial_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -13 +13 @@
- def self?.retry_after_exponential_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_exponential_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -16 +16 @@
- def call: (response response) -> bool?
+ def call: (retriesResponse response) -> bool?
@@ -20 +20 @@
- def retry_after: () -> (^(retriesRequest request, response response) -> Numeric | Numeric)?
+ def retry_after: () -> (^(retriesRequest request, retriesResponse response) -> Numeric | Numeric)?
@@ -38 +38 @@
- def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> (retriesResponse | ErrorResponse)?
+ def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> retriesResponse?
@@ -40 +40 @@
- def retryable_request?: (retriesRequest request, response response, retriesOptions options) -> boolish
+ def retryable_request?: (retriesRequest request, retriesResponse response, retriesOptions options) -> boolish
@@ -42 +42 @@
- def retryable_response?: (response response, retriesOptions options) -> boolish
+ def retryable_response?: (retriesResponse response, retriesOptions options) -> boolish
@@ -46 +46 @@
- def try_partial_retry: (retriesRequest request, (retriesResponse | ErrorResponse) response) -> void
+ def try_partial_retry: (retriesRequest request, retriesResponse response) -> void
@@ -48 +48,3 @@
- def prepare_to_retry: (Request & RequestMethods request, response response) -> void
+ def prepare_to_retry: (Request & RequestMethods request, retriesResponse response) -> void
+
+ def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> void
@@ -56 +58 @@
- attr_writer partial_response: Response?
+ attr_writer partial_response: retriesResponse?
@@ -58 +60 @@
- def response=: (retriesResponse | ErrorResponse response) -> void
+ def response=: (retriesResponse response) -> void
@@ -62 +64 @@
- def from_partial_response: (Response response) -> void
+ def from_partial_response: (retriesHTTPResponse response) -> void
@@ -69 +71,3 @@
- type retriesResponse = Response & ResponseMethods
+ type retriesHTTPResponse = Response & ResponseMethods
+
+ type retriesResponse = retriesHTTPResponse | ErrorResponse
sig/request.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/request.rbs 2026-03-06 03:33:58.234148373 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/request.rbs 2026-03-06 03:33:58.351147097 +0000
@@ -8,0 +9 @@
+ ALLOWED_URI_SCHEMES: Array[String]
sig/resolver/native.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/resolver/native.rbs 2026-03-06 03:33:58.239148318 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/resolver/native.rbs 2026-03-06 03:33:58.352147086 +0000
@@ -39,0 +40,2 @@
+ def on_io_error: (IOError error) -> void
+
sig/resolver/resolver.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/resolver/resolver.rbs 2026-03-06 03:33:58.240148307 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/resolver/resolver.rbs 2026-03-06 03:33:58.352147086 +0000
@@ -30,2 +29,0 @@
- def force_close: (*untyped args) -> void
-
@@ -62,0 +61,2 @@
+
+ def disconnect: () -> void
sig/selector.rbs
--- /tmp/d20260306-2578-25bnvm/httpx-1.7.2/sig/selector.rbs 2026-03-06 03:33:58.241148296 +0000
+++ /tmp/d20260306-2578-25bnvm/httpx-1.7.3/sig/selector.rbs 2026-03-06 03:33:58.353147075 +0000
@@ -15,0 +16,4 @@
+
+ def on_io_error: (IOError error) -> void
+
+ def force_close: (?bool delete_pending) -> void |
Contributor
gem compare --diff httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md
--- /tmp/20260306-2537-ny67nr 2026-03-06 03:34:00.522178695 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/doc/release_notes/1_7_3.md 2026-03-06 03:34:00.483178260 +0000
@@ -0,0 +1,29 @@
+# 1.7.3
+
+## Improvements
+
+### cookies plugin: Jar as CookieStore
+
+While previously an implementation detail, the cookie jar from a `:cookie` plugin-enabled session can now be manipulated by the end user:
+
+```ruby
+cookies_sess = HTTPX.plugin(:cookies)
+
+jar = cookies.make_jar
+
+sess = cookies_ses.with(cookies: jar)
+
+# perform requests using sess, get/set/delete cookies in jar
+```
+
+The jar API now closely follows the [Web Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore), by providing the same set of functions.
+
+Some API backwards compatibility is maintained, however since this was an internal implementation detail, this effort isn't meant to be thorough.
+
+## Bugfixes
+
+* `http-2`: clear buffered data chunks when receiving a `GOAWAY` stream frame; without this, the client kept sending the corresponding `DATA` frames, despite the peer server making it known that it wouldn't process it. While this is valid HTTP/2, this could increase the connection window until a point where it'd go over the max frame size. this issue was observed during large file uploads where the first request could fail and make the client renegotiate.
+* `webmock` adapter: fixed response body length accounting which was making `response.body.empty?` return true for responses with payload.
+* `:rate_limiter` plugin relies on an internal refactoring to be able to wait for the time suggested by the peer server instead of the potentially relying on custom user logic via own `:retry_after`.
+* `:fiber_concurrency`: fix wrong names for native/system resolver overrides.
+* connection: fix for race condition when closing the connection, where the state only transitions to `closed` after checking the connection back in to the pool, potentially corrupting it if another session meanwhile has picked it up and manipulated it.
\ No newline at end of file
* Changed:
README.md
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/README.md 2026-03-06 03:34:00.411177458 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/README.md 2026-03-06 03:34:00.464178048 +0000
@@ -49 +49,3 @@
-http.patch("http://example.com/file", body: File.open("path/to/file")) # request body is streamed
+File.open("path/to/file") do |file|
+ http.patch("http://example.com/file", body: file) # request body is streamed
+end
lib/httpx/adapters/webmock.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/adapters/webmock.rb 2026-03-06 03:34:00.431177681 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/adapters/webmock.rb 2026-03-06 03:34:00.484178271 +0000
@@ -84,0 +85 @@
+ @body.mock!
@@ -93,4 +94,2 @@
- def decode_chunk(chunk)
- return chunk if @response.mocked?
-
- super
+ def mock!
+ @inflaters = nil
lib/httpx/connection.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/connection.rb 2026-03-06 03:34:00.432177692 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/connection.rb 2026-03-06 03:34:00.485178282 +0000
@@ -229,0 +230,3 @@
+ rescue IOError => e
+ @write_buffer.clear
+ on_io_error(e)
@@ -377,0 +381,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
@@ -496 +504 @@
- # buffer has been drainned, mark and exit the write loop.
+ # buffer has been drained, mark and exit the write loop.
@@ -588,0 +597,2 @@
+
+ parser.pending.clear
@@ -724,2 +733,0 @@
-
- disconnect
@@ -743 +750,0 @@
- disconnect if @pending.empty?
@@ -744,0 +752,2 @@
+ # TODO: should this raise an error instead?
+ return unless @pending.empty?
@@ -760,0 +770,5 @@
+ # post state change
+ case nextstate
+ when :closed, :inactive
+ disconnect
+ end
lib/httpx/connection/http1.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/connection/http1.rb 2026-03-06 03:34:00.432177692 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/connection/http1.rb 2026-03-06 03:34:00.485178282 +0000
@@ -283 +282,0 @@
- return if @requests.empty?
lib/httpx/connection/http2.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/connection/http2.rb 2026-03-06 03:34:00.433177703 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/connection/http2.rb 2026-03-06 03:34:00.486178294 +0000
@@ -6,2 +5,0 @@
-HTTP2::Connection.__send__(:public, :send_buffer) if HTTP2::VERSION < "1.1.1"
-
@@ -218,3 +216 @@
- stream.on(:half_close) do
- log(level: 2) { "#{stream.id}: waiting for response..." }
- end
+ stream.on(:half_close) { on_stream_half_close(stream, request) }
@@ -305 +301 @@
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
@@ -333,0 +330,10 @@
+ def on_stream_half_close(stream, _request)
+ unless stream.send_buffer.empty?
+ stream.send_buffer.clear
+ stream.data("", end_stream: true)
+ end
+
+ # TODO: omit log line if response already here
+ log(level: 2) { "#{stream.id}: waiting for response..." }
+ end
+
@@ -407,12 +413 @@
- log(level: 2, color: :blue) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :blue) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
@@ -423,12 +418,28 @@
- log(level: 2, color: :magenta) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :magenta) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
+ end
+
+ def frame_with_extra_info(frame)
+ case frame[:type]
+ when :data
+ frame.merge(payload: frame[:payload].bytesize)
+ when :headers, :ping
+ frame.merge(payload: log_redact_headers(frame[:payload]))
+ when :window_update
+ connection_or_stream = if (id = frame[:stream]).zero?
+ @connection
+ else
+ @streams.each_value.find { |s| s.id == id }
+ end
+ if connection_or_stream
+ frame.merge(
+ local_window: connection_or_stream.local_window,
+ remote_window: connection_or_stream.remote_window,
+ buffered_amount: connection_or_stream.buffered_amount,
+ stream_state: connection_or_stream.state,
+ )
+ else
+ frame
+ end
+ else
+ frame
+ end.merge(connection_state: @connection.state)
lib/httpx/plugins/auth/digest.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:34:00.436177736 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:34:00.488178316 +0000
@@ -11 +11,2 @@
- Error = Class.new(Error)
+ class Error < Error
+ end
lib/httpx/plugins/cookies.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/cookies.rb 2026-03-06 03:34:00.437177747 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/cookies.rb 2026-03-06 03:34:00.490178338 +0000
@@ -10,2 +9,0 @@
- # It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
- #
@@ -48,0 +47,6 @@
+ # factory method to return a Jar to the user, which can then manipulate
+ # externally to the session.
+ def make_jar(*args)
+ Jar.new(*args)
+ end
+
@@ -99 +103 @@
- jar.add(Cookie.new(name, value))
+ jar.set(name, value)
lib/httpx/plugins/cookies/cookie.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:34:00.438177758 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:34:00.490178338 +0000
@@ -16,0 +17 @@
+ # assigns a new +path+ to this cookie.
@@ -18,0 +20 @@
+ @for_domain = false
@@ -22 +24 @@
- # See #domain.
+ # assigns a new +domain+ to this cookie.
@@ -39,0 +42,7 @@
+ # checks whether +other+ is the same cookie, i.e. name, value, domain and path are
+ # the same.
+ def ==(other)
+ @name == other.name && @value == other.value &&
+ @path == other.path && @domain == other.domain
+ end
+
@@ -49,0 +59,11 @@
+ def match?(name_or_options)
+ case name_or_options
+ when String
+ @name == name_or_options
+ when Hash, Array
+ name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
+ else
+ false
+ end
+ end
+
@@ -52 +72,7 @@
- return cookie if cookie.is_a?(self)
+ case cookie
+ when self
+ cookie
+ when Array, Hash
+ options = Hash[cookie] #: cookie_attributes
+ super(options[:name], options[:value], options)
+ else
@@ -54 +80,2 @@
- super
+ super
+ end
@@ -87 +114 @@
- def initialize(arg, *attrs)
+ def initialize(arg, value, attrs = nil)
@@ -90,7 +117,3 @@
- if attrs.empty?
- attr_hash = Hash.try_convert(arg)
- else
- @name = arg
- @value, attr_hash = attrs
- attr_hash = Hash.try_convert(attr_hash)
- end
+ @name = arg
+ @value = value
+ attr_hash = Hash.try_convert(attrs)
lib/httpx/plugins/cookies/jar.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:34:00.438177758 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:34:00.491178349 +0000
@@ -7 +7,6 @@
- # It holds a bunch of cookies.
+ # It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
+ # initialization from parsing `Set-Cookie` HTTP header values.
+ #
+ # It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
+ # by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
+ #
@@ -14,0 +20 @@
+ @mtx = orig.instance_variable_get(:@mtx).dup
@@ -17,0 +24,2 @@
+ # initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
+ # can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
@@ -18,0 +27 @@
+ @mtx = Thread::Mutex.new
@@ -34,0 +44 @@
+ # parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
@@ -37 +47 @@
- add(Cookie.new(name, value, attrs))
+ set(Cookie.new(name, value, attrs))
@@ -40,0 +51,42 @@
+ # returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get(name_or_options)
+ each.find { |ck| ck.match?(name_or_options) }
+ end
+
+ # returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get_all(name_or_options)
+ each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
+ end
+
+ # when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
+ # it creates a cookie with it and the value-or-attributes passed to +value_or_options+.
+
+ # optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
+ def set(name, value_or_options = nil)
+ cookie = case name
+ when Cookie
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ name
+ when Array, Hash
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ Cookie.new(name)
+ else
+ raise ArgumentError, "the second argument is required" unless value_or_options
+
+ Cookie.new(name, value_or_options)
+ end
+
+ synchronize do
+ # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
+ # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
+ @cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }
+
+ @cookies << cookie
+ end
+ end
+
+ # @deprecated
@@ -41,0 +94 @@
+ warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
@@ -43 +95,0 @@
-
@@ -44,0 +97,2 @@
+ set(c)
+ end
@@ -46,5 +100,13 @@
- # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
- # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
- @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
-
- @cookies << c
+ # deletes all cookies in the store which match either the name (when String) or all attributes (when a Hash
+ # or array of tuples) passed to +name_or_options+.
+ #
+ # alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
+ def delete(name_or_options)
+ synchronize do
+ case name_or_options
+ when Cookie
+ @cookies.delete(name_or_options)
+ else
+ @cookies.delete_if { |ck| ck.match?(name_or_options) }
+ end
+ end
@@ -52,0 +115 @@
+ # returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
@@ -56,0 +120,2 @@
+ # enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
+ # only yield cookies which match its domain and path.
@@ -60 +125 @@
- return @cookies.each(&blk) unless uri
+ return synchronize { @cookies.each(&blk) } unless uri
@@ -65,6 +130,8 @@
- @cookies.delete_if do |cookie|
- if cookie.expired?(now)
- true
- else
- yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
- false
+ synchronize do
+ @cookies.delete_if do |cookie|
+ if cookie.expired?(now)
+ true
+ else
+ yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
+ false
+ end
@@ -76 +143 @@
- cookies_dup = dup
+ jar_dup = dup
@@ -88 +155 @@
- cookies_dup.add(cookie)
+ jar_dup.set(cookie)
@@ -91 +158,9 @@
- cookies_dup
+ jar_dup
+ end
+
+ private
+
+ def synchronize(&block)
+ return yield if @mtx.owned?
+
+ @mtx.synchronize(&block)
lib/httpx/plugins/expect.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/expect.rb 2026-03-06 03:34:00.438177758 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/expect.rb 2026-03-06 03:34:00.491178349 +0000
@@ -11,0 +12,20 @@
+ NOEXPECT_STORE_MUTEX = Thread::Mutex.new
+
+ class Store
+ def initialize
+ @store = []
+ @mutex = Thread::Mutex.new
+ end
+
+ def include?(host)
+ @mutex.synchronize { @store.include?(host) }
+ end
+
+ def add(host)
+ @mutex.synchronize { @store << host }
+ end
+
+ def delete(host)
+ @mutex.synchronize { @store.delete(host) }
+ end
+ end
@@ -15 +35,5 @@
- @no_expect_store ||= []
+ return Ractor.store_if_absent(:httpx_no_expect_store) { Store.new } if Utils.in_ractor?
+
+ @no_expect_store ||= NOEXPECT_STORE_MUTEX.synchronize do
+ @no_expect_store || Store.new
+ end
@@ -92 +116 @@
- Expect.no_expect_store << request.origin
+ Expect.no_expect_store.add(request.origin)
lib/httpx/plugins/fiber_concurrency.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:34:00.438177758 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:34:00.492178361 +0000
@@ -163,3 +163 @@
- module NativeResolverMethods
- private
-
+ module ResolverNativeMethods
@@ -175 +173 @@
- module SystemResolverMethods
+ module ResolverSystemMethods
lib/httpx/plugins/follow_redirects.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:34:00.438177758 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:34:00.492178361 +0000
@@ -4 +4,3 @@
- InsecureRedirectError = Class.new(Error)
+ class InsecureRedirectError < Error
+ end
+
lib/httpx/plugins/rate_limiter.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:34:00.441177792 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:34:00.498178427 +0000
@@ -19,19 +19 @@
- klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
- end
-
- # Servers send the "Retry-After" header field to indicate how long the
- # user agent ought to wait before making a follow-up request. When
- # sent with a 503 (Service Unavailable) response, Retry-After indicates
- # how long the service is expected to be unavailable to the client.
- # When sent with any 3xx (Redirection) response, Retry-After indicates
- # the minimum time that the user agent is asked to wait before issuing
- # the redirected request.
- #
- def retry_after_rate_limit(_, response)
- return unless response.is_a?(Response)
-
- retry_after = response.headers["retry-after"]
-
- return unless retry_after
-
- Utils.parse_retry_after(retry_after)
+ klass.plugin(:retries)
@@ -53,0 +36,18 @@
+ end
+
+ # Servers send the "Retry-After" header field to indicate how long the
+ # user agent ought to wait before making a follow-up request. When
+ # sent with a 503 (Service Unavailable) response, Retry-After indicates
+ # how long the service is expected to be unavailable to the client.
+ # When sent with any 3xx (Redirection) response, Retry-After indicates
+ # the minimum time that the user agent is asked to wait before issuing
+ # the redirected request.
+ #
+ def when_to_retry(_, response, options)
+ return super unless response.is_a?(Response)
+
+ retry_after = response.headers["retry-after"]
+
+ return super unless retry_after
+
+ Utils.parse_retry_after(retry_after)
lib/httpx/plugins/retries.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/retries.rb 2026-03-06 03:34:00.442177803 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/retries.rb 2026-03-06 03:34:00.499178438 +0000
@@ -151,4 +151 @@
- retry_after = options.retry_after
- retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
-
- if retry_after
+ if (retry_after = when_to_retry(request, response, options))
@@ -203,0 +201,6 @@
+ def when_to_retry(request, response, options)
+ retry_after = options.retry_after
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
+ retry_after
+ end
+
@@ -239,0 +243 @@
+ @partial_response = nil
@@ -243 +247 @@
- if @partial_response
+ if (partial_response = @partial_response)
@@ -245 +249 @@
- response.from_partial_response(@partial_response)
+ response.from_partial_response(partial_response)
@@ -247 +251 @@
- @partial_response.close
+ partial_response.close
lib/httpx/plugins/ssrf_filter.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:34:00.442177803 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:34:00.500178450 +0000
@@ -108,0 +109 @@
+ request.response = response
lib/httpx/plugins/stream_bidi.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:34:00.442177803 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:34:00.500178450 +0000
@@ -191,0 +192,4 @@
+ def force_close(*)
+ terminate
+ end
+
@@ -203,0 +208,2 @@
+
+ alias_method :on_io_error, :on_error
lib/httpx/request.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/request.rb 2026-03-06 03:34:00.443177814 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/request.rb 2026-03-06 03:34:00.501178461 +0000
@@ -94 +94 @@
- @response = @peer_address = @informational_status = nil
+ @response = @drainer = @peer_address = @informational_status = nil
lib/httpx/resolver/resolver.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/resolver/resolver.rb 2026-03-06 03:34:00.445177837 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/resolver/resolver.rb 2026-03-06 03:34:00.504178494 +0000
@@ -121,0 +122,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
lib/httpx/selector.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/selector.rb 2026-03-06 03:34:00.446177848 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/selector.rb 2026-03-06 03:34:00.505178505 +0000
@@ -207,2 +207 @@
- sel.on_error(e)
- sel.force_close(true)
+ sel.on_io_error(e)
@@ -252,2 +251,3 @@
- io.on_error(e)
- io.force_close(true)
+ io.on_io_error(e)
+
+ return
lib/httpx/session.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/session.rb 2026-03-06 03:34:00.446177848 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/session.rb 2026-03-06 03:34:00.505178505 +0000
@@ -142,0 +143 @@
+ # do not check-in connections only created for Happy Eyeballs
@@ -145 +146 @@
- return if @closing && connection.state == :closed
+ return if @closing && connection.state == :closed && !connection.used?
@@ -180 +180,0 @@
- log(level: 2) { "finding connection for #{request_uri}..." }
@@ -183 +183 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
@@ -189 +189 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
@@ -243 +243 @@
- log(level: 2) { "response fetched" }
+ log(level: 2) { "response##{response.object_id} fetched" }
@@ -251,0 +252 @@
+ log(level: 2) { "finding connection for request##{request.object_id}..." }
lib/httpx/version.rb
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/lib/httpx/version.rb 2026-03-06 03:34:00.449177881 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/lib/httpx/version.rb 2026-03-06 03:34:00.508178539 +0000
@@ -4 +4 @@
- VERSION = "1.7.2"
+ VERSION = "1.7.3"
sig/chainable.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/chainable.rbs 2026-03-06 03:34:00.449177881 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/chainable.rbs 2026-03-06 03:34:00.510178561 +0000
@@ -32 +32 @@
- | (:rate_limiter, ?options) -> Session
+ | (:rate_limiter, ?options) -> Plugins::sessionRateLimiter
sig/connection.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/connection.rbs 2026-03-06 03:34:00.449177881 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/connection.rbs 2026-03-06 03:34:00.510178561 +0000
@@ -39,0 +40 @@
+ @max_concurrent_requests: Integer?
@@ -120,0 +122,2 @@
+
+ def on_io_error: (IOError error) -> void
sig/connection/http2.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/connection/http2.rbs 2026-03-06 03:34:00.450177892 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/connection/http2.rbs 2026-03-06 03:34:00.510178561 +0000
@@ -81,0 +82,2 @@
+ def on_stream_half_close: (::HTTP2::Stream stream, Request request) -> void
+
@@ -90 +92,3 @@
- def on_frame_sent: (::HTTP2::frame) -> void
+ def on_frame_sent: (::HTTP2::frame frame) -> void
+
+ def frame_with_extra_info: (::HTTP2::frame frame) -> Hash[Symbol, untyped]
@@ -92 +96 @@
- def on_frame_received: (::HTTP2::frame) -> void
+ def on_frame_received: (::HTTP2::frame frame) -> void
@@ -94 +98 @@
- def on_altsvc: (String origin, ::HTTP2::frame) -> void
+ def on_altsvc: (String origin, ::HTTP2::frame frame) -> void
@@ -96 +100 @@
- def on_promise: (::HTTP2::Stream) -> void
+ def on_promise: (::HTTP2::Stream stream) -> void
sig/plugins/cookies.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/cookies.rbs 2026-03-06 03:34:00.454177937 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/cookies.rbs 2026-03-06 03:34:00.514178606 +0000
@@ -17,0 +18,2 @@
+
+ def make_jar: (*untyped) -> Jar
sig/plugins/cookies/cookie.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/cookies/cookie.rbs 2026-03-06 03:34:00.454177937 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/cookies/cookie.rbs 2026-03-06 03:34:00.514178606 +0000
@@ -33,0 +34,2 @@
+ def match?: (String | cookie_attributes name_or_options) -> bool
+
@@ -44,2 +46 @@
- def initialize: (cookie_attributes) -> untyped
- | (_ToS, _ToS, ?cookie_attributes) -> untyped
+ def initialize: (_ToS name, _ToS value, ?cookie_attributes) -> void
sig/plugins/cookies/jar.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/cookies/jar.rbs 2026-03-06 03:34:00.454177937 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/cookies/jar.rbs 2026-03-06 03:34:00.514178606 +0000
@@ -8,0 +9 @@
+ @mtx: Thread::Mutex
@@ -11,0 +13,6 @@
+ def get: (String | cookie_attributes name_or_options) -> Cookie?
+
+ def get_all: (String | cookie_attributes name_or_options) -> Array[Cookie]
+
+ def set: (_ToS | Cookie name, ?(cookie_attributes | _ToS) value_or_options) -> void
+
@@ -13,0 +21,2 @@
+ def delete: (String | Cookie | cookie_attributes name_or_options) -> void
+
@@ -23,0 +33,2 @@
+
+ def synchronize: [T] { () -> T } -> T
sig/plugins/expect.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/expect.rbs 2026-03-06 03:34:00.455177948 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/expect.rbs 2026-03-06 03:34:00.515178617 +0000
@@ -4,0 +5,17 @@
+ NOEXPECT_STORE_MUTEX: Thread::Mutex
+
+ self.@no_expect_store: Store
+ def self.no_expect_store: () -> Store
+
+ def self.extra_options: (Options) -> (Options & _ExpectOptions)
+
+ class Store
+ @store: Array[String]
+ @mutex: Thread::Mutex
+
+ def include?: (String host) -> bool
+
+ def add: (String host) -> void
+
+ def delete: (String host) -> void
+ end
@@ -11,2 +27,0 @@
-
- def self.extra_options: (Options) -> (Options & _ExpectOptions)
sig/plugins/proxy/socks4.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/proxy/socks4.rbs 2026-03-06 03:34:00.457177970 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/proxy/socks4.rbs 2026-03-06 03:34:00.516178628 +0000
@@ -7,0 +8,4 @@
+ VERSION: Integer
+ CONNECT: Integer
+ GRANTED: Integer
+ PROTOCOLS: Array[String]
sig/plugins/rate_limiter.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/rate_limiter.rbs 2026-03-06 03:34:00.458177982 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/rate_limiter.rbs 2026-03-06 03:34:00.517178639 +0000
@@ -8,2 +7,0 @@
- def self.retry_after_rate_limit: (untyped, response) -> Numeric?
-
@@ -13,0 +12,2 @@
+
+ type sessionRateLimiter = Session & RateLimiter::InstanceMethods
sig/plugins/response_cache.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/response_cache.rbs 2026-03-06 03:34:00.458177982 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/response_cache.rbs 2026-03-06 03:34:00.517178639 +0000
@@ -54 +54 @@
- @cache: bool
+ @cached: bool
@@ -69 +69 @@
- def cache_control: () -> Array[String]?
+ %a{pure} def cache_control: () -> Array[String]?
@@ -71 +71 @@
- def vary: () -> Array[String]?
+ %a{pure} def vary: () -> Array[String]?
sig/plugins/retries.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/plugins/retries.rbs 2026-03-06 03:34:00.458177982 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/plugins/retries.rbs 2026-03-06 03:34:00.517178639 +0000
@@ -11 +11 @@
- def self?.retry_after_polynomial_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_polynomial_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -13 +13 @@
- def self?.retry_after_exponential_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_exponential_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -16 +16 @@
- def call: (response response) -> bool?
+ def call: (retriesResponse response) -> bool?
@@ -20 +20 @@
- def retry_after: () -> (^(retriesRequest request, response response) -> Numeric | Numeric)?
+ def retry_after: () -> (^(retriesRequest request, retriesResponse response) -> Numeric | Numeric)?
@@ -38 +38 @@
- def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> (retriesResponse | ErrorResponse)?
+ def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> retriesResponse?
@@ -40 +40 @@
- def retryable_request?: (retriesRequest request, response response, retriesOptions options) -> boolish
+ def retryable_request?: (retriesRequest request, retriesResponse response, retriesOptions options) -> boolish
@@ -42 +42 @@
- def retryable_response?: (response response, retriesOptions options) -> boolish
+ def retryable_response?: (retriesResponse response, retriesOptions options) -> boolish
@@ -46 +46 @@
- def try_partial_retry: (retriesRequest request, (retriesResponse | ErrorResponse) response) -> void
+ def try_partial_retry: (retriesRequest request, retriesResponse response) -> void
@@ -48 +48,3 @@
- def prepare_to_retry: (Request & RequestMethods request, response response) -> void
+ def prepare_to_retry: (Request & RequestMethods request, retriesResponse response) -> void
+
+ def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> void
@@ -56 +58 @@
- attr_writer partial_response: Response?
+ attr_writer partial_response: retriesResponse?
@@ -58 +60 @@
- def response=: (retriesResponse | ErrorResponse response) -> void
+ def response=: (retriesResponse response) -> void
@@ -62 +64 @@
- def from_partial_response: (Response response) -> void
+ def from_partial_response: (retriesHTTPResponse response) -> void
@@ -69 +71,3 @@
- type retriesResponse = Response & ResponseMethods
+ type retriesHTTPResponse = Response & ResponseMethods
+
+ type retriesResponse = retriesHTTPResponse | ErrorResponse
sig/request.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/request.rbs 2026-03-06 03:34:00.460178004 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/request.rbs 2026-03-06 03:34:00.518178650 +0000
@@ -8,0 +9 @@
+ ALLOWED_URI_SCHEMES: Array[String]
sig/resolver/native.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/resolver/native.rbs 2026-03-06 03:34:00.461178015 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/resolver/native.rbs 2026-03-06 03:34:00.519178661 +0000
@@ -39,0 +40,2 @@
+ def on_io_error: (IOError error) -> void
+
sig/resolver/resolver.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/resolver/resolver.rbs 2026-03-06 03:34:00.461178015 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/resolver/resolver.rbs 2026-03-06 03:34:00.519178661 +0000
@@ -30,2 +29,0 @@
- def force_close: (*untyped args) -> void
-
@@ -62,0 +61,2 @@
+
+ def disconnect: () -> void
sig/selector.rbs
--- /tmp/d20260306-2537-727lv0/httpx-1.7.2/sig/selector.rbs 2026-03-06 03:34:00.462178026 +0000
+++ /tmp/d20260306-2537-727lv0/httpx-1.7.3/sig/selector.rbs 2026-03-06 03:34:00.520178673 +0000
@@ -15,0 +16,4 @@
+
+ def on_io_error: (IOError error) -> void
+
+ def force_close: (?bool delete_pending) -> void |
Contributor
gem compare httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT version:
1.7.2: 1.7.2
1.7.3: 1.7.3
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
lib/httpx/adapters/webmock.rb +3/-4
lib/httpx/connection.rb +18/-4
lib/httpx/connection/http1.rb +0/-1
lib/httpx/connection/http2.rb +41/-30
lib/httpx/plugins/auth/digest.rb +2/-1
lib/httpx/plugins/cookies.rb +7/-3
lib/httpx/plugins/cookies/cookie.rb +34/-11
lib/httpx/plugins/cookies/jar.rb +93/-18
lib/httpx/plugins/expect.rb +26/-2
lib/httpx/plugins/fiber_concurrency.rb +2/-4
lib/httpx/plugins/follow_redirects.rb +3/-1
lib/httpx/plugins/rate_limiter.rb +19/-19
lib/httpx/plugins/retries.rb +11/-7
lib/httpx/plugins/ssrf_filter.rb +1/-0
lib/httpx/plugins/stream_bidi.rb +6/-0
lib/httpx/request.rb +1/-1
lib/httpx/resolver/resolver.rb +5/-0
lib/httpx/selector.rb +4/-4
lib/httpx/session.rb +6/-5
lib/httpx/version.rb +1/-1
sig/chainable.rbs +1/-1
sig/connection.rbs +3/-0
sig/connection/http2.rbs +8/-4
sig/plugins/cookies.rbs +2/-0
sig/plugins/cookies/cookie.rbs +3/-2
sig/plugins/cookies/jar.rbs +11/-0
sig/plugins/expect.rbs +17/-2
sig/plugins/proxy/socks4.rbs +4/-0
sig/plugins/rate_limiter.rbs +2/-2
sig/plugins/response_cache.rbs +3/-3
sig/plugins/retries.rbs +17/-13
sig/request.rbs +1/-0
sig/resolver/native.rbs +2/-0
sig/resolver/resolver.rbs +2/-2
sig/selector.rbs +4/-0
DIFFERENT extra_rdoc_files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
DIFFERENT runtime dependencies:
1.7.2->1.7.3:
* Updated:
http-2 from: [">= 1.0.0"] to: [">= 1.1.3"] |
1 similar comment
Contributor
gem compare httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT version:
1.7.2: 1.7.2
1.7.3: 1.7.3
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
lib/httpx/adapters/webmock.rb +3/-4
lib/httpx/connection.rb +18/-4
lib/httpx/connection/http1.rb +0/-1
lib/httpx/connection/http2.rb +41/-30
lib/httpx/plugins/auth/digest.rb +2/-1
lib/httpx/plugins/cookies.rb +7/-3
lib/httpx/plugins/cookies/cookie.rb +34/-11
lib/httpx/plugins/cookies/jar.rb +93/-18
lib/httpx/plugins/expect.rb +26/-2
lib/httpx/plugins/fiber_concurrency.rb +2/-4
lib/httpx/plugins/follow_redirects.rb +3/-1
lib/httpx/plugins/rate_limiter.rb +19/-19
lib/httpx/plugins/retries.rb +11/-7
lib/httpx/plugins/ssrf_filter.rb +1/-0
lib/httpx/plugins/stream_bidi.rb +6/-0
lib/httpx/request.rb +1/-1
lib/httpx/resolver/resolver.rb +5/-0
lib/httpx/selector.rb +4/-4
lib/httpx/session.rb +6/-5
lib/httpx/version.rb +1/-1
sig/chainable.rbs +1/-1
sig/connection.rbs +3/-0
sig/connection/http2.rbs +8/-4
sig/plugins/cookies.rbs +2/-0
sig/plugins/cookies/cookie.rbs +3/-2
sig/plugins/cookies/jar.rbs +11/-0
sig/plugins/expect.rbs +17/-2
sig/plugins/proxy/socks4.rbs +4/-0
sig/plugins/rate_limiter.rbs +2/-2
sig/plugins/response_cache.rbs +3/-3
sig/plugins/retries.rbs +17/-13
sig/request.rbs +1/-0
sig/resolver/native.rbs +2/-0
sig/resolver/resolver.rbs +2/-2
sig/selector.rbs +4/-0
DIFFERENT extra_rdoc_files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md +29/-0
* Changed:
README.md +3/-1
DIFFERENT runtime dependencies:
1.7.2->1.7.3:
* Updated:
http-2 from: [">= 1.0.0"] to: [">= 1.1.3"] |
Contributor
gem compare --diff httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md
--- /tmp/20260306-2800-3jzguc 2026-03-06 03:34:16.109562300 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/doc/release_notes/1_7_3.md 2026-03-06 03:34:16.077562085 +0000
@@ -0,0 +1,29 @@
+# 1.7.3
+
+## Improvements
+
+### cookies plugin: Jar as CookieStore
+
+While previously an implementation detail, the cookie jar from a `:cookie` plugin-enabled session can now be manipulated by the end user:
+
+```ruby
+cookies_sess = HTTPX.plugin(:cookies)
+
+jar = cookies.make_jar
+
+sess = cookies_ses.with(cookies: jar)
+
+# perform requests using sess, get/set/delete cookies in jar
+```
+
+The jar API now closely follows the [Web Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore), by providing the same set of functions.
+
+Some API backwards compatibility is maintained, however since this was an internal implementation detail, this effort isn't meant to be thorough.
+
+## Bugfixes
+
+* `http-2`: clear buffered data chunks when receiving a `GOAWAY` stream frame; without this, the client kept sending the corresponding `DATA` frames, despite the peer server making it known that it wouldn't process it. While this is valid HTTP/2, this could increase the connection window until a point where it'd go over the max frame size. this issue was observed during large file uploads where the first request could fail and make the client renegotiate.
+* `webmock` adapter: fixed response body length accounting which was making `response.body.empty?` return true for responses with payload.
+* `:rate_limiter` plugin relies on an internal refactoring to be able to wait for the time suggested by the peer server instead of the potentially relying on custom user logic via own `:retry_after`.
+* `:fiber_concurrency`: fix wrong names for native/system resolver overrides.
+* connection: fix for race condition when closing the connection, where the state only transitions to `closed` after checking the connection back in to the pool, potentially corrupting it if another session meanwhile has picked it up and manipulated it.
\ No newline at end of file
* Changed:
README.md
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/README.md 2026-03-06 03:34:15.992561515 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/README.md 2026-03-06 03:34:16.058561958 +0000
@@ -49 +49,3 @@
-http.patch("http://example.com/file", body: File.open("path/to/file")) # request body is streamed
+File.open("path/to/file") do |file|
+ http.patch("http://example.com/file", body: file) # request body is streamed
+end
lib/httpx/adapters/webmock.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/adapters/webmock.rb 2026-03-06 03:34:16.029561763 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/adapters/webmock.rb 2026-03-06 03:34:16.078562092 +0000
@@ -84,0 +85 @@
+ @body.mock!
@@ -93,4 +94,2 @@
- def decode_chunk(chunk)
- return chunk if @response.mocked?
-
- super
+ def mock!
+ @inflaters = nil
lib/httpx/connection.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/connection.rb 2026-03-06 03:34:16.030561770 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/connection.rb 2026-03-06 03:34:16.079562098 +0000
@@ -229,0 +230,3 @@
+ rescue IOError => e
+ @write_buffer.clear
+ on_io_error(e)
@@ -377,0 +381,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
@@ -496 +504 @@
- # buffer has been drainned, mark and exit the write loop.
+ # buffer has been drained, mark and exit the write loop.
@@ -588,0 +597,2 @@
+
+ parser.pending.clear
@@ -724,2 +733,0 @@
-
- disconnect
@@ -743 +750,0 @@
- disconnect if @pending.empty?
@@ -744,0 +752,2 @@
+ # TODO: should this raise an error instead?
+ return unless @pending.empty?
@@ -760,0 +770,5 @@
+ # post state change
+ case nextstate
+ when :closed, :inactive
+ disconnect
+ end
lib/httpx/connection/http1.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/connection/http1.rb 2026-03-06 03:34:16.030561770 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/connection/http1.rb 2026-03-06 03:34:16.079562098 +0000
@@ -283 +282,0 @@
- return if @requests.empty?
lib/httpx/connection/http2.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/connection/http2.rb 2026-03-06 03:34:16.031561776 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/connection/http2.rb 2026-03-06 03:34:16.079562098 +0000
@@ -6,2 +5,0 @@
-HTTP2::Connection.__send__(:public, :send_buffer) if HTTP2::VERSION < "1.1.1"
-
@@ -218,3 +216 @@
- stream.on(:half_close) do
- log(level: 2) { "#{stream.id}: waiting for response..." }
- end
+ stream.on(:half_close) { on_stream_half_close(stream, request) }
@@ -305 +301 @@
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
@@ -333,0 +330,10 @@
+ def on_stream_half_close(stream, _request)
+ unless stream.send_buffer.empty?
+ stream.send_buffer.clear
+ stream.data("", end_stream: true)
+ end
+
+ # TODO: omit log line if response already here
+ log(level: 2) { "#{stream.id}: waiting for response..." }
+ end
+
@@ -407,12 +413 @@
- log(level: 2, color: :blue) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :blue) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
@@ -423,12 +418,28 @@
- log(level: 2, color: :magenta) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :magenta) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
+ end
+
+ def frame_with_extra_info(frame)
+ case frame[:type]
+ when :data
+ frame.merge(payload: frame[:payload].bytesize)
+ when :headers, :ping
+ frame.merge(payload: log_redact_headers(frame[:payload]))
+ when :window_update
+ connection_or_stream = if (id = frame[:stream]).zero?
+ @connection
+ else
+ @streams.each_value.find { |s| s.id == id }
+ end
+ if connection_or_stream
+ frame.merge(
+ local_window: connection_or_stream.local_window,
+ remote_window: connection_or_stream.remote_window,
+ buffered_amount: connection_or_stream.buffered_amount,
+ stream_state: connection_or_stream.state,
+ )
+ else
+ frame
+ end
+ else
+ frame
+ end.merge(connection_state: @connection.state)
lib/httpx/plugins/auth/digest.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:34:16.033561790 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:34:16.081562112 +0000
@@ -11 +11,2 @@
- Error = Class.new(Error)
+ class Error < Error
+ end
lib/httpx/plugins/cookies.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/cookies.rb 2026-03-06 03:34:16.035561803 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/cookies.rb 2026-03-06 03:34:16.083562125 +0000
@@ -10,2 +9,0 @@
- # It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
- #
@@ -48,0 +47,6 @@
+ # factory method to return a Jar to the user, which can then manipulate
+ # externally to the session.
+ def make_jar(*args)
+ Jar.new(*args)
+ end
+
@@ -99 +103 @@
- jar.add(Cookie.new(name, value))
+ jar.set(name, value)
lib/httpx/plugins/cookies/cookie.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:34:16.035561803 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:34:16.083562125 +0000
@@ -16,0 +17 @@
+ # assigns a new +path+ to this cookie.
@@ -18,0 +20 @@
+ @for_domain = false
@@ -22 +24 @@
- # See #domain.
+ # assigns a new +domain+ to this cookie.
@@ -39,0 +42,7 @@
+ # checks whether +other+ is the same cookie, i.e. name, value, domain and path are
+ # the same.
+ def ==(other)
+ @name == other.name && @value == other.value &&
+ @path == other.path && @domain == other.domain
+ end
+
@@ -49,0 +59,11 @@
+ def match?(name_or_options)
+ case name_or_options
+ when String
+ @name == name_or_options
+ when Hash, Array
+ name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
+ else
+ false
+ end
+ end
+
@@ -52 +72,7 @@
- return cookie if cookie.is_a?(self)
+ case cookie
+ when self
+ cookie
+ when Array, Hash
+ options = Hash[cookie] #: cookie_attributes
+ super(options[:name], options[:value], options)
+ else
@@ -54 +80,2 @@
- super
+ super
+ end
@@ -87 +114 @@
- def initialize(arg, *attrs)
+ def initialize(arg, value, attrs = nil)
@@ -90,7 +117,3 @@
- if attrs.empty?
- attr_hash = Hash.try_convert(arg)
- else
- @name = arg
- @value, attr_hash = attrs
- attr_hash = Hash.try_convert(attr_hash)
- end
+ @name = arg
+ @value = value
+ attr_hash = Hash.try_convert(attrs)
lib/httpx/plugins/cookies/jar.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:34:16.035561803 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:34:16.083562125 +0000
@@ -7 +7,6 @@
- # It holds a bunch of cookies.
+ # It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
+ # initialization from parsing `Set-Cookie` HTTP header values.
+ #
+ # It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
+ # by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
+ #
@@ -14,0 +20 @@
+ @mtx = orig.instance_variable_get(:@mtx).dup
@@ -17,0 +24,2 @@
+ # initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
+ # can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
@@ -18,0 +27 @@
+ @mtx = Thread::Mutex.new
@@ -34,0 +44 @@
+ # parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
@@ -37 +47 @@
- add(Cookie.new(name, value, attrs))
+ set(Cookie.new(name, value, attrs))
@@ -40,0 +51,42 @@
+ # returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get(name_or_options)
+ each.find { |ck| ck.match?(name_or_options) }
+ end
+
+ # returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get_all(name_or_options)
+ each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
+ end
+
+ # when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
+ # it creates a cookie with it and the value-or-attributes passed to +value_or_options+.
+
+ # optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
+ def set(name, value_or_options = nil)
+ cookie = case name
+ when Cookie
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ name
+ when Array, Hash
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ Cookie.new(name)
+ else
+ raise ArgumentError, "the second argument is required" unless value_or_options
+
+ Cookie.new(name, value_or_options)
+ end
+
+ synchronize do
+ # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
+ # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
+ @cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }
+
+ @cookies << cookie
+ end
+ end
+
+ # @deprecated
@@ -41,0 +94 @@
+ warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
@@ -43 +95,0 @@
-
@@ -44,0 +97,2 @@
+ set(c)
+ end
@@ -46,5 +100,13 @@
- # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
- # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
- @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
-
- @cookies << c
+ # deletes all cookies in the store which match either the name (when String) or all attributes (when a Hash
+ # or array of tuples) passed to +name_or_options+.
+ #
+ # alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
+ def delete(name_or_options)
+ synchronize do
+ case name_or_options
+ when Cookie
+ @cookies.delete(name_or_options)
+ else
+ @cookies.delete_if { |ck| ck.match?(name_or_options) }
+ end
+ end
@@ -52,0 +115 @@
+ # returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
@@ -56,0 +120,2 @@
+ # enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
+ # only yield cookies which match its domain and path.
@@ -60 +125 @@
- return @cookies.each(&blk) unless uri
+ return synchronize { @cookies.each(&blk) } unless uri
@@ -65,6 +130,8 @@
- @cookies.delete_if do |cookie|
- if cookie.expired?(now)
- true
- else
- yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
- false
+ synchronize do
+ @cookies.delete_if do |cookie|
+ if cookie.expired?(now)
+ true
+ else
+ yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
+ false
+ end
@@ -76 +143 @@
- cookies_dup = dup
+ jar_dup = dup
@@ -88 +155 @@
- cookies_dup.add(cookie)
+ jar_dup.set(cookie)
@@ -91 +158,9 @@
- cookies_dup
+ jar_dup
+ end
+
+ private
+
+ def synchronize(&block)
+ return yield if @mtx.owned?
+
+ @mtx.synchronize(&block)
lib/httpx/plugins/expect.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/expect.rb 2026-03-06 03:34:16.035561803 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/expect.rb 2026-03-06 03:34:16.083562125 +0000
@@ -11,0 +12,20 @@
+ NOEXPECT_STORE_MUTEX = Thread::Mutex.new
+
+ class Store
+ def initialize
+ @store = []
+ @mutex = Thread::Mutex.new
+ end
+
+ def include?(host)
+ @mutex.synchronize { @store.include?(host) }
+ end
+
+ def add(host)
+ @mutex.synchronize { @store << host }
+ end
+
+ def delete(host)
+ @mutex.synchronize { @store.delete(host) }
+ end
+ end
@@ -15 +35,5 @@
- @no_expect_store ||= []
+ return Ractor.store_if_absent(:httpx_no_expect_store) { Store.new } if Utils.in_ractor?
+
+ @no_expect_store ||= NOEXPECT_STORE_MUTEX.synchronize do
+ @no_expect_store || Store.new
+ end
@@ -92 +116 @@
- Expect.no_expect_store << request.origin
+ Expect.no_expect_store.add(request.origin)
lib/httpx/plugins/fiber_concurrency.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:34:16.035561803 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:34:16.083562125 +0000
@@ -163,3 +163 @@
- module NativeResolverMethods
- private
-
+ module ResolverNativeMethods
@@ -175 +173 @@
- module SystemResolverMethods
+ module ResolverSystemMethods
lib/httpx/plugins/follow_redirects.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:34:16.035561803 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:34:16.084562132 +0000
@@ -4 +4,3 @@
- InsecureRedirectError = Class.new(Error)
+ class InsecureRedirectError < Error
+ end
+
lib/httpx/plugins/rate_limiter.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:34:16.038561823 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:34:16.086562145 +0000
@@ -19,19 +19 @@
- klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
- end
-
- # Servers send the "Retry-After" header field to indicate how long the
- # user agent ought to wait before making a follow-up request. When
- # sent with a 503 (Service Unavailable) response, Retry-After indicates
- # how long the service is expected to be unavailable to the client.
- # When sent with any 3xx (Redirection) response, Retry-After indicates
- # the minimum time that the user agent is asked to wait before issuing
- # the redirected request.
- #
- def retry_after_rate_limit(_, response)
- return unless response.is_a?(Response)
-
- retry_after = response.headers["retry-after"]
-
- return unless retry_after
-
- Utils.parse_retry_after(retry_after)
+ klass.plugin(:retries)
@@ -53,0 +36,18 @@
+ end
+
+ # Servers send the "Retry-After" header field to indicate how long the
+ # user agent ought to wait before making a follow-up request. When
+ # sent with a 503 (Service Unavailable) response, Retry-After indicates
+ # how long the service is expected to be unavailable to the client.
+ # When sent with any 3xx (Redirection) response, Retry-After indicates
+ # the minimum time that the user agent is asked to wait before issuing
+ # the redirected request.
+ #
+ def when_to_retry(_, response, options)
+ return super unless response.is_a?(Response)
+
+ retry_after = response.headers["retry-after"]
+
+ return super unless retry_after
+
+ Utils.parse_retry_after(retry_after)
lib/httpx/plugins/retries.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/retries.rb 2026-03-06 03:34:16.039561830 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/retries.rb 2026-03-06 03:34:16.086562145 +0000
@@ -151,4 +151 @@
- retry_after = options.retry_after
- retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
-
- if retry_after
+ if (retry_after = when_to_retry(request, response, options))
@@ -203,0 +201,6 @@
+ def when_to_retry(request, response, options)
+ retry_after = options.retry_after
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
+ retry_after
+ end
+
@@ -239,0 +243 @@
+ @partial_response = nil
@@ -243 +247 @@
- if @partial_response
+ if (partial_response = @partial_response)
@@ -245 +249 @@
- response.from_partial_response(@partial_response)
+ response.from_partial_response(partial_response)
@@ -247 +251 @@
- @partial_response.close
+ partial_response.close
lib/httpx/plugins/ssrf_filter.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:34:16.039561830 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:34:16.086562145 +0000
@@ -108,0 +109 @@
+ request.response = response
lib/httpx/plugins/stream_bidi.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:34:16.039561830 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:34:16.088562159 +0000
@@ -191,0 +192,4 @@
+ def force_close(*)
+ terminate
+ end
+
@@ -203,0 +208,2 @@
+
+ alias_method :on_io_error, :on_error
lib/httpx/request.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/request.rb 2026-03-06 03:34:16.041561844 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/request.rb 2026-03-06 03:34:16.090562172 +0000
@@ -94 +94 @@
- @response = @peer_address = @informational_status = nil
+ @response = @drainer = @peer_address = @informational_status = nil
lib/httpx/resolver/resolver.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/resolver/resolver.rb 2026-03-06 03:34:16.042561850 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/resolver/resolver.rb 2026-03-06 03:34:16.092562186 +0000
@@ -121,0 +122,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
lib/httpx/selector.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/selector.rb 2026-03-06 03:34:16.043561857 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/selector.rb 2026-03-06 03:34:16.093562192 +0000
@@ -207,2 +207 @@
- sel.on_error(e)
- sel.force_close(true)
+ sel.on_io_error(e)
@@ -252,2 +251,3 @@
- io.on_error(e)
- io.force_close(true)
+ io.on_io_error(e)
+
+ return
lib/httpx/session.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/session.rb 2026-03-06 03:34:16.043561857 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/session.rb 2026-03-06 03:34:16.093562192 +0000
@@ -142,0 +143 @@
+ # do not check-in connections only created for Happy Eyeballs
@@ -145 +146 @@
- return if @closing && connection.state == :closed
+ return if @closing && connection.state == :closed && !connection.used?
@@ -180 +180,0 @@
- log(level: 2) { "finding connection for #{request_uri}..." }
@@ -183 +183 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
@@ -189 +189 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
@@ -243 +243 @@
- log(level: 2) { "response fetched" }
+ log(level: 2) { "response##{response.object_id} fetched" }
@@ -251,0 +252 @@
+ log(level: 2) { "finding connection for request##{request.object_id}..." }
lib/httpx/version.rb
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/lib/httpx/version.rb 2026-03-06 03:34:16.046561877 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/lib/httpx/version.rb 2026-03-06 03:34:16.096562213 +0000
@@ -4 +4 @@
- VERSION = "1.7.2"
+ VERSION = "1.7.3"
sig/chainable.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/chainable.rbs 2026-03-06 03:34:16.046561877 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/chainable.rbs 2026-03-06 03:34:16.096562213 +0000
@@ -32 +32 @@
- | (:rate_limiter, ?options) -> Session
+ | (:rate_limiter, ?options) -> Plugins::sessionRateLimiter
sig/connection.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/connection.rbs 2026-03-06 03:34:16.046561877 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/connection.rbs 2026-03-06 03:34:16.097562219 +0000
@@ -39,0 +40 @@
+ @max_concurrent_requests: Integer?
@@ -120,0 +122,2 @@
+
+ def on_io_error: (IOError error) -> void
sig/connection/http2.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/connection/http2.rbs 2026-03-06 03:34:16.047561884 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/connection/http2.rbs 2026-03-06 03:34:16.097562219 +0000
@@ -81,0 +82,2 @@
+ def on_stream_half_close: (::HTTP2::Stream stream, Request request) -> void
+
@@ -90 +92,3 @@
- def on_frame_sent: (::HTTP2::frame) -> void
+ def on_frame_sent: (::HTTP2::frame frame) -> void
+
+ def frame_with_extra_info: (::HTTP2::frame frame) -> Hash[Symbol, untyped]
@@ -92 +96 @@
- def on_frame_received: (::HTTP2::frame) -> void
+ def on_frame_received: (::HTTP2::frame frame) -> void
@@ -94 +98 @@
- def on_altsvc: (String origin, ::HTTP2::frame) -> void
+ def on_altsvc: (String origin, ::HTTP2::frame frame) -> void
@@ -96 +100 @@
- def on_promise: (::HTTP2::Stream) -> void
+ def on_promise: (::HTTP2::Stream stream) -> void
sig/plugins/cookies.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/cookies.rbs 2026-03-06 03:34:16.050561904 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/cookies.rbs 2026-03-06 03:34:16.100562239 +0000
@@ -17,0 +18,2 @@
+
+ def make_jar: (*untyped) -> Jar
sig/plugins/cookies/cookie.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/cookies/cookie.rbs 2026-03-06 03:34:16.050561904 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/cookies/cookie.rbs 2026-03-06 03:34:16.100562239 +0000
@@ -33,0 +34,2 @@
+ def match?: (String | cookie_attributes name_or_options) -> bool
+
@@ -44,2 +46 @@
- def initialize: (cookie_attributes) -> untyped
- | (_ToS, _ToS, ?cookie_attributes) -> untyped
+ def initialize: (_ToS name, _ToS value, ?cookie_attributes) -> void
sig/plugins/cookies/jar.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/cookies/jar.rbs 2026-03-06 03:34:16.050561904 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/cookies/jar.rbs 2026-03-06 03:34:16.101562246 +0000
@@ -8,0 +9 @@
+ @mtx: Thread::Mutex
@@ -11,0 +13,6 @@
+ def get: (String | cookie_attributes name_or_options) -> Cookie?
+
+ def get_all: (String | cookie_attributes name_or_options) -> Array[Cookie]
+
+ def set: (_ToS | Cookie name, ?(cookie_attributes | _ToS) value_or_options) -> void
+
@@ -13,0 +21,2 @@
+ def delete: (String | Cookie | cookie_attributes name_or_options) -> void
+
@@ -23,0 +33,2 @@
+
+ def synchronize: [T] { () -> T } -> T
sig/plugins/expect.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/expect.rbs 2026-03-06 03:34:16.050561904 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/expect.rbs 2026-03-06 03:34:16.101562246 +0000
@@ -4,0 +5,17 @@
+ NOEXPECT_STORE_MUTEX: Thread::Mutex
+
+ self.@no_expect_store: Store
+ def self.no_expect_store: () -> Store
+
+ def self.extra_options: (Options) -> (Options & _ExpectOptions)
+
+ class Store
+ @store: Array[String]
+ @mutex: Thread::Mutex
+
+ def include?: (String host) -> bool
+
+ def add: (String host) -> void
+
+ def delete: (String host) -> void
+ end
@@ -11,2 +27,0 @@
-
- def self.extra_options: (Options) -> (Options & _ExpectOptions)
sig/plugins/proxy/socks4.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/proxy/socks4.rbs 2026-03-06 03:34:16.052561917 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/proxy/socks4.rbs 2026-03-06 03:34:16.102562253 +0000
@@ -7,0 +8,4 @@
+ VERSION: Integer
+ CONNECT: Integer
+ GRANTED: Integer
+ PROTOCOLS: Array[String]
sig/plugins/rate_limiter.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/rate_limiter.rbs 2026-03-06 03:34:16.052561917 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/rate_limiter.rbs 2026-03-06 03:34:16.103562260 +0000
@@ -8,2 +7,0 @@
- def self.retry_after_rate_limit: (untyped, response) -> Numeric?
-
@@ -13,0 +12,2 @@
+
+ type sessionRateLimiter = Session & RateLimiter::InstanceMethods
sig/plugins/response_cache.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/response_cache.rbs 2026-03-06 03:34:16.052561917 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/response_cache.rbs 2026-03-06 03:34:16.103562260 +0000
@@ -54 +54 @@
- @cache: bool
+ @cached: bool
@@ -69 +69 @@
- def cache_control: () -> Array[String]?
+ %a{pure} def cache_control: () -> Array[String]?
@@ -71 +71 @@
- def vary: () -> Array[String]?
+ %a{pure} def vary: () -> Array[String]?
sig/plugins/retries.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/plugins/retries.rbs 2026-03-06 03:34:16.053561924 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/plugins/retries.rbs 2026-03-06 03:34:16.103562260 +0000
@@ -11 +11 @@
- def self?.retry_after_polynomial_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_polynomial_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -13 +13 @@
- def self?.retry_after_exponential_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_exponential_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -16 +16 @@
- def call: (response response) -> bool?
+ def call: (retriesResponse response) -> bool?
@@ -20 +20 @@
- def retry_after: () -> (^(retriesRequest request, response response) -> Numeric | Numeric)?
+ def retry_after: () -> (^(retriesRequest request, retriesResponse response) -> Numeric | Numeric)?
@@ -38 +38 @@
- def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> (retriesResponse | ErrorResponse)?
+ def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> retriesResponse?
@@ -40 +40 @@
- def retryable_request?: (retriesRequest request, response response, retriesOptions options) -> boolish
+ def retryable_request?: (retriesRequest request, retriesResponse response, retriesOptions options) -> boolish
@@ -42 +42 @@
- def retryable_response?: (response response, retriesOptions options) -> boolish
+ def retryable_response?: (retriesResponse response, retriesOptions options) -> boolish
@@ -46 +46 @@
- def try_partial_retry: (retriesRequest request, (retriesResponse | ErrorResponse) response) -> void
+ def try_partial_retry: (retriesRequest request, retriesResponse response) -> void
@@ -48 +48,3 @@
- def prepare_to_retry: (Request & RequestMethods request, response response) -> void
+ def prepare_to_retry: (Request & RequestMethods request, retriesResponse response) -> void
+
+ def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> void
@@ -56 +58 @@
- attr_writer partial_response: Response?
+ attr_writer partial_response: retriesResponse?
@@ -58 +60 @@
- def response=: (retriesResponse | ErrorResponse response) -> void
+ def response=: (retriesResponse response) -> void
@@ -62 +64 @@
- def from_partial_response: (Response response) -> void
+ def from_partial_response: (retriesHTTPResponse response) -> void
@@ -69 +71,3 @@
- type retriesResponse = Response & ResponseMethods
+ type retriesHTTPResponse = Response & ResponseMethods
+
+ type retriesResponse = retriesHTTPResponse | ErrorResponse
sig/request.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/request.rbs 2026-03-06 03:34:16.054561931 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/request.rbs 2026-03-06 03:34:16.105562273 +0000
@@ -8,0 +9 @@
+ ALLOWED_URI_SCHEMES: Array[String]
sig/resolver/native.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/resolver/native.rbs 2026-03-06 03:34:16.055561938 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/resolver/native.rbs 2026-03-06 03:34:16.106562280 +0000
@@ -39,0 +40,2 @@
+ def on_io_error: (IOError error) -> void
+
sig/resolver/resolver.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/resolver/resolver.rbs 2026-03-06 03:34:16.055561938 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/resolver/resolver.rbs 2026-03-06 03:34:16.106562280 +0000
@@ -30,2 +29,0 @@
- def force_close: (*untyped args) -> void
-
@@ -62,0 +61,2 @@
+
+ def disconnect: () -> void
sig/selector.rbs
--- /tmp/d20260306-2800-oyz8b6/httpx-1.7.2/sig/selector.rbs 2026-03-06 03:34:16.056561944 +0000
+++ /tmp/d20260306-2800-oyz8b6/httpx-1.7.3/sig/selector.rbs 2026-03-06 03:34:16.106562280 +0000
@@ -15,0 +16,4 @@
+
+ def on_io_error: (IOError error) -> void
+
+ def force_close: (?bool delete_pending) -> void |
Contributor
gem compare --diff httpx 1.7.2 1.7.3Compared versions: ["1.7.2", "1.7.3"]
DIFFERENT files:
1.7.2->1.7.3:
* Added:
doc/release_notes/1_7_3.md
--- /tmp/20260306-2858-64ybys 2026-03-06 03:34:18.487991717 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/doc/release_notes/1_7_3.md 2026-03-06 03:34:18.458991779 +0000
@@ -0,0 +1,29 @@
+# 1.7.3
+
+## Improvements
+
+### cookies plugin: Jar as CookieStore
+
+While previously an implementation detail, the cookie jar from a `:cookie` plugin-enabled session can now be manipulated by the end user:
+
+```ruby
+cookies_sess = HTTPX.plugin(:cookies)
+
+jar = cookies.make_jar
+
+sess = cookies_ses.with(cookies: jar)
+
+# perform requests using sess, get/set/delete cookies in jar
+```
+
+The jar API now closely follows the [Web Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore), by providing the same set of functions.
+
+Some API backwards compatibility is maintained, however since this was an internal implementation detail, this effort isn't meant to be thorough.
+
+## Bugfixes
+
+* `http-2`: clear buffered data chunks when receiving a `GOAWAY` stream frame; without this, the client kept sending the corresponding `DATA` frames, despite the peer server making it known that it wouldn't process it. While this is valid HTTP/2, this could increase the connection window until a point where it'd go over the max frame size. this issue was observed during large file uploads where the first request could fail and make the client renegotiate.
+* `webmock` adapter: fixed response body length accounting which was making `response.body.empty?` return true for responses with payload.
+* `:rate_limiter` plugin relies on an internal refactoring to be able to wait for the time suggested by the peer server instead of the potentially relying on custom user logic via own `:retry_after`.
+* `:fiber_concurrency`: fix wrong names for native/system resolver overrides.
+* connection: fix for race condition when closing the connection, where the state only transitions to `closed` after checking the connection back in to the pool, potentially corrupting it if another session meanwhile has picked it up and manipulated it.
\ No newline at end of file
* Changed:
README.md
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/README.md 2026-03-06 03:34:18.394991915 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/README.md 2026-03-06 03:34:18.443991811 +0000
@@ -49 +49,3 @@
-http.patch("http://example.com/file", body: File.open("path/to/file")) # request body is streamed
+File.open("path/to/file") do |file|
+ http.patch("http://example.com/file", body: file) # request body is streamed
+end
lib/httpx/adapters/webmock.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/adapters/webmock.rb 2026-03-06 03:34:18.413991875 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/adapters/webmock.rb 2026-03-06 03:34:18.459991777 +0000
@@ -84,0 +85 @@
+ @body.mock!
@@ -93,4 +94,2 @@
- def decode_chunk(chunk)
- return chunk if @response.mocked?
-
- super
+ def mock!
+ @inflaters = nil
lib/httpx/connection.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/connection.rb 2026-03-06 03:34:18.414991873 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/connection.rb 2026-03-06 03:34:18.460991775 +0000
@@ -229,0 +230,3 @@
+ rescue IOError => e
+ @write_buffer.clear
+ on_io_error(e)
@@ -377,0 +381,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
@@ -496 +504 @@
- # buffer has been drainned, mark and exit the write loop.
+ # buffer has been drained, mark and exit the write loop.
@@ -588,0 +597,2 @@
+
+ parser.pending.clear
@@ -724,2 +733,0 @@
-
- disconnect
@@ -743 +750,0 @@
- disconnect if @pending.empty?
@@ -744,0 +752,2 @@
+ # TODO: should this raise an error instead?
+ return unless @pending.empty?
@@ -760,0 +770,5 @@
+ # post state change
+ case nextstate
+ when :closed, :inactive
+ disconnect
+ end
lib/httpx/connection/http1.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/connection/http1.rb 2026-03-06 03:34:18.415991870 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/connection/http1.rb 2026-03-06 03:34:18.460991775 +0000
@@ -283 +282,0 @@
- return if @requests.empty?
lib/httpx/connection/http2.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/connection/http2.rb 2026-03-06 03:34:18.415991870 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/connection/http2.rb 2026-03-06 03:34:18.460991775 +0000
@@ -6,2 +5,0 @@
-HTTP2::Connection.__send__(:public, :send_buffer) if HTTP2::VERSION < "1.1.1"
-
@@ -218,3 +216 @@
- stream.on(:half_close) do
- log(level: 2) { "#{stream.id}: waiting for response..." }
- end
+ stream.on(:half_close) { on_stream_half_close(stream, request) }
@@ -305 +301 @@
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
@@ -333,0 +330,10 @@
+ def on_stream_half_close(stream, _request)
+ unless stream.send_buffer.empty?
+ stream.send_buffer.clear
+ stream.data("", end_stream: true)
+ end
+
+ # TODO: omit log line if response already here
+ log(level: 2) { "#{stream.id}: waiting for response..." }
+ end
+
@@ -407,12 +413 @@
- log(level: 2, color: :blue) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :blue) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
@@ -423,12 +418,28 @@
- log(level: 2, color: :magenta) do
- payload =
- case frame[:type]
- when :data
- frame.merge(payload: frame[:payload].bytesize)
- when :headers, :ping
- frame.merge(payload: log_redact_headers(frame[:payload]))
- else
- frame
- end
- "#{frame[:stream]}: #{payload}"
- end
+ log(level: 2, color: :magenta) { "#{frame[:stream]}: #{frame_with_extra_info(frame)}" }
+ end
+
+ def frame_with_extra_info(frame)
+ case frame[:type]
+ when :data
+ frame.merge(payload: frame[:payload].bytesize)
+ when :headers, :ping
+ frame.merge(payload: log_redact_headers(frame[:payload]))
+ when :window_update
+ connection_or_stream = if (id = frame[:stream]).zero?
+ @connection
+ else
+ @streams.each_value.find { |s| s.id == id }
+ end
+ if connection_or_stream
+ frame.merge(
+ local_window: connection_or_stream.local_window,
+ remote_window: connection_or_stream.remote_window,
+ buffered_amount: connection_or_stream.buffered_amount,
+ stream_state: connection_or_stream.state,
+ )
+ else
+ frame
+ end
+ else
+ frame
+ end.merge(connection_state: @connection.state)
lib/httpx/plugins/auth/digest.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:34:18.417991866 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/auth/digest.rb 2026-03-06 03:34:18.463991768 +0000
@@ -11 +11,2 @@
- Error = Class.new(Error)
+ class Error < Error
+ end
lib/httpx/plugins/cookies.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/cookies.rb 2026-03-06 03:34:18.419991862 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/cookies.rb 2026-03-06 03:34:18.464991766 +0000
@@ -10,2 +9,0 @@
- # It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
- #
@@ -48,0 +47,6 @@
+ # factory method to return a Jar to the user, which can then manipulate
+ # externally to the session.
+ def make_jar(*args)
+ Jar.new(*args)
+ end
+
@@ -99 +103 @@
- jar.add(Cookie.new(name, value))
+ jar.set(name, value)
lib/httpx/plugins/cookies/cookie.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:34:18.419991862 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/cookies/cookie.rb 2026-03-06 03:34:18.464991766 +0000
@@ -16,0 +17 @@
+ # assigns a new +path+ to this cookie.
@@ -18,0 +20 @@
+ @for_domain = false
@@ -22 +24 @@
- # See #domain.
+ # assigns a new +domain+ to this cookie.
@@ -39,0 +42,7 @@
+ # checks whether +other+ is the same cookie, i.e. name, value, domain and path are
+ # the same.
+ def ==(other)
+ @name == other.name && @value == other.value &&
+ @path == other.path && @domain == other.domain
+ end
+
@@ -49,0 +59,11 @@
+ def match?(name_or_options)
+ case name_or_options
+ when String
+ @name == name_or_options
+ when Hash, Array
+ name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
+ else
+ false
+ end
+ end
+
@@ -52 +72,7 @@
- return cookie if cookie.is_a?(self)
+ case cookie
+ when self
+ cookie
+ when Array, Hash
+ options = Hash[cookie] #: cookie_attributes
+ super(options[:name], options[:value], options)
+ else
@@ -54 +80,2 @@
- super
+ super
+ end
@@ -87 +114 @@
- def initialize(arg, *attrs)
+ def initialize(arg, value, attrs = nil)
@@ -90,7 +117,3 @@
- if attrs.empty?
- attr_hash = Hash.try_convert(arg)
- else
- @name = arg
- @value, attr_hash = attrs
- attr_hash = Hash.try_convert(attr_hash)
- end
+ @name = arg
+ @value = value
+ attr_hash = Hash.try_convert(attrs)
lib/httpx/plugins/cookies/jar.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:34:18.419991862 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/cookies/jar.rb 2026-03-06 03:34:18.465991764 +0000
@@ -7 +7,6 @@
- # It holds a bunch of cookies.
+ # It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
+ # initialization from parsing `Set-Cookie` HTTP header values.
+ #
+ # It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
+ # by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
+ #
@@ -14,0 +20 @@
+ @mtx = orig.instance_variable_get(:@mtx).dup
@@ -17,0 +24,2 @@
+ # initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
+ # can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
@@ -18,0 +27 @@
+ @mtx = Thread::Mutex.new
@@ -34,0 +44 @@
+ # parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
@@ -37 +47 @@
- add(Cookie.new(name, value, attrs))
+ set(Cookie.new(name, value, attrs))
@@ -40,0 +51,42 @@
+ # returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get(name_or_options)
+ each.find { |ck| ck.match?(name_or_options) }
+ end
+
+ # returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
+ # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
+ def get_all(name_or_options)
+ each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
+ end
+
+ # when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
+ # it creates a cookie with it and the value-or-attributes passed to +value_or_options+.
+
+ # optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
+ def set(name, value_or_options = nil)
+ cookie = case name
+ when Cookie
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ name
+ when Array, Hash
+ raise ArgumentError, "there should not be a second argument" if value_or_options
+
+ Cookie.new(name)
+ else
+ raise ArgumentError, "the second argument is required" unless value_or_options
+
+ Cookie.new(name, value_or_options)
+ end
+
+ synchronize do
+ # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
+ # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
+ @cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }
+
+ @cookies << cookie
+ end
+ end
+
+ # @deprecated
@@ -41,0 +94 @@
+ warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
@@ -43 +95,0 @@
-
@@ -44,0 +97,2 @@
+ set(c)
+ end
@@ -46,5 +100,13 @@
- # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
- # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
- @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
-
- @cookies << c
+ # deletes all cookies in the store which match either the name (when String) or all attributes (when a Hash
+ # or array of tuples) passed to +name_or_options+.
+ #
+ # alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
+ def delete(name_or_options)
+ synchronize do
+ case name_or_options
+ when Cookie
+ @cookies.delete(name_or_options)
+ else
+ @cookies.delete_if { |ck| ck.match?(name_or_options) }
+ end
+ end
@@ -52,0 +115 @@
+ # returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
@@ -56,0 +120,2 @@
+ # enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
+ # only yield cookies which match its domain and path.
@@ -60 +125 @@
- return @cookies.each(&blk) unless uri
+ return synchronize { @cookies.each(&blk) } unless uri
@@ -65,6 +130,8 @@
- @cookies.delete_if do |cookie|
- if cookie.expired?(now)
- true
- else
- yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
- false
+ synchronize do
+ @cookies.delete_if do |cookie|
+ if cookie.expired?(now)
+ true
+ else
+ yield cookie if cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
+ false
+ end
@@ -76 +143 @@
- cookies_dup = dup
+ jar_dup = dup
@@ -88 +155 @@
- cookies_dup.add(cookie)
+ jar_dup.set(cookie)
@@ -91 +158,9 @@
- cookies_dup
+ jar_dup
+ end
+
+ private
+
+ def synchronize(&block)
+ return yield if @mtx.owned?
+
+ @mtx.synchronize(&block)
lib/httpx/plugins/expect.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/expect.rb 2026-03-06 03:34:18.419991862 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/expect.rb 2026-03-06 03:34:18.465991764 +0000
@@ -11,0 +12,20 @@
+ NOEXPECT_STORE_MUTEX = Thread::Mutex.new
+
+ class Store
+ def initialize
+ @store = []
+ @mutex = Thread::Mutex.new
+ end
+
+ def include?(host)
+ @mutex.synchronize { @store.include?(host) }
+ end
+
+ def add(host)
+ @mutex.synchronize { @store << host }
+ end
+
+ def delete(host)
+ @mutex.synchronize { @store.delete(host) }
+ end
+ end
@@ -15 +35,5 @@
- @no_expect_store ||= []
+ return Ractor.store_if_absent(:httpx_no_expect_store) { Store.new } if Utils.in_ractor?
+
+ @no_expect_store ||= NOEXPECT_STORE_MUTEX.synchronize do
+ @no_expect_store || Store.new
+ end
@@ -92 +116 @@
- Expect.no_expect_store << request.origin
+ Expect.no_expect_store.add(request.origin)
lib/httpx/plugins/fiber_concurrency.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:34:18.419991862 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/fiber_concurrency.rb 2026-03-06 03:34:18.465991764 +0000
@@ -163,3 +163 @@
- module NativeResolverMethods
- private
-
+ module ResolverNativeMethods
@@ -175 +173 @@
- module SystemResolverMethods
+ module ResolverSystemMethods
lib/httpx/plugins/follow_redirects.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:34:18.420991860 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/follow_redirects.rb 2026-03-06 03:34:18.465991764 +0000
@@ -4 +4,3 @@
- InsecureRedirectError = Class.new(Error)
+ class InsecureRedirectError < Error
+ end
+
lib/httpx/plugins/rate_limiter.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:34:18.422991855 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/rate_limiter.rb 2026-03-06 03:34:18.467991760 +0000
@@ -19,19 +19 @@
- klass.plugin(:retries, retry_after: method(:retry_after_rate_limit))
- end
-
- # Servers send the "Retry-After" header field to indicate how long the
- # user agent ought to wait before making a follow-up request. When
- # sent with a 503 (Service Unavailable) response, Retry-After indicates
- # how long the service is expected to be unavailable to the client.
- # When sent with any 3xx (Redirection) response, Retry-After indicates
- # the minimum time that the user agent is asked to wait before issuing
- # the redirected request.
- #
- def retry_after_rate_limit(_, response)
- return unless response.is_a?(Response)
-
- retry_after = response.headers["retry-after"]
-
- return unless retry_after
-
- Utils.parse_retry_after(retry_after)
+ klass.plugin(:retries)
@@ -53,0 +36,18 @@
+ end
+
+ # Servers send the "Retry-After" header field to indicate how long the
+ # user agent ought to wait before making a follow-up request. When
+ # sent with a 503 (Service Unavailable) response, Retry-After indicates
+ # how long the service is expected to be unavailable to the client.
+ # When sent with any 3xx (Redirection) response, Retry-After indicates
+ # the minimum time that the user agent is asked to wait before issuing
+ # the redirected request.
+ #
+ def when_to_retry(_, response, options)
+ return super unless response.is_a?(Response)
+
+ retry_after = response.headers["retry-after"]
+
+ return super unless retry_after
+
+ Utils.parse_retry_after(retry_after)
lib/httpx/plugins/retries.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/retries.rb 2026-03-06 03:34:18.423991853 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/retries.rb 2026-03-06 03:34:18.468991758 +0000
@@ -151,4 +151 @@
- retry_after = options.retry_after
- retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
-
- if retry_after
+ if (retry_after = when_to_retry(request, response, options))
@@ -203,0 +201,6 @@
+ def when_to_retry(request, response, options)
+ retry_after = options.retry_after
+ retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
+ retry_after
+ end
+
@@ -239,0 +243 @@
+ @partial_response = nil
@@ -243 +247 @@
- if @partial_response
+ if (partial_response = @partial_response)
@@ -245 +249 @@
- response.from_partial_response(@partial_response)
+ response.from_partial_response(partial_response)
@@ -247 +251 @@
- @partial_response.close
+ partial_response.close
lib/httpx/plugins/ssrf_filter.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:34:18.423991853 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/ssrf_filter.rb 2026-03-06 03:34:18.468991758 +0000
@@ -108,0 +109 @@
+ request.response = response
lib/httpx/plugins/stream_bidi.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:34:18.423991853 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/plugins/stream_bidi.rb 2026-03-06 03:34:18.468991758 +0000
@@ -191,0 +192,4 @@
+ def force_close(*)
+ terminate
+ end
+
@@ -203,0 +208,2 @@
+
+ alias_method :on_io_error, :on_error
lib/httpx/request.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/request.rb 2026-03-06 03:34:18.424991851 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/request.rb 2026-03-06 03:34:18.469991756 +0000
@@ -94 +94 @@
- @response = @peer_address = @informational_status = nil
+ @response = @drainer = @peer_address = @informational_status = nil
lib/httpx/resolver/resolver.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/resolver/resolver.rb 2026-03-06 03:34:18.426991847 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/resolver/resolver.rb 2026-03-06 03:34:18.471991751 +0000
@@ -121,0 +122,5 @@
+ def on_io_error(e)
+ on_error(e)
+ force_close(true)
+ end
+
lib/httpx/selector.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/selector.rb 2026-03-06 03:34:18.426991847 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/selector.rb 2026-03-06 03:34:18.471991751 +0000
@@ -207,2 +207 @@
- sel.on_error(e)
- sel.force_close(true)
+ sel.on_io_error(e)
@@ -252,2 +251,3 @@
- io.on_error(e)
- io.force_close(true)
+ io.on_io_error(e)
+
+ return
lib/httpx/session.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/session.rb 2026-03-06 03:34:18.427991845 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/session.rb 2026-03-06 03:34:18.472991749 +0000
@@ -142,0 +143 @@
+ # do not check-in connections only created for Happy Eyeballs
@@ -145 +146 @@
- return if @closing && connection.state == :closed
+ return if @closing && connection.state == :closed && !connection.used?
@@ -180 +180,0 @@
- log(level: 2) { "finding connection for #{request_uri}..." }
@@ -183 +183 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
@@ -189 +189 @@
- connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
+ log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in pool##{@pool.object_id}" }
@@ -243 +243 @@
- log(level: 2) { "response fetched" }
+ log(level: 2) { "response##{response.object_id} fetched" }
@@ -251,0 +252 @@
+ log(level: 2) { "finding connection for request##{request.object_id}..." }
lib/httpx/version.rb
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/lib/httpx/version.rb 2026-03-06 03:34:18.429991841 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/lib/httpx/version.rb 2026-03-06 03:34:18.474991745 +0000
@@ -4 +4 @@
- VERSION = "1.7.2"
+ VERSION = "1.7.3"
sig/chainable.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/chainable.rbs 2026-03-06 03:34:18.429991841 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/chainable.rbs 2026-03-06 03:34:18.474991745 +0000
@@ -32 +32 @@
- | (:rate_limiter, ?options) -> Session
+ | (:rate_limiter, ?options) -> Plugins::sessionRateLimiter
sig/connection.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/connection.rbs 2026-03-06 03:34:18.429991841 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/connection.rbs 2026-03-06 03:34:18.474991745 +0000
@@ -39,0 +40 @@
+ @max_concurrent_requests: Integer?
@@ -120,0 +122,2 @@
+
+ def on_io_error: (IOError error) -> void
sig/connection/http2.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/connection/http2.rbs 2026-03-06 03:34:18.430991839 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/connection/http2.rbs 2026-03-06 03:34:18.475991743 +0000
@@ -81,0 +82,2 @@
+ def on_stream_half_close: (::HTTP2::Stream stream, Request request) -> void
+
@@ -90 +92,3 @@
- def on_frame_sent: (::HTTP2::frame) -> void
+ def on_frame_sent: (::HTTP2::frame frame) -> void
+
+ def frame_with_extra_info: (::HTTP2::frame frame) -> Hash[Symbol, untyped]
@@ -92 +96 @@
- def on_frame_received: (::HTTP2::frame) -> void
+ def on_frame_received: (::HTTP2::frame frame) -> void
@@ -94 +98 @@
- def on_altsvc: (String origin, ::HTTP2::frame) -> void
+ def on_altsvc: (String origin, ::HTTP2::frame frame) -> void
@@ -96 +100 @@
- def on_promise: (::HTTP2::Stream) -> void
+ def on_promise: (::HTTP2::Stream stream) -> void
sig/plugins/cookies.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/cookies.rbs 2026-03-06 03:34:18.433991832 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/cookies.rbs 2026-03-06 03:34:18.478991736 +0000
@@ -17,0 +18,2 @@
+
+ def make_jar: (*untyped) -> Jar
sig/plugins/cookies/cookie.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/cookies/cookie.rbs 2026-03-06 03:34:18.433991832 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/cookies/cookie.rbs 2026-03-06 03:34:18.478991736 +0000
@@ -33,0 +34,2 @@
+ def match?: (String | cookie_attributes name_or_options) -> bool
+
@@ -44,2 +46 @@
- def initialize: (cookie_attributes) -> untyped
- | (_ToS, _ToS, ?cookie_attributes) -> untyped
+ def initialize: (_ToS name, _ToS value, ?cookie_attributes) -> void
sig/plugins/cookies/jar.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/cookies/jar.rbs 2026-03-06 03:34:18.433991832 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/cookies/jar.rbs 2026-03-06 03:34:18.478991736 +0000
@@ -8,0 +9 @@
+ @mtx: Thread::Mutex
@@ -11,0 +13,6 @@
+ def get: (String | cookie_attributes name_or_options) -> Cookie?
+
+ def get_all: (String | cookie_attributes name_or_options) -> Array[Cookie]
+
+ def set: (_ToS | Cookie name, ?(cookie_attributes | _ToS) value_or_options) -> void
+
@@ -13,0 +21,2 @@
+ def delete: (String | Cookie | cookie_attributes name_or_options) -> void
+
@@ -23,0 +33,2 @@
+
+ def synchronize: [T] { () -> T } -> T
sig/plugins/expect.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/expect.rbs 2026-03-06 03:34:18.434991830 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/expect.rbs 2026-03-06 03:34:18.479991734 +0000
@@ -4,0 +5,17 @@
+ NOEXPECT_STORE_MUTEX: Thread::Mutex
+
+ self.@no_expect_store: Store
+ def self.no_expect_store: () -> Store
+
+ def self.extra_options: (Options) -> (Options & _ExpectOptions)
+
+ class Store
+ @store: Array[String]
+ @mutex: Thread::Mutex
+
+ def include?: (String host) -> bool
+
+ def add: (String host) -> void
+
+ def delete: (String host) -> void
+ end
@@ -11,2 +27,0 @@
-
- def self.extra_options: (Options) -> (Options & _ExpectOptions)
sig/plugins/proxy/socks4.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/proxy/socks4.rbs 2026-03-06 03:34:18.435991828 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/proxy/socks4.rbs 2026-03-06 03:34:18.480991732 +0000
@@ -7,0 +8,4 @@
+ VERSION: Integer
+ CONNECT: Integer
+ GRANTED: Integer
+ PROTOCOLS: Array[String]
sig/plugins/rate_limiter.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/rate_limiter.rbs 2026-03-06 03:34:18.435991828 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/rate_limiter.rbs 2026-03-06 03:34:18.481991730 +0000
@@ -8,2 +7,0 @@
- def self.retry_after_rate_limit: (untyped, response) -> Numeric?
-
@@ -13,0 +12,2 @@
+
+ type sessionRateLimiter = Session & RateLimiter::InstanceMethods
sig/plugins/response_cache.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/response_cache.rbs 2026-03-06 03:34:18.436991826 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/response_cache.rbs 2026-03-06 03:34:18.481991730 +0000
@@ -54 +54 @@
- @cache: bool
+ @cached: bool
@@ -69 +69 @@
- def cache_control: () -> Array[String]?
+ %a{pure} def cache_control: () -> Array[String]?
@@ -71 +71 @@
- def vary: () -> Array[String]?
+ %a{pure} def vary: () -> Array[String]?
sig/plugins/retries.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/plugins/retries.rbs 2026-03-06 03:34:18.436991826 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/plugins/retries.rbs 2026-03-06 03:34:18.481991730 +0000
@@ -11 +11 @@
- def self?.retry_after_polynomial_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_polynomial_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -13 +13 @@
- def self?.retry_after_exponential_backoff: (retriesRequest request, response response) -> Numeric
+ def self?.retry_after_exponential_backoff: (retriesRequest request, retriesResponse response) -> Numeric
@@ -16 +16 @@
- def call: (response response) -> bool?
+ def call: (retriesResponse response) -> bool?
@@ -20 +20 @@
- def retry_after: () -> (^(retriesRequest request, response response) -> Numeric | Numeric)?
+ def retry_after: () -> (^(retriesRequest request, retriesResponse response) -> Numeric | Numeric)?
@@ -38 +38 @@
- def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> (retriesResponse | ErrorResponse)?
+ def fetch_response: (retriesRequest request, Selector selector, retriesOptions options) -> retriesResponse?
@@ -40 +40 @@
- def retryable_request?: (retriesRequest request, response response, retriesOptions options) -> boolish
+ def retryable_request?: (retriesRequest request, retriesResponse response, retriesOptions options) -> boolish
@@ -42 +42 @@
- def retryable_response?: (response response, retriesOptions options) -> boolish
+ def retryable_response?: (retriesResponse response, retriesOptions options) -> boolish
@@ -46 +46 @@
- def try_partial_retry: (retriesRequest request, (retriesResponse | ErrorResponse) response) -> void
+ def try_partial_retry: (retriesRequest request, retriesResponse response) -> void
@@ -48 +48,3 @@
- def prepare_to_retry: (Request & RequestMethods request, response response) -> void
+ def prepare_to_retry: (Request & RequestMethods request, retriesResponse response) -> void
+
+ def when_to_retry: (Request & RequestMethods request, retriesResponse response, retriesOptions options) -> void
@@ -56 +58 @@
- attr_writer partial_response: Response?
+ attr_writer partial_response: retriesResponse?
@@ -58 +60 @@
- def response=: (retriesResponse | ErrorResponse response) -> void
+ def response=: (retriesResponse response) -> void
@@ -62 +64 @@
- def from_partial_response: (Response response) -> void
+ def from_partial_response: (retriesHTTPResponse response) -> void
@@ -69 +71,3 @@
- type retriesResponse = Response & ResponseMethods
+ type retriesHTTPResponse = Response & ResponseMethods
+
+ type retriesResponse = retriesHTTPResponse | ErrorResponse
sig/request.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/request.rbs 2026-03-06 03:34:18.438991822 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/request.rbs 2026-03-06 03:34:18.482991728 +0000
@@ -8,0 +9 @@
+ ALLOWED_URI_SCHEMES: Array[String]
sig/resolver/native.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/resolver/native.rbs 2026-03-06 03:34:18.439991819 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/resolver/native.rbs 2026-03-06 03:34:18.484991723 +0000
@@ -39,0 +40,2 @@
+ def on_io_error: (IOError error) -> void
+
sig/resolver/resolver.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/resolver/resolver.rbs 2026-03-06 03:34:18.439991819 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/resolver/resolver.rbs 2026-03-06 03:34:18.484991723 +0000
@@ -30,2 +29,0 @@
- def force_close: (*untyped args) -> void
-
@@ -62,0 +61,2 @@
+
+ def disconnect: () -> void
sig/selector.rbs
--- /tmp/d20260306-2858-z6ydi2/httpx-1.7.2/sig/selector.rbs 2026-03-06 03:34:18.440991817 +0000
+++ /tmp/d20260306-2858-z6ydi2/httpx-1.7.3/sig/selector.rbs 2026-03-06 03:34:18.484991723 +0000
@@ -15,0 +16,4 @@
+
+ def on_io_error: (IOError error) -> void
+
+ def force_close: (?bool delete_pending) -> void |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps httpx from 1.7.2 to 1.7.3.
Commits
7827cf5bump version to 1.7.3e30807bcookie jar: synchronize access to internal store, so jar is usable across thr...2d33e09Merge branch 'delete-cookies' into 'master'4da90e1better assertion failed message in pool test24ff45dcookies: add #make_jar to sessionb9c984dcookies: make Jar follow the CookieStore API3d50644Add cookie deletion7a1f674Merge branch 'gh-126' into 'master'955e757init@drainerin the initializerd617c4bhttp2: on stream half-close, thrash buffered data on the parserDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)