Class: ReactOnRails::LengthPrefixedParser
- Inherits:
-
Object
- Object
- ReactOnRails::LengthPrefixedParser
- 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
-
.parse_one_chunk_result(str) ⇒ Object
Parses a complete length-prefixed result string that must contain exactly one chunk.
Instance Method Summary collapse
-
#error? ⇒ Boolean
True if the parser encountered a protocol error.
-
#feed(chunk, &block) ⇒ Object
Appends bytes to buffer and yields complete chunks as they become available.
-
#flush ⇒ Object
Called when the stream ends to detect truncated responses.
-
#initialize ⇒ LengthPrefixedParser
constructor
A new instance of LengthPrefixedParser.
Constructor Details
#initialize ⇒ LengthPrefixedParser
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.
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 |
#flush ⇒ Object
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 |