Class: ReactOnRailsPro::RendererHttpClient

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

Overview

rubocop:disable Metrics/ClassLength

Defined Under Namespace

Classes: ConnectTimeoutWrapper, ConnectionError, Error, HTTPError, Response, TimeoutError

Constant Summary collapse

CONNECTION_ERRORS =
[
  SocketError,
  IOError,
  Errno::ECONNRESET,
  Errno::ECONNREFUSED,
  Errno::EHOSTUNREACH,
  Errno::ENETUNREACH,
  Errno::EPIPE,
  Errno::ETIMEDOUT,
  Protocol::HTTP::RefusedError,
  # Treat HTTP/2 stream resets as transport failures because the renderer can
  # abort streams without a usable HTTP response for Request/StreamRequest.
  Protocol::HTTP2::StreamError
].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.

:@__ror_pro_http_clients__

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.



272
273
274
275
276
277
278
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 272

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"
end

Class Method Details

.build_form_body(form) ⇒ Object



196
197
198
199
200
201
202
203
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 196

def build_form_body(form)
  return build_multipart_body(form) if form.any? { |_name, value| file_part?(value) }

  [
    [["content-type", "application/x-www-form-urlencoded"]],
    URI.encode_www_form(flatten_url_encoded_form(form))
  ]
end

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

Raises:

  • (ArgumentError)


179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 179

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

  body = +"".b

  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

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



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 159

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

  new(
    origin:,
    pool_size: 1,
    connect_timeout:,
    read_timeout:,
    force_http2: false
  ).get(path)
end

.split_url(url) ⇒ Object



171
172
173
174
175
176
177
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 171

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

Instance Method Details

#closeObject



307
308
309
310
311
312
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 307

def close
  scheduler = Fiber.scheduler
  return unless scheduler

  evict_client_from_scheduler(scheduler)
end

#get(path) ⇒ Object



287
288
289
290
291
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 287

def get(path)
  build_response(stream: false) do |yielder, status_assigner|
    execute_request(:get, path, [[], nil], yielder, status_assigner)
  end
end

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



280
281
282
283
284
285
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 280

def post(path, form: nil, json: nil, stream: false)
  headers, body = request_body(form:, json:)
  build_response(stream:) do |yielder, status_assigner|
    execute_request(:post, path, [headers, body], yielder, status_assigner)
  end
end

#post_bidi(path, headers:) ⇒ Object

Bidirectional HTTP/2 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 sends END_STREAM on the HTTP/2 stream.



299
300
301
302
303
304
305
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 299

def post_bidi(path, headers:)
  writable = Protocol::HTTP::Body::Writable.new
  response = build_response(stream: true) do |yielder, status_assigner|
    execute_request(:post, path, [headers, writable], yielder, status_assigner)
  end
  [writable.output, response]
end