Class: Hyperion::ResponseWriter

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.page_cache_availableObject

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

Returns:

  • (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