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? ⇒ Boolean
Return true if the task has started processing (i.e. #started! was called).
-
#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.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
# File 'lib/patient_http/request_task.rb', line 264 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).
152 153 154 155 156 157 |
# File 'lib/patient_http/request_task.rb', line 152 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.
121 122 123 |
# File 'lib/patient_http/request_task.rb', line 121 def completed_at wall_clock_time(@completed_at) if @completed_at end |
#duration ⇒ Float?
Execution duration in seconds.
135 136 137 138 139 |
# File 'lib/patient_http/request_task.rb', line 135 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.
107 108 109 |
# File 'lib/patient_http/request_task.rb', line 107 def enqueued_at wall_clock_time(@enqueued_at) if @enqueued_at end |
#enqueued_duration ⇒ Float?
Enqueued duration in seconds.
127 128 129 130 131 |
# File 'lib/patient_http/request_task.rb', line 127 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.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/patient_http/request_task.rb', line 163 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.
193 194 195 |
# File 'lib/patient_http/request_task.rb', line 193 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.
201 202 203 |
# File 'lib/patient_http/request_task.rb', line 201 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.
284 285 286 |
# File 'lib/patient_http/request_task.rb', line 284 def original_id id.split("/").first end |
#redirect_task(location:, status:) ⇒ RequestTask
Create a new RequestTask for following a redirect.
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 247 248 249 250 251 252 253 254 255 |
# File 'lib/patient_http/request_task.rb', line 210 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 and preprocessors on cross-origin redirects to # prevent credential leakage cross_origin = cross_origin?(request.url, redirect_url) redirect_headers = cross_origin ? request.headers.except(*SENSITIVE_HEADERS) : request.headers redirect_preprocessors = cross_origin ? [] : request.preprocessors # 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, preprocessors: redirect_preprocessors ) 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.
143 144 145 |
# File 'lib/patient_http/request_task.rb', line 143 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? ⇒ Boolean
Return true if the task has started processing (i.e. #started! was called). Used to keep observer request_start/request_end notifications balanced when a task is re-enqueued during shutdown.
100 101 102 |
# File 'lib/patient_http/request_task.rb', line 100 def started? !@started_at.nil? end |
#started_at ⇒ Time?
Returns the wall clock time when the task was started.
114 115 116 |
# File 'lib/patient_http/request_task.rb', line 114 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).
186 187 188 |
# File 'lib/patient_http/request_task.rb', line 186 def success? !@response.nil? end |