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
65
66
67
68
# 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.
  bytes_out = if buffered.empty?
                io.write(head)
                head.bytesize
              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)
                head.bytesize
              end
  Hyperion.metrics.increment(:bytes_written, bytes_out)
ensure
  body.close if body.respond_to?(:close)
end