Class: ReactOnRailsPro::RendererHttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails_pro/renderer_http_client.rb,
sig/react_on_rails_pro/renderer_http_client.rbs

Overview

rubocop:disable Metrics/ClassLength

Defined Under Namespace

Classes: BufferedResponseBody, ConnectTimeoutWrapper, ConnectionError, Error, HTTPError, MultipartBody, MultipartFileBody, PersistentThreadClient, Response, TimeoutError

Constant Summary collapse

CONNECTION_ERRORS =

Returns:

  • (Array[Class])
[
  SocketError,
  IOError,
  Errno::ECONNRESET,
  Errno::ECONNREFUSED,
  Errno::EHOSTUNREACH,
  Errno::ENETUNREACH,
  Errno::EPIPE,
  Errno::ETIMEDOUT,
  Protocol::HTTP::RefusedError
].freeze
SCHEDULER_CLIENTS_KEY =

Per-scheduler storage for persistent HTTP clients. When an outer Fiber.scheduler exists BEFORE we enter Sync {}, clients are stored on the scheduler object using this instance variable key. This enables connection reuse across requests within the same long-lived scheduler context (e.g., Falcon, Puma with async scheduler). The hash maps origin URLs to Async::HTTP::Client instances.

IMPORTANT: We only use persistent mode when a scheduler already exists before execute_request enters Sync {}. If Sync {} creates an ephemeral scheduler, we use the ephemeral client path to ensure proper cleanup when the block exits.

Returns:

  • (Symbol)
:@__ror_pro_http_clients__
CLIENT_GENERATION_MUTEX =
Mutex.new

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(origin:, pool_size:, connect_timeout:, read_timeout:, force_http2: true) ⇒ RendererHttpClient

Returns a new instance of RendererHttpClient.



605
606
607
608
609
610
611
612
613
614
615
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 605

def initialize(origin:, pool_size:, connect_timeout:, read_timeout:, force_http2: true)
  @origin = origin
  @pool_size = pool_size
  @connect_timeout = connect_timeout
  @read_timeout = read_timeout
  @force_h2c = force_http2 && URI.parse(origin).scheme == "http"
  @thread_clients = {}.compare_by_identity
  @thread_clients_mutex = Mutex.new
  @closed = false
  @closed_mutex = Mutex.new
end

Class Method Details

.build_form_body(form) ⇒ [Array[[String, String]], MultipartBody | String]

Parameters:

  • form (Hash[untyped, untyped])

Returns:



546
547
548
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 546

def build_form_body(form)
  build_multipart_body(form)
end

.build_multipart_body(form, boundary: SecureRandom.hex(24)) ⇒ Object

Raises:

  • (ArgumentError)


529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 529

def build_multipart_body(form, boundary: SecureRandom.hex(24))
  raise ArgumentError, "boundary must not contain '--'" if boundary.include?("--")

  body = MultipartBody.new

  form.each do |name, value|
    append_multipart_value(body, boundary, name, value)
  end

  body << "--#{boundary}--\r\n"

  [
    [["content-type", "multipart/form-data; boundary=#{boundary}"]],
    body
  ]
end

.bump_client_generationInteger

Returns:

  • (Integer)


523
524
525
526
527
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 523

def bump_client_generation
  CLIENT_GENERATION_MUTEX.synchronize do
    @client_generation = (@client_generation || 0) + 1
  end
end

.client_generationInteger

Returns:

  • (Integer)


517
518
519
520
521
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 517

def client_generation
  CLIENT_GENERATION_MUTEX.synchronize do
    @client_generation ||= 0
  end
end

.get(url, connect_timeout:, read_timeout:) ⇒ Response

Parameters:

  • url (String)
  • connect_timeout: (Numeric, nil)
  • read_timeout: (Numeric, nil)

Returns:



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 492

def get(url, connect_timeout:, read_timeout:)
  origin, path = split_url(url)

  client = new(
    origin:,
    pool_size: 1,
    connect_timeout:,
    read_timeout:,
    force_http2: false
  )
  response = client.get(path)
  response.body
  response
ensure
  client&.close
end

.split_url(url) ⇒ [String, String]

Parameters:

  • url (String)

Returns:

  • ([String, String])


509
510
511
512
513
514
515
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 509

def split_url(url)
  uri = URI.parse(url)
  port = uri.port unless uri.default_port == uri.port
  origin = "#{uri.scheme}://#{uri.host}#{":#{port}" if port}"

  [origin, uri.request_uri]
end

.transport_config_descriptionString

Returns:

  • (String)


58
59
60
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 58

def self.transport_config_description
  "renderer_http_force_http2 = #{ReactOnRailsPro.configuration.renderer_http_force_http2}"
end

Instance Method Details

#closenil

Returns:

  • (nil)


660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 660

def close
  return unless mark_closed

  close_error = nil
  scheduler = Fiber.scheduler
  begin
    evict_client_from_scheduler(scheduler) if scheduler
  rescue StandardError => e
    close_error ||= e
  end

  begin
    close_thread_clients
  rescue StandardError => e
    close_error ||= e
  end

  raise close_error if close_error
end

#get(path) ⇒ Response

Parameters:

  • path (String)

Returns:



629
630
631
632
633
634
635
636
637
638
639
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 629

def get(path)
  ensure_open!
  headers = []
  parent_context = ReactOnRailsPro::OpenTelemetry.capture_context
  build_response(stream: false) do |yielder, status_assigner, headers_assigner|
    execute_request_with_trace(:get, path, [headers, nil],
                               stream: false,
                               response_handlers: [yielder, status_assigner, headers_assigner],
                               parent_context:)
  end
end

#post(path, form: nil, json: nil, raw: nil, stream: false) ⇒ Object



617
618
619
620
621
622
623
624
625
626
627
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 617

def post(path, form: nil, json: nil, raw: nil, stream: false)
  ensure_open!
  headers, body = request_body(form:, json:, raw:)
  parent_context = ReactOnRailsPro::OpenTelemetry.capture_context
  build_response(stream:) do |yielder, status_assigner, headers_assigner|
    execute_request_with_trace(:post, path, [headers, body],
                               stream:,
                               response_handlers: [yielder, status_assigner, headers_assigner],
                               parent_context:)
  end
end

#post_bidi(path, headers:) ⇒ [untyped, Response]

Bidirectional streaming POST. Returns [output, response] where:

  • output is a Protocol::HTTP::Body::Writable::Output (supports << and close)
  • response is a lazy Response whose body is consumed via Response#each

The caller writes NDJSON lines to output while concurrently reading response chunks. Calling output.close signals request-body EOF to the selected transport.

Parameters:

  • path (String)
  • headers: (Array[[String, String]])

Returns:



647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 647

def post_bidi(path, headers:)
  ensure_open!
  writable = WritableBody.new
  parent_context = ReactOnRailsPro::OpenTelemetry.capture_context
  response = build_response(stream: true) do |yielder, status_assigner, headers_assigner|
    execute_request_with_trace(:post, path, [headers, writable],
                               stream: true,
                               response_handlers: [yielder, status_assigner, headers_assigner],
                               parent_context:)
  end
  [writable.output, response]
end