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.



259
260
261
262
263
264
265
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 259

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



183
184
185
186
187
188
189
190
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 183

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)


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 166

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



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 146

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

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

.split_url(url) ⇒ Object



158
159
160
161
162
163
164
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 158

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



294
295
296
297
298
299
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 294

def close
  scheduler = Fiber.scheduler
  return unless scheduler

  evict_client_from_scheduler(scheduler)
end

#get(path) ⇒ Object



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

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



267
268
269
270
271
272
# File 'lib/react_on_rails_pro/renderer_http_client.rb', line 267

def post(path, form: nil, json: nil, stream: false)
  headers, body = request_body(form: form, json: json)
  build_response(stream: 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.



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

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