patient_http 1.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.
- checksums.yaml +7 -0
- data/ARCHITECTURE.md +322 -0
- data/CHANGELOG.md +30 -0
- data/MIT-LICENSE +20 -0
- data/README.md +653 -0
- data/VERSION +1 -0
- data/db/migrate/20250101000000_create_patient_http_payloads.rb +15 -0
- data/lib/patient_http/callback_args.rb +176 -0
- data/lib/patient_http/callback_validator.rb +52 -0
- data/lib/patient_http/class_helper.rb +26 -0
- data/lib/patient_http/client.rb +80 -0
- data/lib/patient_http/client_pool.rb +178 -0
- data/lib/patient_http/configuration.rb +365 -0
- data/lib/patient_http/encryptor.rb +69 -0
- data/lib/patient_http/error.rb +76 -0
- data/lib/patient_http/external_storage.rb +134 -0
- data/lib/patient_http/http_error.rb +106 -0
- data/lib/patient_http/http_headers.rb +99 -0
- data/lib/patient_http/lifecycle_manager.rb +174 -0
- data/lib/patient_http/payload.rb +160 -0
- data/lib/patient_http/payload_store/active_record_store.rb +102 -0
- data/lib/patient_http/payload_store/base.rb +150 -0
- data/lib/patient_http/payload_store/file_store.rb +92 -0
- data/lib/patient_http/payload_store/redis_store.rb +98 -0
- data/lib/patient_http/payload_store/s3_store.rb +94 -0
- data/lib/patient_http/payload_store.rb +11 -0
- data/lib/patient_http/processor.rb +538 -0
- data/lib/patient_http/processor_observer.rb +48 -0
- data/lib/patient_http/rails/engine.rb +21 -0
- data/lib/patient_http/redirect_error.rb +136 -0
- data/lib/patient_http/redirect_helper.rb +90 -0
- data/lib/patient_http/request.rb +158 -0
- data/lib/patient_http/request_error.rb +150 -0
- data/lib/patient_http/request_helper.rb +230 -0
- data/lib/patient_http/request_task.rb +308 -0
- data/lib/patient_http/request_template.rb +114 -0
- data/lib/patient_http/response.rb +183 -0
- data/lib/patient_http/response_reader.rb +135 -0
- data/lib/patient_http/synchronous_executor.rb +241 -0
- data/lib/patient_http/task_handler.rb +55 -0
- data/lib/patient_http/time_helper.rb +32 -0
- data/lib/patient_http.rb +313 -0
- data/patient_http.gemspec +48 -0
- metadata +161 -0
data/lib/patient_http.rb
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
require "async/http"
|
|
5
|
+
require "concurrent"
|
|
6
|
+
require "monitor"
|
|
7
|
+
require "json"
|
|
8
|
+
require "uri"
|
|
9
|
+
require "zlib"
|
|
10
|
+
require "time"
|
|
11
|
+
require "socket"
|
|
12
|
+
require "securerandom"
|
|
13
|
+
require "logger"
|
|
14
|
+
|
|
15
|
+
# Generic async HTTP connection pool for Ruby applications.
|
|
16
|
+
#
|
|
17
|
+
# This module provides:
|
|
18
|
+
# - Async HTTP request processing using Ruby's Fiber scheduler
|
|
19
|
+
# - Connection pooling with HTTP/2 support
|
|
20
|
+
# - Configurable timeouts, retries, and proxy support
|
|
21
|
+
# - Error handling with typed errors
|
|
22
|
+
#
|
|
23
|
+
# This module can be used standalone or integrated with job systems
|
|
24
|
+
# like Sidekiq via adapters.
|
|
25
|
+
module PatientHttp
|
|
26
|
+
# Raised when trying to enqueue a request when the processor is not running
|
|
27
|
+
class NotRunningError < StandardError; end
|
|
28
|
+
|
|
29
|
+
class MaxCapacityError < StandardError; end
|
|
30
|
+
|
|
31
|
+
class ResponseTooLargeError < StandardError; end
|
|
32
|
+
|
|
33
|
+
# HTTP redirect status codes that should be followed
|
|
34
|
+
FOLLOWABLE_REDIRECT_STATUSES = [301, 302, 303, 307, 308].freeze
|
|
35
|
+
|
|
36
|
+
VERSION = File.read(File.join(__dir__, "../VERSION")).strip
|
|
37
|
+
|
|
38
|
+
# Autoload utility modules
|
|
39
|
+
autoload :ClassHelper, File.join(__dir__, "patient_http/class_helper")
|
|
40
|
+
autoload :TimeHelper, File.join(__dir__, "patient_http/time_helper")
|
|
41
|
+
|
|
42
|
+
# Autoload all components
|
|
43
|
+
autoload :CallbackArgs, File.join(__dir__, "patient_http/callback_args")
|
|
44
|
+
autoload :CallbackValidator, File.join(__dir__, "patient_http/callback_validator")
|
|
45
|
+
autoload :Client, File.join(__dir__, "patient_http/client")
|
|
46
|
+
autoload :ClientError, File.join(__dir__, "patient_http/http_error")
|
|
47
|
+
autoload :ClientPool, File.join(__dir__, "patient_http/client_pool")
|
|
48
|
+
autoload :Configuration, File.join(__dir__, "patient_http/configuration")
|
|
49
|
+
autoload :Encryptor, File.join(__dir__, "patient_http/encryptor")
|
|
50
|
+
autoload :Error, File.join(__dir__, "patient_http/error")
|
|
51
|
+
autoload :ExternalStorage, File.join(__dir__, "patient_http/external_storage")
|
|
52
|
+
autoload :HttpError, File.join(__dir__, "patient_http/http_error")
|
|
53
|
+
autoload :HttpHeaders, File.join(__dir__, "patient_http/http_headers")
|
|
54
|
+
autoload :LifecycleManager, File.join(__dir__, "patient_http/lifecycle_manager")
|
|
55
|
+
autoload :Payload, File.join(__dir__, "patient_http/payload")
|
|
56
|
+
autoload :PayloadStore, File.join(__dir__, "patient_http/payload_store")
|
|
57
|
+
autoload :Processor, File.join(__dir__, "patient_http/processor")
|
|
58
|
+
autoload :ProcessorObserver, File.join(__dir__, "patient_http/processor_observer")
|
|
59
|
+
autoload :RecursiveRedirectError, File.join(__dir__, "patient_http/redirect_error")
|
|
60
|
+
autoload :RedirectError, File.join(__dir__, "patient_http/redirect_error")
|
|
61
|
+
autoload :RedirectHelper, File.join(__dir__, "patient_http/redirect_helper")
|
|
62
|
+
autoload :Request, File.join(__dir__, "patient_http/request")
|
|
63
|
+
autoload :RequestError, File.join(__dir__, "patient_http/request_error")
|
|
64
|
+
autoload :RequestHelper, File.join(__dir__, "patient_http/request_helper")
|
|
65
|
+
autoload :RequestTask, File.join(__dir__, "patient_http/request_task")
|
|
66
|
+
autoload :RequestTemplate, File.join(__dir__, "patient_http/request_template")
|
|
67
|
+
autoload :Response, File.join(__dir__, "patient_http/response")
|
|
68
|
+
autoload :ResponseReader, File.join(__dir__, "patient_http/response_reader")
|
|
69
|
+
autoload :ServerError, File.join(__dir__, "patient_http/http_error")
|
|
70
|
+
autoload :SynchronousExecutor, File.join(__dir__, "patient_http/synchronous_executor")
|
|
71
|
+
autoload :TaskHandler, File.join(__dir__, "patient_http/task_handler")
|
|
72
|
+
autoload :TooManyRedirectsError, File.join(__dir__, "patient_http/redirect_error")
|
|
73
|
+
|
|
74
|
+
@testing = %w[RAILS_ENV RACK_ENV APP_ENV].any? { |var| ENV[var] == "test" }
|
|
75
|
+
@handler = nil
|
|
76
|
+
@handler_mutex = Monitor.new
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
# Check if running in testing mode.
|
|
80
|
+
#
|
|
81
|
+
# @api private
|
|
82
|
+
def testing?
|
|
83
|
+
@testing
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Set testing mode.
|
|
87
|
+
#
|
|
88
|
+
# @api private
|
|
89
|
+
def testing=(value)
|
|
90
|
+
@testing = !!value
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Registers a request handler that will be called to process each request.
|
|
94
|
+
# The handler must be a callable object (responds to `call`) or a block.
|
|
95
|
+
#
|
|
96
|
+
# The handler will receive keyword arguments: request, callback, callback_args,
|
|
97
|
+
# and raise_error_responses. It should return the request id for the enqueued request.
|
|
98
|
+
#
|
|
99
|
+
# @param callable [#call, nil] A callable object that will handle requests.
|
|
100
|
+
# @yield [request, callback, callback_args, raise_error_responses] If a block is given,
|
|
101
|
+
# it will be used as the request handler
|
|
102
|
+
# @raise [ArgumentError] if neither a callable nor a block is provided, or if both are provided
|
|
103
|
+
# @raise [ArgumentError] if the provided callable does not respond to `call`
|
|
104
|
+
# @raise [ArgumentError] if the handler does not support the required keyword arguments
|
|
105
|
+
# @return [#call] the registered handler
|
|
106
|
+
def register_handler(callable = nil, &block)
|
|
107
|
+
raise ArgumentError.new("Must provide a callable object or a block") unless callable || block_given?
|
|
108
|
+
raise ArgumentError.new("Cannot provide both a callable object and a block") if callable && block_given?
|
|
109
|
+
|
|
110
|
+
handler = callable || block
|
|
111
|
+
raise ArgumentError.new("Handler must be a callable object or a block") unless handler.respond_to?(:call)
|
|
112
|
+
|
|
113
|
+
validate_handler_parameters!(handler)
|
|
114
|
+
|
|
115
|
+
@handler_mutex.synchronize { @handler = handler }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Registers a request handler, raising an error if one is already registered.
|
|
119
|
+
#
|
|
120
|
+
# This is a safer alternative to {.register_handler} that prevents accidental
|
|
121
|
+
# double-registration.
|
|
122
|
+
#
|
|
123
|
+
# @param callable [#call, nil] A callable object that will handle requests.
|
|
124
|
+
# @yield [request, callback, callback_args, raise_error_responses] If a block is given,
|
|
125
|
+
# it will be used as the request handler
|
|
126
|
+
# @raise [RuntimeError] if a handler is already registered
|
|
127
|
+
# @raise [ArgumentError] if neither a callable nor a block is provided, or if both are provided
|
|
128
|
+
# @raise [ArgumentError] if the provided callable does not respond to `call`
|
|
129
|
+
# @raise [ArgumentError] if the handler does not support the required keyword arguments
|
|
130
|
+
# @return [#call] the registered handler
|
|
131
|
+
def register_handler!(callable = nil, &block)
|
|
132
|
+
@handler_mutex.synchronize do
|
|
133
|
+
if @handler
|
|
134
|
+
raise "A PatientHttp handler is already registered. Unregister the existing handler before registering a new one."
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
register_handler(callable, &block)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Unregisters the current request handler.
|
|
142
|
+
#
|
|
143
|
+
# @param handler [#call, nil] If provided, only unregisters if the given handler matches
|
|
144
|
+
# the current handler
|
|
145
|
+
# @return [void]
|
|
146
|
+
def unregister_handler(handler = nil)
|
|
147
|
+
@handler_mutex.synchronize do
|
|
148
|
+
@handler = nil if @handler == handler || handler.nil?
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Executes the registered request handler with the given request parameters.
|
|
153
|
+
#
|
|
154
|
+
# @param request [Request] the HTTP request to handle
|
|
155
|
+
# @param callback [Class, String] the callback class or name
|
|
156
|
+
# @param callback_args [Hash, nil] JSON-compatible callback arguments
|
|
157
|
+
# @param raise_error_responses [Boolean, nil] when true, non-success responses are
|
|
158
|
+
# reported as errors
|
|
159
|
+
# @raise [RuntimeError] if no handler is registered
|
|
160
|
+
# @return [Object] return value from the registered request handler
|
|
161
|
+
def execute(request:, callback:, callback_args: nil, raise_error_responses: nil)
|
|
162
|
+
handler = @handler_mutex.synchronize { @handler }
|
|
163
|
+
|
|
164
|
+
unless handler
|
|
165
|
+
raise "No request handler registered; you must register a PatientHttp handler before executing requests"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
handler.call(
|
|
169
|
+
request: request,
|
|
170
|
+
callback: callback,
|
|
171
|
+
callback_args: callback_args,
|
|
172
|
+
raise_error_responses: raise_error_responses
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Enqueues an HTTP GET request.
|
|
177
|
+
#
|
|
178
|
+
# @param uri [String] absolute URL
|
|
179
|
+
# @param callback [Class, String] callback class to handle the response
|
|
180
|
+
# @param kwargs [Hash] forwarded to `request`
|
|
181
|
+
# @return [Object] return value from the registered request handler
|
|
182
|
+
def get(uri, callback:, **kwargs)
|
|
183
|
+
request(:get, uri, callback: callback, **kwargs)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Enqueues an HTTP POST request.
|
|
187
|
+
#
|
|
188
|
+
# @param uri [String] absolute URL
|
|
189
|
+
# @param callback [Class, String] callback class to handle the response
|
|
190
|
+
# @param kwargs [Hash] forwarded to `request`
|
|
191
|
+
# @return [Object] return value from the registered request handler
|
|
192
|
+
def post(uri, callback:, **kwargs)
|
|
193
|
+
request(:post, uri, callback: callback, **kwargs)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Enqueues an HTTP PUT request.
|
|
197
|
+
#
|
|
198
|
+
# @param uri [String] absolute URL
|
|
199
|
+
# @param callback [Class, String] callback class to handle the response
|
|
200
|
+
# @param kwargs [Hash] forwarded to `request`
|
|
201
|
+
# @return [Object] return value from the registered request handler
|
|
202
|
+
def put(uri, callback:, **kwargs)
|
|
203
|
+
request(:put, uri, callback: callback, **kwargs)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Enqueues an HTTP PATCH request.
|
|
207
|
+
#
|
|
208
|
+
# @param uri [String] absolute URL
|
|
209
|
+
# @param callback [Class, String] callback class to handle the response
|
|
210
|
+
# @param kwargs [Hash] forwarded to `request`
|
|
211
|
+
# @return [Object] return value from the registered request handler
|
|
212
|
+
def patch(uri, callback:, **kwargs)
|
|
213
|
+
request(:patch, uri, callback: callback, **kwargs)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Enqueues an HTTP DELETE request.
|
|
217
|
+
#
|
|
218
|
+
# @param uri [String] absolute URL
|
|
219
|
+
# @param callback [Class, String] callback class to handle the response
|
|
220
|
+
# @param kwargs [Hash] forwarded to `request`
|
|
221
|
+
# @return [Object] return value from the registered request handler
|
|
222
|
+
def delete(uri, callback:, **kwargs)
|
|
223
|
+
request(:delete, uri, callback: callback, **kwargs)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Builds and dispatches an HTTP request.
|
|
227
|
+
#
|
|
228
|
+
# @param method [Symbol] HTTP method (`:get`, `:post`, `:put`, `:patch`, `:delete`)
|
|
229
|
+
# @param url [String] absolute URL
|
|
230
|
+
# @param callback [Class, String] callback class to handle the response
|
|
231
|
+
# @param headers [Hash, nil] request headers
|
|
232
|
+
# @param body [String, nil] raw request body
|
|
233
|
+
# @param json [Hash, Array, nil] JSON payload encoded by the request layer
|
|
234
|
+
# @param params [Hash, nil] query parameters
|
|
235
|
+
# @param timeout [Numeric, nil] timeout in seconds for this request
|
|
236
|
+
# @param raise_error_responses [Boolean, nil] when true, non-success responses are
|
|
237
|
+
# reported as errors
|
|
238
|
+
# @param callback_args [Hash, nil] JSON-compatible callback arguments
|
|
239
|
+
# @return [Object] return value from the registered request handler
|
|
240
|
+
def request(
|
|
241
|
+
method,
|
|
242
|
+
url,
|
|
243
|
+
callback:,
|
|
244
|
+
headers: nil,
|
|
245
|
+
body: nil,
|
|
246
|
+
json: nil,
|
|
247
|
+
params: nil,
|
|
248
|
+
timeout: nil,
|
|
249
|
+
raise_error_responses: nil,
|
|
250
|
+
callback_args: nil
|
|
251
|
+
)
|
|
252
|
+
request = Request.new(method, url, body: body, json: json, headers: headers, params: params, timeout: timeout)
|
|
253
|
+
execute(
|
|
254
|
+
request: request,
|
|
255
|
+
callback: callback,
|
|
256
|
+
callback_args: callback_args,
|
|
257
|
+
raise_error_responses: raise_error_responses
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
private
|
|
262
|
+
|
|
263
|
+
# Validates that the handler accepts the required keyword arguments.
|
|
264
|
+
#
|
|
265
|
+
# @param handler [#call] the handler to validate
|
|
266
|
+
# @raise [ArgumentError] if the handler does not support the required keyword arguments
|
|
267
|
+
# @return [void]
|
|
268
|
+
def validate_handler_parameters!(handler)
|
|
269
|
+
required_keywords = %i[request callback callback_args raise_error_responses]
|
|
270
|
+
|
|
271
|
+
# Get the parameters of the handler's call method
|
|
272
|
+
method_obj = handler.is_a?(Proc) ? handler : handler.method(:call)
|
|
273
|
+
params = method_obj.parameters
|
|
274
|
+
|
|
275
|
+
# Check if handler has keyword rest parameter (**kwargs)
|
|
276
|
+
has_keyrest = params.any? { |type, _name| type == :keyrest }
|
|
277
|
+
return if has_keyrest
|
|
278
|
+
|
|
279
|
+
# rubocop:disable Style/HashSlice
|
|
280
|
+
positional_params = params.select { |type, _name| %i[req opt].include?(type) }
|
|
281
|
+
if positional_params.any?
|
|
282
|
+
raise ArgumentError.new(
|
|
283
|
+
"Handler must not accept positional parameters. " \
|
|
284
|
+
"Found: #{positional_params.map { |_type, name| name }.join(", ")}"
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
keyword_params = params.select { |type, _name| %i[keyreq key].include?(type) }
|
|
289
|
+
keyword_names = keyword_params.map { |_type, name| name }
|
|
290
|
+
|
|
291
|
+
missing_keywords = required_keywords - keyword_names
|
|
292
|
+
if missing_keywords.any?
|
|
293
|
+
raise ArgumentError.new(
|
|
294
|
+
"Handler must accept keyword arguments: " \
|
|
295
|
+
"#{required_keywords.map(&:to_s).join(", ")}. " \
|
|
296
|
+
"Missing: #{missing_keywords.map(&:to_s).join(", ")}"
|
|
297
|
+
)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
required_keyword_names = keyword_params
|
|
301
|
+
.select { |type, _name| type == :keyreq }
|
|
302
|
+
.map { |_type, name| name }
|
|
303
|
+
# rubocop:enable Style/HashSlice
|
|
304
|
+
extra_required_keywords = required_keyword_names - required_keywords
|
|
305
|
+
return unless extra_required_keywords.any?
|
|
306
|
+
|
|
307
|
+
raise ArgumentError.new(
|
|
308
|
+
"Handler must not have extra required keyword parameters. " \
|
|
309
|
+
"Found: #{extra_required_keywords.map(&:to_s).join(", ")}"
|
|
310
|
+
)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Gem::Specification.new do |spec|
|
|
2
|
+
spec.name = "patient_http"
|
|
3
|
+
spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
|
|
4
|
+
spec.authors = ["Brian Durand"]
|
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
|
6
|
+
|
|
7
|
+
spec.summary = "Generic async HTTP connection pool for Ruby applications using Fiber-based concurrency"
|
|
8
|
+
spec.description = "This gem provides a dedicated async HTTP processor that uses Ruby's Fiber scheduler for non-blocking I/O. Application threads hand off HTTP requests to the processor and return immediately. The processor handles hundreds of concurrent HTTP connections using fibers, then notifies the application when responses arrive via a pluggable callback mechanism. This design keeps application threads free to do other work while HTTP requests are in flight."
|
|
9
|
+
|
|
10
|
+
spec.homepage = "https://github.com/bdurand/patient_http"
|
|
11
|
+
spec.license = "MIT"
|
|
12
|
+
|
|
13
|
+
spec.metadata = {
|
|
14
|
+
"homepage_uri" => spec.homepage,
|
|
15
|
+
"source_code_uri" => spec.homepage,
|
|
16
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
21
|
+
ignore_files = %w[
|
|
22
|
+
.
|
|
23
|
+
AGENTS.md
|
|
24
|
+
Appraisals
|
|
25
|
+
Gemfile
|
|
26
|
+
Gemfile.lock
|
|
27
|
+
Rakefile
|
|
28
|
+
docker-compose.yml
|
|
29
|
+
bin/
|
|
30
|
+
gemfiles/
|
|
31
|
+
spec/
|
|
32
|
+
test_app/
|
|
33
|
+
]
|
|
34
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
|
35
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
spec.require_paths = ["lib"]
|
|
39
|
+
|
|
40
|
+
spec.required_ruby_version = ">= 3.2"
|
|
41
|
+
|
|
42
|
+
spec.add_dependency "async", "~> 2.0"
|
|
43
|
+
spec.add_dependency "async-http", "~> 0.60"
|
|
44
|
+
spec.add_dependency "concurrent-ruby", "~> 1.2"
|
|
45
|
+
spec.add_dependency "logger"
|
|
46
|
+
|
|
47
|
+
spec.add_development_dependency "bundler"
|
|
48
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: patient_http
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Brian Durand
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: async
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: async-http
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.60'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.60'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: concurrent-ruby
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.2'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.2'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: logger
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
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
|
+
description: This gem provides a dedicated async HTTP processor that uses Ruby's Fiber
|
|
83
|
+
scheduler for non-blocking I/O. Application threads hand off HTTP requests to the
|
|
84
|
+
processor and return immediately. The processor handles hundreds of concurrent HTTP
|
|
85
|
+
connections using fibers, then notifies the application when responses arrive via
|
|
86
|
+
a pluggable callback mechanism. This design keeps application threads free to do
|
|
87
|
+
other work while HTTP requests are in flight.
|
|
88
|
+
email:
|
|
89
|
+
- bbdurand@gmail.com
|
|
90
|
+
executables: []
|
|
91
|
+
extensions: []
|
|
92
|
+
extra_rdoc_files: []
|
|
93
|
+
files:
|
|
94
|
+
- ARCHITECTURE.md
|
|
95
|
+
- CHANGELOG.md
|
|
96
|
+
- MIT-LICENSE
|
|
97
|
+
- README.md
|
|
98
|
+
- VERSION
|
|
99
|
+
- db/migrate/20250101000000_create_patient_http_payloads.rb
|
|
100
|
+
- lib/patient_http.rb
|
|
101
|
+
- lib/patient_http/callback_args.rb
|
|
102
|
+
- lib/patient_http/callback_validator.rb
|
|
103
|
+
- lib/patient_http/class_helper.rb
|
|
104
|
+
- lib/patient_http/client.rb
|
|
105
|
+
- lib/patient_http/client_pool.rb
|
|
106
|
+
- lib/patient_http/configuration.rb
|
|
107
|
+
- lib/patient_http/encryptor.rb
|
|
108
|
+
- lib/patient_http/error.rb
|
|
109
|
+
- lib/patient_http/external_storage.rb
|
|
110
|
+
- lib/patient_http/http_error.rb
|
|
111
|
+
- lib/patient_http/http_headers.rb
|
|
112
|
+
- lib/patient_http/lifecycle_manager.rb
|
|
113
|
+
- lib/patient_http/payload.rb
|
|
114
|
+
- lib/patient_http/payload_store.rb
|
|
115
|
+
- lib/patient_http/payload_store/active_record_store.rb
|
|
116
|
+
- lib/patient_http/payload_store/base.rb
|
|
117
|
+
- lib/patient_http/payload_store/file_store.rb
|
|
118
|
+
- lib/patient_http/payload_store/redis_store.rb
|
|
119
|
+
- lib/patient_http/payload_store/s3_store.rb
|
|
120
|
+
- lib/patient_http/processor.rb
|
|
121
|
+
- lib/patient_http/processor_observer.rb
|
|
122
|
+
- lib/patient_http/rails/engine.rb
|
|
123
|
+
- lib/patient_http/redirect_error.rb
|
|
124
|
+
- lib/patient_http/redirect_helper.rb
|
|
125
|
+
- lib/patient_http/request.rb
|
|
126
|
+
- lib/patient_http/request_error.rb
|
|
127
|
+
- lib/patient_http/request_helper.rb
|
|
128
|
+
- lib/patient_http/request_task.rb
|
|
129
|
+
- lib/patient_http/request_template.rb
|
|
130
|
+
- lib/patient_http/response.rb
|
|
131
|
+
- lib/patient_http/response_reader.rb
|
|
132
|
+
- lib/patient_http/synchronous_executor.rb
|
|
133
|
+
- lib/patient_http/task_handler.rb
|
|
134
|
+
- lib/patient_http/time_helper.rb
|
|
135
|
+
- patient_http.gemspec
|
|
136
|
+
homepage: https://github.com/bdurand/patient_http
|
|
137
|
+
licenses:
|
|
138
|
+
- MIT
|
|
139
|
+
metadata:
|
|
140
|
+
homepage_uri: https://github.com/bdurand/patient_http
|
|
141
|
+
source_code_uri: https://github.com/bdurand/patient_http
|
|
142
|
+
changelog_uri: https://github.com/bdurand/patient_http/blob/main/CHANGELOG.md
|
|
143
|
+
rdoc_options: []
|
|
144
|
+
require_paths:
|
|
145
|
+
- lib
|
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - ">="
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '3.2'
|
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '0'
|
|
156
|
+
requirements: []
|
|
157
|
+
rubygems_version: 4.0.3
|
|
158
|
+
specification_version: 4
|
|
159
|
+
summary: Generic async HTTP connection pool for Ruby applications using Fiber-based
|
|
160
|
+
concurrency
|
|
161
|
+
test_files: []
|