patient_http 1.3.0 → 1.5.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 +4 -4
- data/ARCHITECTURE.md +19 -7
- data/CHANGELOG.md +38 -0
- data/README.md +21 -4
- data/VERSION +1 -1
- data/lib/patient_http/client.rb +27 -2
- data/lib/patient_http/client_pool.rb +14 -7
- data/lib/patient_http/completion_executor.rb +139 -0
- data/lib/patient_http/configuration.rb +46 -1
- data/lib/patient_http/payload.rb +37 -15
- data/lib/patient_http/processor.rb +387 -113
- data/lib/patient_http/processor_observer.rb +71 -3
- data/lib/patient_http/request.rb +23 -2
- data/lib/patient_http/request_helper.rb +15 -6
- data/lib/patient_http/request_preparer.rb +7 -0
- data/lib/patient_http/request_task.rb +2 -1
- data/lib/patient_http/request_template.rb +8 -3
- data/lib/patient_http/response_reader.rb +225 -17
- data/lib/patient_http/synchronous_executor.rb +16 -33
- data/lib/patient_http.rb +13 -2
- data/patient_http.gemspec +0 -2
- metadata +3 -16
|
@@ -20,6 +20,7 @@ module PatientHttp
|
|
|
20
20
|
@on_error = on_error
|
|
21
21
|
@proxy_client = nil
|
|
22
22
|
@request_preparer = RequestPreparer.new(config)
|
|
23
|
+
@response_reader = ResponseReader.new(nil, config: config)
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Execute the request synchronously.
|
|
@@ -62,11 +63,12 @@ module PatientHttp
|
|
|
62
63
|
# flattened to a single joined string value.
|
|
63
64
|
headers_hash = async_response.headers.to_h.transform_values(&:to_s)
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
chunks = read_response_body(async_response, headers_hash)
|
|
67
|
+
body_content = @response_reader.decode_body(chunks, headers_hash)
|
|
66
68
|
|
|
67
69
|
{
|
|
68
70
|
status: async_response.status,
|
|
69
|
-
headers: headers_hash,
|
|
71
|
+
headers: ResponseReader.rewrite_content_encoding(headers_hash),
|
|
70
72
|
body: body_content
|
|
71
73
|
}
|
|
72
74
|
end
|
|
@@ -133,19 +135,22 @@ module PatientHttp
|
|
|
133
135
|
|
|
134
136
|
# Create HTTP client with config settings (retries, proxy, connection timeout).
|
|
135
137
|
#
|
|
138
|
+
# The client is not wrapped in a Protocol::HTTP::AcceptEncoding middleware.
|
|
139
|
+
# That wrapper overwrites the request's accept-encoding header, which would
|
|
140
|
+
# ignore a caller opting out of compression, so response bodies are decoded
|
|
141
|
+
# by ResponseReader here exactly as they are on the async path.
|
|
142
|
+
#
|
|
136
143
|
# @param url [String] the resolved request URL
|
|
137
|
-
# @return [
|
|
144
|
+
# @return [Async::HTTP::Client] the HTTP client
|
|
138
145
|
def create_http_client(url)
|
|
139
146
|
endpoint = Async::HTTP::Endpoint.parse(url)
|
|
140
147
|
endpoint = configure_endpoint(endpoint) if @config.connection_timeout
|
|
141
148
|
|
|
142
|
-
|
|
149
|
+
if @config.proxy_url
|
|
143
150
|
create_proxied_client(endpoint)
|
|
144
151
|
else
|
|
145
152
|
Async::HTTP::Client.new(endpoint, retries: @config.retries)
|
|
146
153
|
end
|
|
147
|
-
|
|
148
|
-
Protocol::HTTP::AcceptEncoding.new(client)
|
|
149
154
|
end
|
|
150
155
|
|
|
151
156
|
# Create a proxied HTTP client.
|
|
@@ -174,11 +179,13 @@ module PatientHttp
|
|
|
174
179
|
)
|
|
175
180
|
end
|
|
176
181
|
|
|
177
|
-
# Read the response body with size validation.
|
|
182
|
+
# Read the raw response body chunks with size validation. The chunks are
|
|
183
|
+
# the wire bytes; ResponseReader#decode_body inflates and applies the
|
|
184
|
+
# charset, enforcing the same limit on the inflated bytes.
|
|
178
185
|
#
|
|
179
186
|
# @param async_response [Async::HTTP::Protocol::Response] the async HTTP response
|
|
180
187
|
# @param headers_hash [Hash] the response headers
|
|
181
|
-
# @return [String
|
|
188
|
+
# @return [Array<String>, nil] the raw body chunks or nil if no body present
|
|
182
189
|
def read_response_body(async_response, headers_hash)
|
|
183
190
|
return nil unless async_response.body
|
|
184
191
|
|
|
@@ -210,31 +217,7 @@ module PatientHttp
|
|
|
210
217
|
async_response.body.close unless finished
|
|
211
218
|
end
|
|
212
219
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
charset = extract_charset(headers_hash)
|
|
216
|
-
if charset
|
|
217
|
-
begin
|
|
218
|
-
encoding = Encoding.find(charset)
|
|
219
|
-
body.force_encoding(encoding)
|
|
220
|
-
rescue ArgumentError
|
|
221
|
-
# Invalid charset, keep binary
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
body
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# Extract charset from Content-Type header.
|
|
229
|
-
def extract_charset(headers_hash)
|
|
230
|
-
content_type = headers_hash["content-type"]
|
|
231
|
-
return nil unless content_type
|
|
232
|
-
|
|
233
|
-
match = content_type.match(/;\s*charset\s*=\s*([^;\s]+)/i)
|
|
234
|
-
return nil unless match
|
|
235
|
-
|
|
236
|
-
charset = match[1].strip
|
|
237
|
-
charset.gsub(/\A["']|["']\z/, "")
|
|
220
|
+
chunks
|
|
238
221
|
end
|
|
239
222
|
|
|
240
223
|
# Invoke callback synchronously.
|
data/lib/patient_http.rb
CHANGED
|
@@ -30,6 +30,12 @@ module PatientHttp
|
|
|
30
30
|
|
|
31
31
|
class ResponseTooLargeError < StandardError; end
|
|
32
32
|
|
|
33
|
+
# Raised when a request names a processor that is not configured. Handlers
|
|
34
|
+
# that support named processors raise this at enqueue time; the executing
|
|
35
|
+
# side raises it for a job that names an unconfigured processor so the job
|
|
36
|
+
# lands in the job system's retry mechanism instead of being dropped.
|
|
37
|
+
class UnknownProcessorError < StandardError; end
|
|
38
|
+
|
|
33
39
|
# HTTP redirect status codes that should be followed
|
|
34
40
|
FOLLOWABLE_REDIRECT_STATUSES = [301, 302, 303, 307, 308].freeze
|
|
35
41
|
|
|
@@ -45,6 +51,7 @@ module PatientHttp
|
|
|
45
51
|
autoload :Client, File.join(__dir__, "patient_http/client")
|
|
46
52
|
autoload :ClientError, File.join(__dir__, "patient_http/http_error")
|
|
47
53
|
autoload :ClientPool, File.join(__dir__, "patient_http/client_pool")
|
|
54
|
+
autoload :CompletionExecutor, File.join(__dir__, "patient_http/completion_executor")
|
|
48
55
|
autoload :Configuration, File.join(__dir__, "patient_http/configuration")
|
|
49
56
|
autoload :Encryptor, File.join(__dir__, "patient_http/encryptor")
|
|
50
57
|
autoload :Error, File.join(__dir__, "patient_http/error")
|
|
@@ -323,6 +330,8 @@ module PatientHttp
|
|
|
323
330
|
# @param callback_args [Hash, nil] JSON-compatible callback arguments
|
|
324
331
|
# @param preprocessors [String, Symbol, Array<String, Symbol>, nil] names of preprocessors
|
|
325
332
|
# registered on the configuration to apply to the request when it is sent
|
|
333
|
+
# @param processor [String, Symbol, nil] name of the processor that should execute
|
|
334
|
+
# the request; handlers that support named processors route on this value
|
|
326
335
|
# @return [Object] return value from the registered request handler
|
|
327
336
|
def request(
|
|
328
337
|
method,
|
|
@@ -335,7 +344,8 @@ module PatientHttp
|
|
|
335
344
|
timeout: nil,
|
|
336
345
|
raise_error_responses: nil,
|
|
337
346
|
callback_args: nil,
|
|
338
|
-
preprocessors: nil
|
|
347
|
+
preprocessors: nil,
|
|
348
|
+
processor: nil
|
|
339
349
|
)
|
|
340
350
|
request = Request.new(
|
|
341
351
|
method,
|
|
@@ -345,7 +355,8 @@ module PatientHttp
|
|
|
345
355
|
headers: headers,
|
|
346
356
|
params: params,
|
|
347
357
|
timeout: timeout,
|
|
348
|
-
preprocessors: preprocessors
|
|
358
|
+
preprocessors: preprocessors,
|
|
359
|
+
processor: processor
|
|
349
360
|
)
|
|
350
361
|
execute(
|
|
351
362
|
request: request,
|
data/patient_http.gemspec
CHANGED
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.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Durand
|
|
@@ -65,20 +65,6 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: bundler
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - ">="
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0'
|
|
75
|
-
type: :development
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - ">="
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0'
|
|
82
68
|
description: This gem provides a dedicated async HTTP processor that uses Ruby's Fiber
|
|
83
69
|
scheduler for non-blocking I/O. Application threads hand off HTTP requests to the
|
|
84
70
|
processor and return immediately. The processor handles hundreds of concurrent HTTP
|
|
@@ -103,6 +89,7 @@ files:
|
|
|
103
89
|
- lib/patient_http/class_helper.rb
|
|
104
90
|
- lib/patient_http/client.rb
|
|
105
91
|
- lib/patient_http/client_pool.rb
|
|
92
|
+
- lib/patient_http/completion_executor.rb
|
|
106
93
|
- lib/patient_http/configuration.rb
|
|
107
94
|
- lib/patient_http/encryptor.rb
|
|
108
95
|
- lib/patient_http/error.rb
|
|
@@ -159,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
159
146
|
- !ruby/object:Gem::Version
|
|
160
147
|
version: '0'
|
|
161
148
|
requirements: []
|
|
162
|
-
rubygems_version:
|
|
149
|
+
rubygems_version: 3.6.9
|
|
163
150
|
specification_version: 4
|
|
164
151
|
summary: Generic async HTTP connection pool for Ruby applications using Fiber-based
|
|
165
152
|
concurrency
|