philiprehberger-http_client 0.7.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d0ec8636cc91257698783cc3876cb93be649c33ad70c91317c6738b145a2ab1
4
- data.tar.gz: 778941d598bfb51d3ec2e1291e8ab4231ac43137385659b17795bdebf8f14c21
3
+ metadata.gz: 0c2d06598cd67b2eacacb8b5782991c53204d9d2d0975161e8a342f02b6ea9ea
4
+ data.tar.gz: 65df219a10eff45bdd84e202dc74336881c979f0cedcfb33855118e519b0d0d9
5
5
  SHA512:
6
- metadata.gz: a86d3da1c7d89b7bbf1341cb0efe062f99e12a025909f28307bfc163f24435f1706a2b1d2a025439b2ba8d59060c9b06c04fe97b213895a5587ba0ecd1c9da4b
7
- data.tar.gz: 46e39334ae5e243f378ca80e31313180f980bf819fcce88760d3c3b8a2628fc0cd23b99219da31e56d520364dfca0fd9f643ab9cbf0a0c907780e85f6c017494
6
+ metadata.gz: e4b987d048b25358a516553179038dbcdda91f417205fd43f58aa6bc10dd524a7c6835c951ac1f4e2b8e0639f3daad2de648d49876e787f6f911ce2130ee822d
7
+ data.tar.gz: 48ea64a75160012db6e407df73067252dc570a370c5b7968fb061f2621f468efde0973b26ad484caed947859e6f37122d7c5f3316d0f220b62a120c8b8fd81ef
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-04-05
11
+
12
+ ### Added
13
+ - `Client#close` method to explicitly drain the connection pool
14
+ - `Client.open(**opts, &block)` block form with automatic cleanup
15
+
16
+ ### Fixed
17
+ - Response objects now properly initialize `metrics` and `redirects` attributes
18
+ - 307 and 308 redirects now preserve the original HTTP method instead of converting to GET
19
+ - Clarified that `dns_time`, `connect_time`, and `tls_time` metrics are unavailable from Net::HTTP
20
+
10
21
  ## [0.7.0] - 2026-04-04
11
22
 
12
23
  ### Added
data/README.md CHANGED
@@ -299,6 +299,8 @@ metrics.first_byte_time # => 0.180
299
299
  metrics.to_h # => { dns_time: 0.0, connect_time: 0.0, ... }
300
300
  ```
301
301
 
302
+ > **Note:** `dns_time`, `connect_time`, and `tls_time` are not available from Ruby's stdlib `Net::HTTP` and will always be `0.0`. Only `total_time` and `first_byte_time` are populated.
303
+
302
304
  ### Connection pooling
303
305
 
304
306
  Reuse TCP connections to the same host for better performance:
@@ -317,6 +319,23 @@ client = Philiprehberger::HttpClient.new(
317
319
  client.pool.drain
318
320
  ```
319
321
 
322
+ ### Client lifecycle
323
+
324
+ Use `Client.open` for automatic cleanup, or call `close` manually to drain the connection pool:
325
+
326
+ ```ruby
327
+ # Block form — pool is drained automatically
328
+ Philiprehberger::HttpClient.open(base_url: "https://api.example.com", pool: true) do |client|
329
+ client.get("/data")
330
+ client.post("/submit", json: { key: "value" })
331
+ end
332
+
333
+ # Manual form
334
+ client = Philiprehberger::HttpClient.new(base_url: "https://api.example.com", pool: true)
335
+ client.get("/data")
336
+ client.close # drains the connection pool
337
+ ```
338
+
320
339
  ### Request ID tracking
321
340
 
322
341
  Every request is assigned a unique `X-Request-ID` header automatically:
@@ -428,6 +447,8 @@ client.head("/resource")
428
447
  | `bearer_token(token)` | Set Bearer token auth for all subsequent requests |
429
448
  | `basic_auth(user, pass)` | Set Basic auth for all subsequent requests |
430
449
  | `clear_cache!` | Flush the response cache |
450
+ | `close` | Drain the connection pool (no-op if pooling disabled) |
451
+ | `self.open(**opts, &block)` | Block form — creates client, yields it, ensures `close` is called |
431
452
  | `pool` | Returns the `Pool` instance (nil if pooling disabled) |
