Class: PatientHttp::RequestTask
- Inherits:
-
Object
- Object
- PatientHttp::RequestTask
- Includes:
- TimeHelper
- Defined in:
- lib/patient_http/request_task.rb
Overview
A wrapper around Request that includes callback and job context for the Processor. This class allows HTTP requests to be enqueued and processed asynchronously, tracking their lifecycle and providing methods to handle success and error callbacks.
Constant Summary collapse
- SENSITIVE_HEADERS =
Headers that are sensitive to origin and should be stripped on cross-origin redirects
%w[authorization cookie].freeze
Instance Attribute Summary collapse
-
#callback ⇒ String
readonly
Class name for the callback service.
-
#callback_args ⇒ Hash
readonly
Callback arguments to include in Response/Error objects (never nil, defaults to empty hash).
-
#error ⇒ Exception?
readonly
The error, set on failure.
-
#id ⇒ String
readonly
Unique UUID for tracking the task.
-
#raise_error_responses ⇒ Boolean
readonly
Whether to raise HttpError for non-2xx responses.
-
#redirects ⇒ Array<String>
readonly
URLs visited during redirect chain.
-
#request ⇒ Request
readonly
The HTTP request details.
-
#response ⇒ Response?
readonly
The HTTP response, set on success.
-
#task_handler ⇒ TaskHandler
readonly
The handler for job lifecycle operations.
Instance Method Summary collapse
-
#build_response(status:, headers:, body:) ⇒ Response
private
Build a Response object from async response data.
-
#completed!(response) ⇒ void
Called with the HTTP response on a completed request.
-
#completed_at ⇒ Time?
Returns the wall clock time when the task was completed.
-
#duration ⇒ Float?
Execution duration in seconds.
-
#enqueued! ⇒ void
Mark task as enqueued.
-
#enqueued_at ⇒ Time?
Returns the wall clock time when the task was enqueued.
-
#enqueued_duration ⇒ Float?
Enqueued duration in seconds.
-
#error!(exception) ⇒ void
Called with the HTTP error on a failed request.
-
#error? ⇒ Boolean
Return true if an error was raised during the request.
-
#initialize(request:, task_handler:, callback:, callback_args: {}, raise_error_responses: false, redirects: [], id: nil, default_max_redirects: 5) ⇒ RequestTask
constructor
Initializes a new RequestTask.
-
#max_redirects ⇒ Integer
Returns the maximum number of redirects to follow.
-
#original_id ⇒ String
Get the id of the first request task before any redirects.
-
#redirect_task(location:, status:) ⇒ RequestTask
Create a new RequestTask for following a redirect.
-
#retry ⇒ String
Re-enqueue the original job via the task handler.
-
#started! ⇒ void
Mark task as started.
-
#started_at ⇒ Time?
Returns the wall clock time when the task was started.
-
#success? ⇒ Boolean
Return true if the task successfully received a response from the server.
Methods included from TimeHelper
#monotonic_time, #wall_clock_time
Constructor Details
#initialize(request:, task_handler:, callback:, callback_args: {}, raise_error_responses: false, redirects: [], id: nil, default_max_redirects: 5) ⇒ RequestTask
Initializes a new RequestTask.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/patient_http/request_task.rb', line 52 def initialize( request:, task_handler:, callback:, callback_args: {}, raise_error_responses: false, redirects: [], id: nil, default_max_redirects: 5 ) @id = id&.to_s || SecureRandom.uuid @request = request @task_handler = task_handler @callback = callback.is_a?(Class) ? callback.name : callback.to_s @callback_args = CallbackValidator.validate_callback_args(callback_args) || {} @raise_error_responses = raise_error_responses @redirects = redirects || [] @default_max_redirects = default_max_redirects @enqueued_at = nil @started_at = nil @completed_at = nil @response = nil @error = nil raise ArgumentError, "request is required" unless @request raise ArgumentError, "task_handler is required" unless @task_handler raise ArgumentError, "callback is required" if @callback.nil? || @callback.empty? CallbackValidator.validate!(@callback) end |
Instance Attribute Details
#callback ⇒ String (readonly)
Returns Class name for the callback service.
23 24 25 |
# File 'lib/patient_http/request_task.rb', line 23 def callback @callback end |
#callback_args ⇒ Hash (readonly)
Returns Callback arguments to include in Response/Error objects (never nil, defaults to empty hash).
26 27 28 |
# File 'lib/patient_http/request_task.rb', line 26 def callback_args @callback_args end |
#error ⇒ Exception? (readonly)
Returns The error, set on failure.
38 39 40 |
# File 'lib/patient_http/request_task.rb', line 38 def error @error end |
#id ⇒ String (readonly)
Returns Unique UUID for tracking the task.
14 15 16 |
# File 'lib/patient_http/request_task.rb', line 14 def id @id end |
#raise_error_responses ⇒ Boolean (readonly)
Returns Whether to raise HttpError for non-2xx responses.
29 30 31 |
# File 'lib/patient_http/request_task.rb', line 29 def raise_error_responses @raise_error_responses end |
#redirects ⇒ Array<String> (readonly)
Returns URLs visited during redirect chain.
32 33 34 |
# File 'lib/patient_http/request_task.rb', line 32 def redirects @redirects end |
#request ⇒ Request (readonly)
Returns The HTTP request details.
17 18 19 |
# File 'lib/patient_http/request_task.rb', line 17 def request @request end |
#response ⇒ Response? (readonly)
Returns The HTTP response, set on success.
35 36 37 |
# File 'lib/patient_http/request_task.rb', line 35 def response @response end |
#task_handler ⇒ TaskHandler (readonly)
Returns The handler for job lifecycle operations.
20 21 22 |
# File 'lib/patient_http/request_task.rb', line 20 def task_handler @task_handler end |
Instance Method Details
#build_response(status:, headers:, body:) ⇒ Response
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Build a Response object from async response data.
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/patient_http/request_task.rb', line 255 def build_response(status:, headers:, body:) original_id = id.split("/").first Response.new( status: status, headers: headers, body: body, duration: duration, request_id: original_id, url: request.url, http_method: request.http_method, callback_args: @callback_args, redirects: @redirects ) end |
#completed!(response) ⇒ void
This method returns an undefined value.
Called with the HTTP response on a completed request. Note that the response may represent an HTTP error (4xx or 5xx status).
143 144 145 146 147 148 |
# File 'lib/patient_http/request_task.rb', line 143 def completed!(response) @completed_at = monotonic_time @response = response @task_handler.on_complete(response, @callback) end |
#completed_at ⇒ Time?
Returns the wall clock time when the task was completed.
112 113 114 |
# File 'lib/patient_http/request_task.rb', line 112 def completed_at wall_clock_time(@completed_at) if @completed_at end |
#duration ⇒ Float?
Execution duration in seconds.
126 127 128 129 130 |
# File 'lib/patient_http/request_task.rb', line 126 def duration return nil unless @started_at ((@completed_at || monotonic_time) - @started_at).round(9) end |
#enqueued! ⇒ void
This method returns an undefined value.
Mark task as enqueued
85 86 87 |
# File 'lib/patient_http/request_task.rb', line 85 def enqueued! @enqueued_at = monotonic_time end |
#enqueued_at ⇒ Time?
Returns the wall clock time when the task was enqueued.
98 99 100 |
# File 'lib/patient_http/request_task.rb', line 98 def enqueued_at wall_clock_time(@enqueued_at) if @enqueued_at end |
#enqueued_duration ⇒ Float?
Enqueued duration in seconds.
118 119 120 121 122 |
# File 'lib/patient_http/request_task.rb', line 118 def enqueued_duration return nil unless @enqueued_at (@started_at || monotonic_time) - @enqueued_at end |
#error!(exception) ⇒ void
This method returns an undefined value.
Called with the HTTP error on a failed request.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/patient_http/request_task.rb', line 154 def error!(exception) @completed_at = monotonic_time @error = exception wrapped_error = exception unless wrapped_error.is_a?(Error) wrapped_error = RequestError.from_exception( exception, request_id: @id, duration: duration, url: request.url, http_method: request.http_method, callback_args: @callback_args ) end @task_handler.on_error(wrapped_error, @callback) end |
#error? ⇒ Boolean
Return true if an error was raised during the request.
184 185 186 |
# File 'lib/patient_http/request_task.rb', line 184 def error? !@error.nil? end |
#max_redirects ⇒ Integer
Returns the maximum number of redirects to follow. Uses the request’s max_redirects if set, otherwise falls back to the default.
192 193 194 |
# File 'lib/patient_http/request_task.rb', line 192 def max_redirects request.max_redirects || @default_max_redirects end |
#original_id ⇒ String
Get the id of the first request task before any redirects. This is useful for tracking the overall request across multiple redirect tasks.
275 276 277 |
# File 'lib/patient_http/request_task.rb', line 275 def original_id id.split("/").first end |
#redirect_task(location:, status:) ⇒ RequestTask
Create a new RequestTask for following a redirect.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/patient_http/request_task.rb', line 201 def redirect_task(location:, status:) # Determine the HTTP method and body for the redirect # 301, 302, 303: Convert to GET (no body) - standard browser behavior # 307, 308: Preserve original method and body if [301, 302, 303].include?(status) redirect_method = :get redirect_body = nil else redirect_method = request.http_method redirect_body = request.body end # Resolve the redirect URL (handle relative URLs) redirect_url = resolve_redirect_url(location) # Strip sensitive headers on cross-origin redirects to prevent credential leakage redirect_headers = if cross_origin?(request.url, redirect_url) request.headers.except(*SENSITIVE_HEADERS) else request.headers end # Create a new request for the redirect redirect_request = Request.new( redirect_method, redirect_url, headers: redirect_headers, body: redirect_body, timeout: request.timeout, max_redirects: request.max_redirects ) redirect_task_id = "#{id.split("/").first}/#{@redirects.size + 2}" # Create the new task with updated redirects chain self.class.new( request: redirect_request, task_handler: @task_handler, callback: @callback, callback_args: @callback_args, raise_error_responses: @raise_error_responses, redirects: @redirects + [request.url], id: redirect_task_id, default_max_redirects: @default_max_redirects ) end |
#retry ⇒ String
Re-enqueue the original job via the task handler.
134 135 136 |
# File 'lib/patient_http/request_task.rb', line 134 def retry @task_handler.retry end |
#started! ⇒ void
This method returns an undefined value.
Mark task as started
91 92 93 |
# File 'lib/patient_http/request_task.rb', line 91 def started! @started_at = monotonic_time end |
#started_at ⇒ Time?
Returns the wall clock time when the task was started.
105 106 107 |
# File 'lib/patient_http/request_task.rb', line 105 def started_at wall_clock_time(@started_at) if @started_at end |
#success? ⇒ Boolean
Return true if the task successfully received a response from the server. Note that the response may represent an HTTP error (4xx or 5xx status).
177 178 179 |
# File 'lib/patient_http/request_task.rb', line 177 def success? !@response.nil? end |