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 replaces this with an io_buffer-batched writer; Phase 7 adds a sibling Http2ResponseWriter. Public surface (#write) stays stable.

Constant Summary collapse

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]/

Instance Method Summary collapse

Instance Method Details

#write(io, status, headers, body, keep_alive: false) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/hyperion/response_writer.rb', line 38

def write(io, status, headers, body, keep_alive: false)
  # Phase 1 buffers the full body so Content-Length is exact.
  # Phase 2 introduces chunked transfer-encoding for streaming bodies;
  # Phase 5 batches via IO::Buffer to avoid this intermediate String.
  buffered = +''
  body.each { |chunk| buffered << chunk }

  reason = REASONS[status] || 'Unknown'
  date_str = Time.now.httpdate

  head = build_head(status, reason, headers, buffered.bytesize, keep_alive, date_str)

  # Phase 8 perf fix: coalesce status line + all headers + body into a
  # SINGLE io.write call. Each syscall round-trip is ~1 usec on macOS
  # kqueue; before this change we issued (1 status) + (N headers) + (1 blank)
  # + (1 body) = 8+ syscalls per response. Now: 1 syscall.
  if buffered.empty?
    io.write(head)
  else
    # Concatenate into the head buffer (which is already a fresh +'' from
    # the C builder or the Ruby fallback) so we still emit a single write.
    head << buffered
    io.write(head)
  end
ensure
  body.close if body.respond_to?(:close)
end