432
453
  | `cache` | Returns the `Cache` instance (nil if caching disabled) |
433
454
 
@@ -204,6 +204,25 @@ module Philiprehberger
204
204
  @cache&.clear!
205
205
  end
206
206
 
207
+ # Drain the connection pool. No-op if pooling is disabled.
208
+ #
209
+ # @return [void]
210
+ def close
211
+ @pool&.drain
212
+ end
213
+
214
+ # Create a client, yield it to the block, and ensure it is closed afterward.
215
+ #
216
+ # @param opts [Hash] Options forwarded to {#initialize}
217
+ # @yield [Client] the client instance
218
+ # @return [Object] the return value of the block
219
+ def self.open(**opts)
220
+ client = new(**opts)
221
+ yield client
222
+ ensure
223
+ client&.close
224
+ end
225
+
207
226
  private
208
227
 
209
228
  def assign_timeout_opts(opts)
@@ -185,6 +185,15 @@ module Philiprehberger
185
185
  @follow_redirects && REDIRECT_CODES.include?(response.status)
186
186
  end
187
187
 
188
+ METHOD_CLASS_MAP = {
189
+ 'GET' => Net::HTTP::Get,
190
+ 'POST' => Net::HTTP::Post,
191
+ 'PUT' => Net::HTTP::Put,
192
+ 'PATCH' => Net::HTTP::Patch,
193
+ 'DELETE' => Net::HTTP::Delete,
194
+ 'HEAD' => Net::HTTP::Head
195
+ }.freeze
196
+
188
197
  def follow_redirect_chain(response, original_request, **timeout_opts)
189
198
  redirect_count = 0
190
199
  redirects = []
@@ -199,7 +208,12 @@ module Philiprehberger
199
208
  redirect_uri = URI.parse(location)
200
209
  redirect_uri = URI.join("#{original_request.uri.scheme}://#{original_request.uri.host}", location) unless redirect_uri.host
201
210
 
202
- redirect_request = Net::HTTP::Get.new(redirect_uri)
211
+ redirect_class = if [307, 308].include?(current_response.status)
212
+ METHOD_CLASS_MAP.fetch(original_request.method, Net::HTTP::Get)
213
+ else
214
+ Net::HTTP::Get
215
+ end
216
+ redirect_request = redirect_class.new(redirect_uri)
203
217
  @default_headers.each { |key, value| redirect_request[key] = value }
204
218
  redirect_request['accept-encoding'] ||= 'gzip, deflate'
205
219
  apply_cookie_header(redirect_request)
@@ -18,6 +18,8 @@ module Philiprehberger
18
18
  @headers = headers
19
19
  @streaming = streaming
20
20
  @request_id = request_id
21
+ @metrics = nil
22
+ @redirects = []
21
23
  end
22
24
 
23
25
  # Returns true if the status code is in the 2xx range.
@@ -50,9 +52,7 @@ module Philiprehberger
50
52
  # Returns the redirect chain (empty if no redirects occurred).
51
53
  #
52
54
  # @return [Array<String>]
53
- def redirects
54
- @redirects || []
55
- end
55
+ attr_reader :redirects
56
56
 
57
57
  # Returns true if the response was redirected.
58
58
  #
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module HttpClient
5
- VERSION = '0.7.0'
5
+ VERSION = '0.8.0'
6
6
  end
7
7
  end
@@ -22,5 +22,14 @@ module Philiprehberger
22
22
  def self.new(**options)
23
23
  Client.new(**options)
24
24
  end
25
+
26
+ # Block form — creates a client, yields it, and ensures cleanup.
27
+ #
28
+ # @param options [Hash] Forwarded to {Client#initialize}
29
+ # @yield [Client]
30
+ # @return [Object] the return value of the block
31
+ def self.open(...)
32
+ Client.open(...)
33
+ end
25
34
  end
26
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-http_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-05 00:00:00.000000000 Z
11
+ date: 2026-04-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A zero-dependency HTTP client built on Ruby's net/http with automatic
14
14
  retries, request/response interceptors, and a clean API for JSON services.