Class: Stipa::Response
- Inherits:
-
Object
- Object
- Stipa::Response
- Defined in:
- lib/stipa/response.rb
Overview
Builds a valid HTTP/1.1 response and serializes it to wire bytes.
Design notes:
- Content-Length is ALWAYS computed from body.bytesize in to_http,
never stored manually. This prevents handler authors from setting
a wrong value and makes binary correctness automatic.
- Header names are stored in Title-Case for wire compatibility but
set_header accepts any casing for developer ergonomics.
- Keep-alive vs Connection:close is decided in to_http based on the
request's HTTP version, so handler code never needs to think about it.
- The Date header is injected automatically — required by RFC 7231.
Constant Summary collapse
- STATUS_MESSAGES =
{ 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', 413 => 'Payload Too Large', 422 => 'Unprocessable Entity', 429 => 'Too Many Requests', 500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', }.freeze
Instance Attribute Summary collapse
-
#body ⇒ Object
Returns the value of attribute body.
-
#headers ⇒ Object
readonly
Returns the value of attribute headers.
-
#status ⇒ Object
Returns the value of attribute status.
-
#template_engine ⇒ Object
Returns the value of attribute template_engine.
Instance Method Summary collapse
- #[](name) ⇒ Object
-
#initialize ⇒ Response
constructor
A new instance of Response.
-
#json(data) ⇒ Object
Set body to a JSON representation of ‘data` and set Content-Type.
-
#render(template, locals: {}, layout: :default) ⇒ Object
Render an ERB template and set the body + Content-Type to text/html.
-
#set_header(name, value) ⇒ Object
(also: #[]=)
Set a response header.
-
#to_http(req = nil) ⇒ Object
Serialize to the exact HTTP/1.1 bytes to write to the socket.
Constructor Details
#initialize ⇒ Response
Returns a new instance of Response.
41 42 43 44 45 46 |
# File 'lib/stipa/response.rb', line 41 def initialize @status = 200 @headers = {} @body = '' @template_engine = nil end |
Instance Attribute Details
#body ⇒ Object
Returns the value of attribute body.
38 39 40 |
# File 'lib/stipa/response.rb', line 38 def body @body end |
#headers ⇒ Object (readonly)
Returns the value of attribute headers.
39 40 41 |
# File 'lib/stipa/response.rb', line 39 def headers @headers end |
#status ⇒ Object
Returns the value of attribute status.
38 39 40 |
# File 'lib/stipa/response.rb', line 38 def status @status end |
#template_engine ⇒ Object
Returns the value of attribute template_engine.
38 39 40 |
# File 'lib/stipa/response.rb', line 38 def template_engine @template_engine end |
Instance Method Details
#[](name) ⇒ Object
55 56 57 |
# File 'lib/stipa/response.rb', line 55 def [](name) @headers[titlecase(name)] end |
#json(data) ⇒ Object
Set body to a JSON representation of ‘data` and set Content-Type. Returns self so it can be used as the last expression in a handler.
82 83 84 85 86 |
# File 'lib/stipa/response.rb', line 82 def json(data) @body = JSON.generate(data) set_header('Content-Type', 'application/json; charset=utf-8') self end |
#render(template, locals: {}, layout: :default) ⇒ Object
Render an ERB template and set the body + Content-Type to text/html.
Requires a template engine to be configured on the app:
app = Stipa::App.new(views: 'views')
Examples:
res.render('home')
res.render('users/show', locals: { user: @user })
res.render('welcome', locals: { name: 'Alice' }, layout: false)
res.render('dashboard', layout: 'layouts/admin')
Returns self for chaining.
71 72 73 74 75 76 77 78 |
# File 'lib/stipa/response.rb', line 71 def render(template, locals: {}, layout: :default) raise 'No template engine configured. Pass views: "path" to Stipa::App.new.' \ unless @template_engine set_header('Content-Type', 'text/html; charset=utf-8') @body = @template_engine.render(template, locals: locals, layout: layout) self end |
#set_header(name, value) ⇒ Object Also known as: []=
Set a response header. Name is normalized to Title-Case. Accepts any casing: set_header(‘content-type’, ‘text/html’) is fine.
50 51 52 |
# File 'lib/stipa/response.rb', line 50 def set_header(name, value) @headers[titlecase(name)] = value.to_s end |
#to_http(req = nil) ⇒ Object
Serialize to the exact HTTP/1.1 bytes to write to the socket. ‘req` is used to decide the Connection header (keep-alive or close).
90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/stipa/response.rb', line 90 def to_http(req = nil) # Force binary encoding so bytesize is always the byte count, # not the character count (matters for multi-byte UTF-8 bodies). body_bytes = @body.to_s.b finalize_headers(body_bytes, req) status_text = STATUS_MESSAGES.fetch(@status, 'Unknown') status_line = "HTTP/1.1 #{@status} #{status_text}" header_block = @headers.map { |k, v| "#{k}: #{v}" }.join("\r\n") # RFC 7230: blank line (CRLF CRLF) separates header block from body "#{status_line}\r\n#{header_block}\r\n\r\n#{body_bytes}" end |