Class: ReactOnRails::LengthPrefixedParser

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails/length_prefixed_parser.rb

Overview

Parses the length-prefixed wire format used between Node renderer and Ruby.

Wire format per chunk:

<metadata JSON>\t<content byte length hex>\n<raw content bytes>

Used by both streaming (Pro) and non-streaming (OSS) paths. Strict protocol parser — any format violation raises an error.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeLengthPrefixedParser

Returns a new instance of LengthPrefixedParser.



33
34
35
36
37
38
39
40
41
42
# File 'lib/react_on_rails/length_prefixed_parser.rb', line 33

def initialize
  # Binary encoding so that `index` returns byte positions (not character positions).
  # Needed because `byteindex` requires Ruby 3.2+ and we support 3.0+.
  # force_encoding is O(1) (flips a flag, no copy). .b allocates a new object but
  # shares the byte buffer via copy-on-write for strings over ~23 bytes.
  @buf = "".b
  @state = :header
  @content_len = 0
  @metadata = nil
end

Class Method Details

.parse_one_chunk_result(str) ⇒ Object

Parses a complete length-prefixed result string that must contain exactly one chunk. Used by the non-streaming rendering path where ExecJS/node renderer returns a single result. Returns a single Hash: { “html” => String|nil, “consoleReplayScript” => “…”, … } Raises if the input contains zero or more than one chunk.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/react_on_rails/length_prefixed_parser.rb', line 18

def self.parse_one_chunk_result(str)
  parser = new
  results = []
  parser.feed(str.to_s.b) { |chunk| results << chunk }
  if results.empty?
    raise ReactOnRails::Error,
          "Malformed render result: expected exactly one length-prefixed chunk but found none"
  end
  if results.size > 1
    raise ReactOnRails::Error,
          "Malformed render result: expected exactly one length-prefixed chunk but found #{results.size}"
  end
  results.first
end

Instance Method Details

#error?Boolean

True if the parser encountered a protocol error.

Returns:

  • (Boolean)


78
79
80
# File 'lib/react_on_rails/length_prefixed_parser.rb', line 78

def error?
  @state == :error
end

#feed(chunk, &block) ⇒ Object

Appends bytes to buffer and yields complete chunks as they become available. Yields Hash: { “html” => content, …metadata } Raises on protocol errors (bad JSON, bad hex, missing tab). After an error, the parser enters :error state and all subsequent calls are no-ops.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/react_on_rails/length_prefixed_parser.rb', line 48

def feed(chunk, &block)
  return if @state == :error

  @buf << (chunk.encoding == Encoding::BINARY ? chunk : chunk.b)

  loop do
    case @state
    when :header
      break unless try_parse_header(&block)
    when :content
      break unless try_read_content(&block)
    end
  end
rescue StandardError
  @state = :error
  raise
end

#flushObject

Called when the stream ends to detect truncated responses. Logs a warning if the buffer still has unconsumed bytes (partial header or content).



68
69
70
71
72
73
74
75
# File 'lib/react_on_rails/length_prefixed_parser.rb', line 68

def flush
  return if @state == :header && @buf.empty?

  Rails.logger.warn(
    "[react_on_rails] Incomplete length-prefixed stream: " \
    "#{@buf.bytesize} bytes remaining in state :#{@state}"
  )
end