http 5.3.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6053cab7210860d3464e9441746186b46c7ac1dc5716ea59cc69dff0d4ef460
4
- data.tar.gz: 49f6d6c66a674e792208f1c17fff2c5dd8fdda330c6bdbabbaddec1f69eebac8
3
+ metadata.gz: 1567381d914309e80e272367711f0e26f48f0123730078e1cde3f6f1eb19447e
4
+ data.tar.gz: f8b6826b7a57470157a9caa3524bd89b77e3274ae69736f3983ad1e512ae8306
5
5
  SHA512:
6
- metadata.gz: 11585d59c45a6e762e6df8792e30107ec1011440ddc4597ad63f4ab5951f0e5c3fd801153bdb32bf74cae90c8ca716368003b3fb59cd98d8dc28c595899be177
7
- data.tar.gz: 481d4f96d65e80226f505b93ff0da954ee4d0c3ee443d6dd4b72711cd0b44ae95c150eeb82d24442a34105dbb75013cb689f4b2ee0a2272d8320b473d91fc72d
6
+ metadata.gz: 698b3d83287e492df7aced8106af2cd861b196c0691a484a007f8f3d56fff8bdaa278f3b3c860b851b4ec51e18b8f63d00b29242d8e1c7aa79ef590abc5f7ab3
7
+ data.tar.gz: b853190dabe880ee5232536f716b493e095f888c53a1fef3e7a598a05fce8864444096113de10f2d7e7ded74b08737bd2f50ac19788dd3c8a2b86e29bd6b6e78
data/CHANGELOG.md CHANGED
@@ -5,63 +5,263 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
9
-
10
-
11
- ## [5.3.1] - 2025-06-09
8
+ ## [6.0.0] - 2026-03-16
12
9
 
13
10
  ### Changed
14
11
 
