Class: Hyperion::ResponseWriter
- Inherits:
-
Object
- Object
- Hyperion::ResponseWriter
- Defined in:
- lib/hyperion/response_writer.rb
Overview
Serializes a Rack [status, headers, body] tuple to an HTTP/1.1 wire stream. Phase 5 adds a chunked-streaming path with per-connection write coalescing; Phase 7 adds a sibling Http2ResponseWriter. Public surface (#write) stays stable.
Defined Under Namespace
Classes: ChunkedCoalescer
Constant Summary collapse
- COALESCE_SMALL_CHUNK_BYTES =
Phase 5 — chunked-write coalescing tunables. Chunks smaller than the threshold accumulate in a per-response buffer; the buffer flushes on any of (a) >= COALESCE_FLUSH_BYTES filled, (b) the writer-fiber tick of COALESCE_TICK_SECONDS elapsed since the last buffer drain, or © end-of-body / explicit body.flush. Picked to keep added latency under 1 ms while still cutting syscall count 3-5× on SSE / streaming JSON / log-tail workloads where per-event payloads are ~50 B.
512- COALESCE_FLUSH_BYTES =
4096- COALESCE_TICK_SECONDS =
0.001- CHUNKED_TERMINATOR =
"0\r\n\r\n"- REASONS =
{ 200 => 'OK', 201 => 'Created', 204 => 'No Content', 301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified', 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 413 => 'Payload Too Large', 414 => 'URI Too Long', 422 => 'Unprocessable Entity', 429 => 'Too Many Requests', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout' }.freeze
- CRLF_HEADER_VALUE =
/[\r\n]/
Class Attribute Summary collapse
-
.page_cache_available ⇒ Object
Returns the value of attribute page_cache_available.
Class Method Summary collapse
Instance Method Summary collapse
-
#write(io, status, headers, body, keep_alive: false, dispatch_mode: nil) ⇒ Object
2.6-C — ‘dispatch_mode:` is the per-response opt-in dispatch shape (typically `:inline_blocking` for static-file routes auto-detected by `Adapter::Rack#call`, or `nil` for the default fiber-yielding path).
Class Attribute Details
.page_cache_available ⇒ Object
Returns the value of attribute page_cache_available.
60 61 62 |
# File 'lib/hyperion/response_writer.rb', line 60 def page_cache_available @page_cache_available end |
Class Method Details
.page_cache_available? ⇒ Boolean
62 63 64 |
# File 'lib/hyperion/response_writer.rb', line 62 def page_cache_available? @page_cache_available end |
Instance Method Details
#write(io, status, headers, body, keep_alive: false, dispatch_mode: nil) ⇒ Object
2.6-C — ‘dispatch_mode:` is the per-response opt-in dispatch shape (typically `:inline_blocking` for static-file routes auto-detected by `Adapter::Rack#call`, or `nil` for the default fiber-yielding path). Only the sendfile branch consumes it today; the chunked and buffered branches ignore it (no fiber-yield in their hot loop to begin with). Forward-compatible — future per-response dispatch shapes plug in here without changing the call-site arity for non-sendfile branches.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/hyperion/response_writer.rb', line 78 def write(io, status, headers, body, keep_alive: false, dispatch_mode: nil) # Zero-copy fast path: bodies that point at an on-disk file (Rack::Files, # asset servers, signed-download responders) get streamed via # IO.copy_stream which delegates to sendfile(2) on Linux for plain TCP # sockets — bytes go from the file's page cache straight to the socket # buffer with no userspace allocation. For TLS sockets we still avoid the # multi-MB String build, but encryption forces a userspace round-trip so # we count that path separately. Phase 5 leaves this branch untouched — # sendfile bypasses the chunked coalescer entirely (the file IS the body # buffer, no userspace chunks to coalesce). if body.respond_to?(:to_path) return write_sendfile(io, status, headers, body, keep_alive: keep_alive, dispatch_mode: dispatch_mode) end # Phase 5 — opt-in chunked streaming path. The app sets # `Transfer-Encoding: chunked` to signal "this body is a stream; do not # buffer". We then iterate `body.each` and emit each chunk in chunked # framing (size-line + payload + CRLF), coalescing chunks <512 B in a # per-response buffer to cut syscall count on SSE / streaming JSON. return write_chunked(io, status, headers, body, keep_alive: keep_alive) if chunked_transfer?(headers) write_buffered(io, status, headers, body, keep_alive: keep_alive) end |