Class: PatientHttp::SynchronousExecutor

Inherits:
Object
  • Object
show all
Includes:
RedirectHelper
Defined in:
lib/patient_http/synchronous_executor.rb

Overview

Handles synchronous/inline execution of HTTP requests.

Used for testing or when synchronous execution is needed. Accepts configuration and optional callback hooks so it has no dependency on any module-level singleton state.

Instance Method Summary collapse

Constructor Details

#initialize(task, config:, on_complete: nil, on_error: nil) ⇒ SynchronousExecutor

Returns a new instance of SynchronousExecutor.

Parameters:

  • task (RequestTask)

    the request task to execute

  • config (Configuration)

    the pool configuration

  • on_complete (Proc, nil) (defaults to: nil)

    hook called with response on success

  • on_error (Proc, nil) (defaults to: nil)

    hook called with error on failure



16
17
18
19
20
21
22
# File 'lib/patient_http/synchronous_executor.rb', line 16

def initialize(task, config:, on_complete: nil, on_error: nil)
  @task = task
  @config = config
  @on_complete = on_complete
  @on_error = on_error
  @proxy_client = nil
end

Instance Method Details

#callvoid

This method returns an undefined value.

Execute the request synchronously.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/patient_http/synchronous_executor.rb', line 26

def call
  Async do
    start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)

    begin
      http_client = nil
      response_data = nil

      loop do
        http_client&.close
        @proxy_client&.close
        @proxy_client = nil
        http_client = create_http_client
        timeout = @task.request.timeout || @config.request_timeout

        response_data = Async::Task.current.with_timeout(timeout) do
          headers = @task.request.headers.to_h.merge("x-request-id" => @task.id)
          headers["user-agent"] ||= @config.user_agent if @config.user_agent
          body = Protocol::HTTP::Body::Buffered.wrap([@task.request.body.to_s]) if @task.request.body

          endpoint = Async::HTTP::Endpoint.parse(@task.request.url)
          endpoint = configure_endpoint(endpoint) if @config.connection_timeout

          verb = @task.request.http_method.to_s.upcase
          options = {
            headers: headers,
            body: body,
            scheme: endpoint.scheme,
            authority: endpoint.authority
          }

          request = Protocol::HTTP::Request[verb, endpoint.path, **options]
          async_response = http_client.call(request)
          headers_hash = async_response.headers.to_h.transform_values(&:to_s)

          body_content = read_response_body(async_response, headers_hash)

          {
            status: async_response.status,
            headers: headers_hash,
            body: body_content
          }
        end

        # Check for redirect
        break unless should_follow_redirect?(@task, response_data)

        redirect_error = check_redirect_error(@task, response_data)
        if redirect_error
          invoke_callback(redirect_error, :error)
          return
        end

        location = response_data[:headers]["location"]
        @task = @task.redirect_task(location: location, status: response_data[:status])
      end

      end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
      duration = end_time - start_time

      response = Response.new(
        status: response_data[:status],
        headers: response_data[:headers],
        body: response_data[:body],
        duration: duration,
        request_id: @task.id,
        url: @task.request.url,
        http_method: @task.request.http_method,
        callback_args: @task.callback_args,
        redirects: @task.redirects
      )

      if @task.raise_error_responses && !response.success?
        http_error = HttpError.new(response)
        invoke_callback(http_error, :error)
      else
        invoke_callback(response, :response)
      end
    rescue => e
      end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
      duration = end_time - start_time

      error = RequestError.from_exception(
        e,
        request_id: @task.id,
        duration: duration,
        url: @task.request.url,
        http_method: @task.request.http_method,
        callback_args: @task.callback_args
      )
      invoke_callback(error, :error)
    ensure
      http_client&.close
      @proxy_client&.close
      @proxy_client = nil
    end
  end
end