15
- - Revert switch to the native llhttp on MRI, as it's not compatible with
16
- standalone bundles
17
- ([#802](https://github.com/httprb/http/issues/802))
12
+ - Merged `http-form_data` gem into the main `http` gem. The `HTTP::FormData`
13
+ module (including `Part`, `File`, `Multipart`, `Urlencoded`, and `CompositeIO`)
14
+ is now shipped directly with `http` instead of being a separate dependency.
15
+ The public API is unchanged.
18
16
 
17
+ ### Fixed
19
18
 
20
- ## [5.3.0] - 2025-06-09
19
+ - `Inflater` no longer raises `Zlib::BufError` when a response declares
20
+ `Content-Encoding: gzip` (or deflate) but the body is not valid compressed
21
+ data. This commonly occurred when following redirects with `auto_inflate`
22
+ enabled, because the redirect response had a `Content-Encoding` header but a
23
+ non-compressed body. ([#621])
24
+ - Persistent connections now auto-flush unread response bodies before sending
25
+ the next request, instead of raising `StateError`. Bodies up to 1 MiB are
26
+ drained transparently; larger bodies cause the connection to close and reopen.
27
+ This prevents the silent body clobbering described in [#371], where an unread
28
+ response body would return `""` after a subsequent request. ([#371])
29
+ - `Response#content_length` now handles duplicate `Content-Length` headers per
30
+ RFC 7230 Section 3.3.2. When all values are identical, they are collapsed into
31
+ a single valid value. When values conflict, `nil` is returned instead of
32
+ raising `TypeError`. ([#566])
33
+ - HTTP 1xx informational responses (e.g. `100 Continue`) are now transparently
34
+ skipped, returning the final response. This was a regression introduced when
35
+ the parser was migrated from http-parser to llhttp. ([#667])
36
+ - Redirect loop detection now considers cookies, so a redirect back to the
37
+ same URL with different cookies is no longer falsely detected as an endless
38
+ loop. Fixes cookie-dependent redirect flows where a server sets a cookie on
39
+ one hop and expects it on the next. ([#544])
40
+ - Per-operation timeouts (`HTTP.timeout(read: n, write: n, connect: n)`) no
41
+ longer default unspecified values to 0.25 seconds. Omitted timeouts now mean
42
+ no timeout for that operation, matching the behavior when no timeout is
43
+ configured at all. ([#579])
44
+ - Per-operation timeout handler now correctly handles `:wait_writable` from
45
+ `read_nonblock` and `:wait_readable` from `write_nonblock` on SSL sockets
46
+ during TLS renegotiation. Previously these symbols were returned as data
47
+ instead of being waited on. ([#358])
48
+ - Persistent sessions now follow cross-origin redirects instead of raising
49
+ `StateError`. `HTTP.persistent` returns an `HTTP::Session` that pools one
50
+ `HTTP::Client` per origin, so redirects to a different domain transparently
51
+ open (and reuse) a separate persistent connection. Cookie management is
52
+ preserved across all hops. ([#557])
53
+ - Chaining configuration methods (`.headers`, `.auth`, `.cookies`, etc.) on a
54
+ persistent session no longer breaks connection reuse. Child sessions created
55
+ by chaining now share the parent's connection pool, so
56
+ `HTTP.persistent(host).headers(...).get(path)` reuses the same underlying
57
+ TCP connection across calls. ([#372])
21
58
 
22
- ### Added
59
+ ### Changed
23
60
 
24
- - (backported) Add .retriable feature to Http
25
- - (backported) Add more specific ConnectionError classes
26
- - (backported) New feature: RaiseError
61
+ - **BREAKING** `HTTP.persistent` now returns an `HTTP::Session` instead of an
62
+ `HTTP::Client`. The session pools persistent clients per origin and exposes
63
+ the same chainable API (`get`, `post`, `headers`, `follow`, etc.) plus a
64
+ `close` method that shuts down all pooled connections. Code that called
65
+ `HTTP::Client`-only methods on the return value will need updating. ([#557])
66
+ - **BREAKING** Convert options hash parameters to explicit keyword arguments
67
+ across the public API. Methods like `HTTP.get(url, body: "data")` continue to
68
+ work, but passing an explicit hash (e.g., `HTTP.get(url, {body: "data"})`) is
69
+ no longer supported, and unrecognized keyword arguments now raise
70
+ `ArgumentError`. Affected methods: all HTTP verb methods (`get`, `post`,
71
+ etc.), `request`, `follow`, `retriable`, `URI.new`, `Request.new`,
72
+ `Response.new`, `Redirector.new`, `Retriable::Performer.new`,
73
+ `Retriable::DelayCalculator.new`, and `Timeout::Null.new` (and subclasses).
74
+ `HTTP::URI.new` also no longer accepts `Addressable::URI` objects.
75
+ - **BREAKING** `addressable` is no longer a runtime dependency. It is now
76
+ lazy-loaded only when parsing non-ASCII (IRI) URIs or normalizing
77
+ internationalized hostnames. Install the `addressable` gem if you need
78
+ non-ASCII URI support. ASCII-only URIs use Ruby's stdlib `URI` parser
79
+ exclusively.
80
+ - **BREAKING** Extract request building into `HTTP::Request::Builder`. The
81
+ `build_request` method has been removed from `Client`, `Session`, and the
82
+ top-level `HTTP` module. Use `HTTP::Request::Builder.new(options).build(verb, uri)`
83
+ to construct requests without executing them.
27
84
 
28
- ### Changed
85
+ ### Added
29
86
 
30
- - (backported) Drop depenency on base64
31
- - (backported) Cache header normalization to reduce object allocation
32
- - (backported) Use native llhttp on MRI
87
+ - Block form for verb methods and `request` that auto-closes the connection
88
+ after the block returns. `HTTP.get(url) { |response| response.status }` yields
89
+ the response, closes the underlying connection, and returns the block's value.
90
+ Works with all verb methods and chained options. ([#270])
91
+ - HTTP caching feature (`HTTP.use(:caching)`) that stores and reuses responses
92
+ according to RFC 7234. Supports `Cache-Control` (`max-age`, `no-cache`,
93
+ `no-store`), `Expires`, `ETag` / `If-None-Match`, and
94
+ `Last-Modified` / `If-Modified-Since` for freshness checks and conditional
95
+ revalidation. Ships with a default in-memory store; custom stores can be
96
+ passed via `store:` option. Only GET and HEAD responses are cached. ([#223])
97
+ - `HTTP.digest_auth(user:, pass:)` for HTTP Digest Authentication (RFC 2617 /
98
+ RFC 7616). Automatically handles 401 challenges with digest credentials,
99
+ supporting MD5, SHA-256, MD5-sess, and SHA-256-sess algorithms with
100
+ quality-of-protection negotiation. Works as a chainable feature:
101
+ `HTTP.digest_auth(user: "admin", pass: "secret").get(url)` ([#448])
102
+ - Happy Eyeballs (RFC 8305) support via Ruby 3.4's native `TCPSocket`
103
+ implementation. Connection attempts now try multiple addresses (IPv6 and
104
+ IPv4) concurrently, improving reliability on dual-stack networks. Connect
105
+ timeouts are passed natively to `TCPSocket` instead of using
106
+ `Timeout.timeout`, avoiding `Thread.raise` interference with the Happy
107
+ Eyeballs state machine. ([#739])
108
+ - `HTTP.base_uri` for setting a base URI that resolves relative request paths
109
+ per RFC 3986. Supports chaining (`HTTP.base_uri("https://api.example.com/v1")
110
+ .get("users")`), and integrates with `persistent` connections by deriving the
111
+ host when omitted ([#519], [#512], [#493])
112
+ - `Request::Body#loggable?` and `Response::Body#loggable?` predicates, and a
113
+ `binary_formatter` option for the logging feature. Binary bodies
114
+ (IO/Enumerable request sources, binary-encoded request strings, and
115
+ binary-encoded responses) are now formatted instead of dumped raw,
116
+ preventing unreadable log output when transferring files like images or
117
+ audio. Available formatters: `:stats` (default, logs byte count),
118
+ `:base64` (logs base64-encoded content), or a custom `Proc`. Invalid
119
+ formatter values raise `ArgumentError` ([#784])
120
+ - `Feature#on_request` and `Feature#around_request` lifecycle hooks, called
121
+ before/around each request attempt (including retries), for per-attempt side
122
+ effects like instrumentation spans and circuit breakers ([#826])
123
+ - Pattern matching support (`deconstruct_keys`) for Response, Response::Status,
124
+ Headers, ContentType, and URI ([#642])
125
+ - Combined global and per-operation timeouts: global and per-operation timeouts
126
+ are no longer mutually exclusive. Use
127
+ `HTTP.timeout(global: 60, read: 30, write: 30, connect: 5)` to set both a
128
+ global request timeout and individual operation limits ([#773])
33
129
 
130
+ ### Fixed
34
131
 
35
- ## [5.2.0] - 2024-02-05
132
+ - `HTTP::URI.form_encode` now encodes newlines as `%0A` instead of
133
+ `%0D%0A` ([#449])
134
+ - Thread-safety: `Headers::Normalizer` cache is now per-thread via
135
+ `Thread.current`, eliminating a potential race condition when multiple
136
+ threads share a normalizer instance
137
+ - Instrumentation feature now correctly starts a new span for each retry
138
+ attempt, fixing `NoMethodError` with `ActiveSupport::Notifications` when
139
+ using `.retriable` with the instrumentation feature ([#826])
140
+ - Raise `HTTP::URI::InvalidError` for malformed or schemeless URIs and
141
+ `ArgumentError` for nil or empty URIs, instead of confusing
142
+ `UnsupportedSchemeError` or `Addressable::URI::InvalidURIError` ([#565])
143
+ - Strip `Authorization` and `Cookie` headers when following redirects to a
144
+ different origin (scheme, host, or port) to prevent credential leakage
145
+ ([#516], [#770])
146
+ - AutoInflate now preserves the response charset encoding instead of
147
+ defaulting to `Encoding::BINARY` ([#535])
148
+ - `LocalJumpError` when using instrumentation with instrumenters that
149
+ unconditionally yield in `#instrument` (e.g., `ActiveSupport::Notifications`)
150
+ ([#673])
151
+ - Logging feature no longer eagerly consumes the response body at debug level;
152
+ body chunks are now logged as they are streamed, preserving
153
+ `response.body.each` ([#785])
36
154
 
37
- ### Added
155
+ ### Removed
38
156
 
39
- - Add `Connection#finished_request?`
40
- ([#743](https://github.com/httprb/http/pull/743))
41
- - Add `Instrumentation#on_error`
42
- ([#746](https://github.com/httprb/http/pull/746))
43
- - Add `base64` dependency (suppresses warnings on Ruby 3.0)
44
- ([#759](https://github.com/httprb/http/pull/759))
45
- - Add `PURGE` HTTP verb
46
- ([#757](https://github.com/httprb/http/pull/757))
47
- - Add Ruby-3.3 support
157
+ - `HTTP::URI` setter methods (`scheme=`, `user=`, `password=`, `authority=`,
158
+ `origin=`, `port=`, `request_uri=`, `fragment=`) and normalized accessors
159
+ (`normalized_user`, `normalized_password`, `normalized_port`,
160
+ `normalized_path`, `normalized_query`) that were delegated to
161
+ `Addressable::URI` but never used internally
162
+ - `HTTP::URI#origin` is no longer delegated to `Addressable::URI`. The new
163
+ implementation follows RFC 6454, normalizing scheme and host to lowercase
164
+ and excluding user info from the origin string
165
+ - `HTTP::URI#request_uri` is no longer delegated to `Addressable::URI`
166
+ - `HTTP::URI#omit` is no longer delegated to `Addressable::URI` and now
167
+ returns `HTTP::URI` instead of `Addressable::URI` ([#491])
168
+ - `HTTP::URI#query_values` and `HTTP::URI#query_values=` delegations to
169
+ `Addressable::URI`. Query parameter merging now uses stdlib
170
+ `URI.decode_www_form`/`URI.encode_www_form`
171
+ - `HTTP::URI` delegations for `normalized_scheme`, `normalized_authority`,
172
+ `normalized_fragment`, and `authority` to `Addressable::URI`. The URI
173
+ normalizer now inlines these operations directly
174
+ - `HTTP::URI#join` is no longer delegated to `Addressable::URI` and now
175
+ returns `HTTP::URI` instead of `Addressable::URI`. Uses stdlib `URI.join`
176
+ with automatic percent-encoding of non-ASCII characters ([#491])
177
+ - `HTTP::URI.form_encode` no longer delegates to `Addressable::URI`. Uses
178
+ stdlib `URI.encode_www_form` instead
48
179
 
49
180
  ### Changed
50
181
 
51
- - **BREAKING** Process features in reverse order
52
- ([#766](https://github.com/httprb/http/pull/766))
53
- - **BREAKING** Downcase Content-Type charset name
54
- ([#753](https://github.com/httprb/http/pull/753))
55
- - **BREAKING** Make URI normalization more conservative
56
- ([#758](https://github.com/httprb/http/pull/758))
182
+ - **BREAKING** `HTTP::Response::Status` no longer inherits from `Delegator`.
183
+ It now uses `Comparable` and `Forwardable` instead, providing `to_i`,
184
+ `to_int`, and `<=>` for numeric comparisons and range matching. Code that
185
+ called `__getobj__`/`__setobj__` or relied on implicit delegation of
186
+ arbitrary `Integer` methods (e.g., `status.even?`) will need to be updated
187
+ to use `status.code` instead
188
+ - **BREAKING** Chainable option methods (`.headers`, `.timeout`, `.cookies`,
189
+ `.auth`, `.follow`, `.via`, `.use`, `.encoding`, `.nodelay`, `.basic_auth`,
190
+ `.accept`) now return a thread-safe `HTTP::Session` instead of `HTTP::Client`.
191
+ `Session` creates a new `Client` for each request, making it safe to share a
192
+ configured session across threads. `HTTP.persistent` still returns
193
+ `HTTP::Client` since persistent connections require mutable state. Code that
194
+ calls HTTP verb methods (`.get`, `.post`, etc.) or accesses `.default_options`
195
+ is unaffected. Code that checks `is_a?(HTTP::Client)` on the return value of
196
+ chainable methods will need to be updated to check for `HTTP::Session`
197
+ - **BREAKING** `.retriable` now returns `HTTP::Session` instead of
198
+ `HTTP::Retriable::Client`. Retry is a session-level option: it flows through
199
+ `HTTP::Options` into `HTTP::Client#perform`, eliminating the need for
200
+ separate `Retriable::Client` and `Retriable::Session` classes
201
+ - Improved error message when request body size cannot be determined to suggest
202
+ setting `Content-Length` explicitly or using chunked `Transfer-Encoding` ([#560])
203
+ - **BREAKING** `Connection#readpartial` now raises `EOFError` instead of
204
+ returning `nil` at end-of-stream, and supports an `outbuf` parameter,
205
+ conforming to the `IO#readpartial` API. `Body#readpartial` and
206
+ `Inflater#readpartial` also raise `EOFError` ([#618])
207
+ - **BREAKING** Stricter timeout options parsing: `.timeout()` with a Hash now
208
+ rejects unknown keys, non-numeric values, string keys, and empty hashes ([#754])
209
+ - Bumped min llhttp dependency version
210
+ - **BREAKING** Handle responses in the reverse order from the requests ([#776])
211
+ - **BREAKING** `Response#cookies` now returns `Array<HTTP::Cookie>` instead of
212
+ `HTTP::CookieJar`. The `cookies` option has been removed from `Options`;
213
+ `Chainable#cookies` now sets the `Cookie` header directly with no implicit
214
+ merging — the last `.cookies()` call wins ([#536])
215
+ - Cookie jar management during redirects moved from `Redirector` to `Session`.
216
+ `Redirector` is now a pure redirect-following engine with no cookie
217
+ awareness; `Session#request` manages cookies across redirect hops
218
+ - **BREAKING** `HTTP::Options.new`, `HTTP::Client.new`, and `HTTP::Session.new`
219
+ now accept keyword arguments instead of an options hash. For example,
220
+ `HTTP::Options.new(response: :body)` continues to work, but
221
+ `HTTP::Options.new({response: :body})` must be updated to
222
+ `HTTP::Options.new(**options)`. Invalid option names now raise
223
+ `ArgumentError` automatically ([#447])
57
224
 
58
- ### Fixed
225
+ ### Removed
59
226
 
60
- - Close sockets on initialize failure
61
- ([#762](https://github.com/httprb/http/pull/762))
62
- - Prevent CRLF injection due to broken URL normalizer
63
- ([#765](https://github.com/httprb/http/pull/765))
227
+ - **BREAKING** Drop Ruby 2.x support
228
+ - **BREAKING** Remove `Headers::Mixin` and the `[]`/`[]=` delegators on
229
+ `Request` and `Response`. Use `request.headers["..."]` and
230
+ `response.headers["..."]` instead ([#537])
64
231
 
65
- [unreleased]: https://github.com/httprb/http/compare/v5.3.0...5-x-stable
66
- [5.3.0]: https://github.com/httprb/http/compare/v5.2.0...v5.3.0
67
- [5.2.0]: https://github.com/httprb/http/compare/v5.1.1...v5.2.0
232
+ [#270]: https://github.com/httprb/http/issues/270
233
+ [#223]: https://github.com/httprb/http/issues/223
234
+ [#358]: https://github.com/httprb/http/issues/358
235
+ [#371]: https://github.com/httprb/http/issues/371
236
+ [#372]: https://github.com/httprb/http/issues/372
237
+ [#447]: https://github.com/httprb/http/issues/447
238
+ [#448]: https://github.com/httprb/http/issues/448
239
+ [#449]: https://github.com/httprb/http/issues/449
240
+ [#491]: https://github.com/httprb/http/issues/491
241
+ [#493]: https://github.com/httprb/http/pull/493
242
+ [#512]: https://github.com/httprb/http/issues/512
243
+ [#516]: https://github.com/httprb/http/issues/516
244
+ [#519]: https://github.com/httprb/http/issues/519
245
+ [#535]: https://github.com/httprb/http/issues/535
246
+ [#536]: https://github.com/httprb/http/issues/536
247
+ [#537]: https://github.com/httprb/http/issues/537
248
+ [#544]: https://github.com/httprb/http/issues/544
249
+ [#557]: https://github.com/httprb/http/issues/557
250
+ [#560]: https://github.com/httprb/http/pull/560
251
+ [#565]: https://github.com/httprb/http/issues/565
252
+ [#566]: https://github.com/httprb/http/issues/566
253
+ [#579]: https://github.com/httprb/http/issues/579
254
+ [#618]: https://github.com/httprb/http/pull/618
255
+ [#621]: https://github.com/httprb/http/issues/621
256
+ [#642]: https://github.com/httprb/http/issues/642
257
+ [#667]: https://github.com/httprb/http/issues/667
258
+ [#673]: https://github.com/httprb/http/issues/673
259
+ [#739]: https://github.com/httprb/http/issues/739
260
+ [#754]: https://github.com/httprb/http/pull/754
261
+ [#770]: https://github.com/httprb/http/issues/770
262
+ [#773]: https://github.com/httprb/http/issues/773
263
+ [#776]: https://github.com/httprb/http/issues/776
264
+ [#784]: https://github.com/httprb/http/issues/784
265
+ [#785]: https://github.com/httprb/http/issues/785
266
+ [#826]: https://github.com/httprb/http/issues/826
267
+ [unreleased]: https://github.com/httprb/http/compare/v5.3.0...main
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2022 Tony Arcieri, Erik Michaels-Ober, Alexey V. Zapparov, Zachary Anker
1
+ Copyright (c) 2011-2026 Tony Arcieri, Erik Berlin, Alexey V. Zapparov, Zachary Anker
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  [![Gem Version][gem-image]][gem-link]
4
4
  [![MIT licensed][license-image]][license-link]
5
- [![Build Status][build-image]][build-link]
6
- [![Code Climate][codeclimate-image]][codeclimate-link]
5
+ [![Docs][docs-image]][docs-link]
6
+ [![Lint][lint-image]][lint-link]
7
+ [![Mutant][mutant-image]][mutant-link]
8
+ [![Test][test-image]][test-link]
9
+ [![Typecheck][typecheck-image]][typecheck-link]
7
10
 
8
11
  [Documentation]
9
12
 
@@ -105,18 +108,91 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
105
108
  => nil
106
109
  ```
107
110
 
111
+ ### Pattern Matching
112
+
113
+ Response objects support Ruby's pattern matching:
114
+
115
+ ```ruby
116
+ case HTTP.get("https://api.example.com/users")
117
+ in { status: 200..299, body: body }
118
+ JSON.parse(body.to_s)
119
+ in { status: 404 }
120
+ nil
121
+ in { status: 400.. }
122
+ raise "request failed"
123
+ end
124
+ ```
125
+
126
+ Pattern matching is also supported on `HTTP::Response::Status`, `HTTP::Headers`,
127
+ `HTTP::ContentType`, and `HTTP::URI`.
128
+
129
+ ### Base URI
130
+
131
+ Set a base URI to avoid repeating the scheme and host in every request:
132
+
133
+ ```ruby
134
+ api = HTTP.base_uri("https://api.example.com/v1")
135
+ api.get("users") # GET https://api.example.com/v1/users
136
+ api.get("users/1") # GET https://api.example.com/v1/users/1
137
+ ```
138
+
139
+ Relative paths are resolved per [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-5).
140
+ Combine with `persistent` to reuse the connection:
141
+
142
+ ```ruby
143
+ HTTP.base_uri("https://api.example.com/v1").persistent do |http|
144
+ http.get("users")
145
+ http.get("posts")
146
+ end
147
+ ```
148
+
149
+ ### Thread Safety
150
+
151
+ Configured sessions are safe to share across threads:
152
+
153
+ ```ruby
154
+ # Build a session once, use it from any thread
155
+ session = HTTP.headers("Accept" => "application/json")
156
+ .timeout(10)
157
+ .auth("Bearer token")
158
+
159
+ threads = 10.times.map do
160
+ Thread.new { session.get("https://example.com/api/data") }
161
+ end
162
+ threads.each(&:join)
163
+ ```
164
+
165
+ Chainable configuration methods (`.headers`, `.timeout`, `.auth`, etc.) return
166
+ an `HTTP::Session`, which creates a fresh `HTTP::Client` for every request.
167
+
168
+ Persistent connections (`HTTP.persistent`) return an `HTTP::Session` that pools
169
+ one `HTTP::Client` per origin. The session itself is **not** thread-safe. For
170
+ thread-safe persistent connections, use the
171
+ [connection_pool](https://rubygems.org/gems/connection_pool) gem:
172
+
173
+ ```ruby
174
+ pool = ConnectionPool.new(size: 5) { HTTP.persistent("https://example.com") }
175
+ pool.with { |http| http.get("/path") }
176
+ ```
177
+
178
+ Cross-origin redirects are handled transparently — the session opens a separate
179
+ persistent connection for each origin encountered during a redirect chain:
180
+
181
+ ```ruby
182
+ HTTP.persistent("https://example.com").follow do |http|
183
+ http.get("/moved-to-other-domain") # follows redirect across origins
184
+ end
185
+ ```
186
+
108
187
  ## Supported Ruby Versions
109
188
 
110
189
  This library aims to support and is [tested against][build-link]
111
190
  the following Ruby versions:
112
191
 
113
- - JRuby 9.3
114
- - Ruby 2.6
115
- - Ruby 2.7
116
- - Ruby 3.0
117
- - Ruby 3.1
118
192
  - Ruby 3.2
119
193
  - Ruby 3.3
194
+ - Ruby 3.4
195
+ - Ruby 4.0
120
196
 
121
197
  If something doesn't work on one of these versions, it's a bug.
122
198
 
@@ -132,8 +208,20 @@ exist at the time of a major release, support for that Ruby version may be
132
208
  dropped.
133
209
 
134
210
 
211
+ ## Upgrading
212
+
213
+ See [UPGRADING.md] for a detailed migration guide between major versions.
214
+
215
+
216
+ ## Security
217
+
218
+ See [SECURITY.md] for reporting vulnerabilities.
219
+
220
+
135
221
  ## Contributing to http.rb
136
222
 
223
+ See [CONTRIBUTING.md] for guidelines, or the quick version:
224
+
137
225
  - Fork http.rb on GitHub
138
226
  - Make your changes
139
227
  - Ensure all tests pass (`bundle exec rake`)
@@ -144,7 +232,7 @@ dropped.
144
232
 
145
233
  ## Copyright
146
234
 
147
- Copyright © 2011-2022 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
235
+ Copyright © 2011-2026 Tony Arcieri, Erik Berlin, Alexey V. Zapparov, Zachary Anker.
148
236
  See LICENSE.txt for further details.
149
237
 
150
238
 
@@ -154,13 +242,22 @@ See LICENSE.txt for further details.
154
242
  [gem-link]: https://rubygems.org/gems/http
155
243
  [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
156
244
  [license-link]: https://github.com/httprb/http/blob/main/LICENSE.txt
157
- [build-image]: https://github.com/httprb/http/workflows/CI/badge.svg
158
- [build-link]: https://github.com/httprb/http/actions/workflows/ci.yml
159
- [codeclimate-image]: https://codeclimate.com/github/httprb/http.svg?branch=main
160
- [codeclimate-link]: https://codeclimate.com/github/httprb/http
245
+ [docs-image]: https://github.com/httprb/http/actions/workflows/docs.yml/badge.svg
246
+ [docs-link]: https://github.com/httprb/http/actions/workflows/docs.yml
247
+ [lint-image]: https://github.com/httprb/http/actions/workflows/lint.yml/badge.svg
248
+ [lint-link]: https://github.com/httprb/http/actions/workflows/lint.yml
249
+ [mutant-image]: https://github.com/httprb/http/actions/workflows/mutant.yml/badge.svg
250
+ [mutant-link]: https://github.com/httprb/http/actions/workflows/mutant.yml
251
+ [test-image]: https://github.com/httprb/http/actions/workflows/test.yml/badge.svg
252
+ [test-link]: https://github.com/httprb/http/actions/workflows/test.yml
253
+ [typecheck-image]: https://github.com/httprb/http/actions/workflows/typecheck.yml/badge.svg
254
+ [typecheck-link]: https://github.com/httprb/http/actions/workflows/typecheck.yml
161
255
 
162
256
  [//]: # (links)
163
257
 
258
+ [contributing.md]: https://github.com/httprb/http/blob/main/CONTRIBUTING.md
164
259
  [documentation]: https://github.com/httprb/http/wiki
165
- [requests]: https://docs.python-requests.org/en/latest/
166
260
  [llhttp]: https://llhttp.org/
261
+ [requests]: https://docs.python-requests.org/en/latest/
262
+ [security.md]: https://github.com/httprb/http/blob/main/SECURITY.md
263
+ [upgrading.md]: https://github.com/httprb/http/blob/main/UPGRADING.md