patient_http 1.1.1 → 1.1.2
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +6 -0
- data/VERSION +1 -1
- data/lib/patient_http/client.rb +4 -3
- data/lib/patient_http/client_pool.rb +23 -8
- data/lib/patient_http/configuration.rb +22 -0
- data/lib/patient_http/request_error.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba06f7676b7d33f9c53adea879761a89d31593f51c3f33b999400d7305fcbf06
|
|
4
|
+
data.tar.gz: ede7b685bbfbdd5049695316f73da55bf0abe203b0a9e1e28eb0bc1da6739f79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9271aecb38a8adac4634d6e02f7797472347e1d4d20a154cf52c39daa450789a1ea1511f464d0551172361f4815ab26eeac535d2d72fb483e169be58bd5900df
|
|
7
|
+
data.tar.gz: 379ff51d8e0a3e90f49577c9922c6f58a2caa6ed6e4d36a651f9f0071ea5d4999cf25df926cdaf1165e27a495db1d1e65d208d8c58cb41e3513f5c8210829332
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 1.1.2
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added `protocol` configuration option to force the HTTP protocol to `:http1` or `:http2` instead of negotiating with the server. Forcing `:http1` also limits the TLS ALPN advertisement to `http/1.1`, which can work around SSL-intercepting proxies that mishandle HTTP/2 negotiation.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `Errno::ECONNABORTED` ("Software caused connection abort") is now treated as a connection error: the pooled client for the host is evicted so the aborted connection is not reused, and `RequestError` classifies it as `:connection` instead of `:unknown`. This error is commonly raised when an SSL-intercepting proxy kills a connection.
|
|
16
|
+
|
|
7
17
|
## 1.1.1
|
|
8
18
|
|
|
9
19
|
### Fixed
|
data/README.md
CHANGED
|
@@ -555,6 +555,12 @@ config = PatientHttp::Configuration.new(
|
|
|
555
555
|
# Retries for failed requests (default: 3)
|
|
556
556
|
retries: 3,
|
|
557
557
|
|
|
558
|
+
# Force the HTTP protocol to :http1 or :http2 (default: nil, negotiates with
|
|
559
|
+
# the server, preferring HTTP/2 for HTTPS). Forcing :http1 also limits the TLS
|
|
560
|
+
# ALPN advertisement to http/1.1, which can work around SSL-intercepting
|
|
561
|
+
# proxies that mishandle HTTP/2.
|
|
562
|
+
protocol: nil,
|
|
563
|
+
|
|
558
564
|
# Logger instance (default: Logger to STDERR at ERROR level)
|
|
559
565
|
logger: Logger.new($stdout)
|
|
560
566
|
)
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.2
|
data/lib/patient_http/client.rb
CHANGED
|
@@ -8,7 +8,8 @@ module PatientHttp
|
|
|
8
8
|
max_size: config.connection_pool_size,
|
|
9
9
|
connection_timeout: config.connection_timeout,
|
|
10
10
|
proxy_url: config.proxy_url,
|
|
11
|
-
retries: config.retries
|
|
11
|
+
retries: config.retries,
|
|
12
|
+
protocol: config.protocol
|
|
12
13
|
)
|
|
13
14
|
@response_reader = ResponseReader.new(@processor)
|
|
14
15
|
end
|
|
@@ -64,8 +65,8 @@ module PatientHttp
|
|
|
64
65
|
|
|
65
66
|
def connection_error?(exception)
|
|
66
67
|
case exception
|
|
67
|
-
when Async::TimeoutError, Errno::ECONNRESET, Errno::
|
|
68
|
-
Errno::EHOSTUNREACH, SocketError, IOError
|
|
68
|
+
when Async::TimeoutError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE,
|
|
69
|
+
Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError, IOError
|
|
69
70
|
true
|
|
70
71
|
else
|
|
71
72
|
false
|
|
@@ -7,17 +7,30 @@ module PatientHttp
|
|
|
7
7
|
# is capped with an LRU algorithm - when a new client is needed and the
|
|
8
8
|
# pool is at capacity, the least recently used client is closed and removed.
|
|
9
9
|
class ClientPool
|
|
10
|
-
|
|
10
|
+
# Supported protocol names mapped to their async-http implementations. Forcing
|
|
11
|
+
# :http1 also limits the TLS ALPN advertisement to http/1.1, which avoids
|
|
12
|
+
# HTTP/2 negotiation with servers and middleboxes that mishandle it.
|
|
13
|
+
PROTOCOLS = {
|
|
14
|
+
http1: Async::HTTP::Protocol::HTTP11,
|
|
15
|
+
http2: Async::HTTP::Protocol::HTTP2
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def initialize(max_size:, connection_timeout: nil, proxy_url: nil, retries: 3, protocol: nil)
|
|
19
|
+
if protocol && !PROTOCOLS.include?(protocol)
|
|
20
|
+
raise ArgumentError.new("protocol must be one of #{PROTOCOLS.keys.inspect}, got: #{protocol.inspect}")
|
|
21
|
+
end
|
|
22
|
+
|
|
11
23
|
@clients = {}
|
|
12
24
|
@max_size = max_size
|
|
13
25
|
@connection_timeout = connection_timeout
|
|
14
26
|
@proxy_url = proxy_url
|
|
15
27
|
@retries = retries
|
|
28
|
+
@protocol = protocol
|
|
16
29
|
@mutex = Mutex.new
|
|
17
30
|
@proxy_client = nil
|
|
18
31
|
end
|
|
19
32
|
|
|
20
|
-
attr_reader :max_size, :connection_timeout, :proxy_url, :retries
|
|
33
|
+
attr_reader :max_size, :connection_timeout, :proxy_url, :retries, :protocol
|
|
21
34
|
|
|
22
35
|
# Get or create a client for the given endpoint.
|
|
23
36
|
#
|
|
@@ -162,17 +175,19 @@ module PatientHttp
|
|
|
162
175
|
|
|
163
176
|
def create_proxy_client
|
|
164
177
|
proxy_endpoint = Async::HTTP::Endpoint.parse(@proxy_url)
|
|
165
|
-
|
|
178
|
+
if @connection_timeout
|
|
179
|
+
proxy_endpoint = Async::HTTP::Endpoint.new(proxy_endpoint.url, timeout: @connection_timeout)
|
|
180
|
+
end
|
|
166
181
|
Async::HTTP::Client.new(proxy_endpoint)
|
|
167
182
|
end
|
|
168
183
|
|
|
169
184
|
def configure_endpoint(endpoint)
|
|
170
|
-
|
|
185
|
+
options = {}
|
|
186
|
+
options[:timeout] = @connection_timeout if @connection_timeout
|
|
187
|
+
options[:protocol] = PROTOCOLS.fetch(@protocol) if @protocol
|
|
188
|
+
return endpoint if options.empty?
|
|
171
189
|
|
|
172
|
-
Async::HTTP::Endpoint.new(
|
|
173
|
-
endpoint.url,
|
|
174
|
-
timeout: @connection_timeout
|
|
175
|
-
)
|
|
190
|
+
Async::HTTP::Endpoint.new(endpoint.url, **options)
|
|
176
191
|
end
|
|
177
192
|
end
|
|
178
193
|
end
|
|
@@ -46,6 +46,10 @@ module PatientHttp
|
|
|
46
46
|
# @return [Integer] Number of retries for failed requests
|
|
47
47
|
attr_reader :retries
|
|
48
48
|
|
|
49
|
+
# @return [Symbol, nil] HTTP protocol to use (:http1 or :http2). When nil, the
|
|
50
|
+
# protocol is negotiated with the server (HTTP/2 preferred for HTTPS).
|
|
51
|
+
attr_reader :protocol
|
|
52
|
+
|
|
49
53
|
# @return [SecretManager] the secret manager instance
|
|
50
54
|
attr_reader :secret_manager
|
|
51
55
|
|
|
@@ -63,6 +67,7 @@ module PatientHttp
|
|
|
63
67
|
# @param connection_timeout [Numeric, nil] Connection timeout in seconds
|
|
64
68
|
# @param proxy_url [String, nil] HTTP/HTTPS proxy URL (supports authentication)
|
|
65
69
|
# @param retries [Integer] Number of retries for failed requests
|
|
70
|
+
# @param protocol [Symbol, nil] HTTP protocol to use (:http1 or :http2); nil to negotiate
|
|
66
71
|
def initialize(
|
|
67
72
|
max_connections: 256,
|
|
68
73
|
request_timeout: 60,
|
|
@@ -76,6 +81,7 @@ module PatientHttp
|
|
|
76
81
|
connection_timeout: nil,
|
|
77
82
|
proxy_url: nil,
|
|
78
83
|
retries: 3,
|
|
84
|
+
protocol: nil,
|
|
79
85
|
encryption_key: nil
|
|
80
86
|
)
|
|
81
87
|
@mutex = Mutex.new
|
|
@@ -102,6 +108,7 @@ module PatientHttp
|
|
|
102
108
|
self.connection_timeout = connection_timeout
|
|
103
109
|
self.proxy_url = proxy_url
|
|
104
110
|
self.retries = retries
|
|
111
|
+
self.protocol = protocol
|
|
105
112
|
self.encryption_key = encryption_key
|
|
106
113
|
end
|
|
107
114
|
|
|
@@ -164,6 +171,20 @@ module PatientHttp
|
|
|
164
171
|
@retries = value
|
|
165
172
|
end
|
|
166
173
|
|
|
174
|
+
def protocol=(value)
|
|
175
|
+
if value.nil?
|
|
176
|
+
@protocol = nil
|
|
177
|
+
return
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
value = value.to_sym if value.is_a?(String)
|
|
181
|
+
unless ClientPool::PROTOCOLS.key?(value)
|
|
182
|
+
raise ArgumentError.new("protocol must be one of #{ClientPool::PROTOCOLS.keys.inspect}, got: #{value.inspect}")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
@protocol = value
|
|
186
|
+
end
|
|
187
|
+
|
|
167
188
|
# Set the encryption callable for encrypting payloads before serialization.
|
|
168
189
|
#
|
|
169
190
|
# @param callable [#call, nil] An object that responds to #call, taking data and returning encrypted data
|
|
@@ -326,6 +347,7 @@ module PatientHttp
|
|
|
326
347
|
"connection_timeout" => connection_timeout,
|
|
327
348
|
"proxy_url" => proxy_url,
|
|
328
349
|
"retries" => retries,
|
|
350
|
+
"protocol" => protocol,
|
|
329
351
|
"payload_stores" => payload_stores.keys,
|
|
330
352
|
"default_payload_store" => default_payload_store_name,
|
|
331
353
|
"secrets" => @mutex.synchronize { @secrets.keys }
|
|
@@ -80,7 +80,7 @@ module PatientHttp
|
|
|
80
80
|
:timeout
|
|
81
81
|
in OpenSSL::SSL::SSLError
|
|
82
82
|
:ssl
|
|
83
|
-
in Errno::ECONNREFUSED | Errno::ECONNRESET | Errno::EHOSTUNREACH | Errno::EPIPE | SocketError | IOError
|
|
83
|
+
in Errno::ECONNREFUSED | Errno::ECONNRESET | Errno::ECONNABORTED | Errno::EHOSTUNREACH | Errno::EPIPE | SocketError | IOError
|
|
84
84
|
:connection
|
|
85
85
|
else
|
|
86
86
|
if exception.is_a?(PatientHttp::ResponseTooLargeError)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: patient_http
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -156,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
156
|
- !ruby/object:Gem::Version
|
|
157
157
|
version: '0'
|
|
158
158
|
requirements: []
|
|
159
|
-
rubygems_version:
|
|
159
|
+
rubygems_version: 3.6.9
|
|
160
160
|
specification_version: 4
|
|
161
161
|
summary: Generic async HTTP connection pool for Ruby applications using Fiber-based
|
|
162
162
|
concurrency
|