Class: Cybertrain::Response

Inherits:
Object
  • Object
show all
Defined in:
cybertrain/http/response.rb

Overview

The response a request builds up on its way through the stack; the Server serializes it with #to_http. Headers keep the case they were set with, and Set-Cookie values live in their own list because one response may carry several.

Controller#response in an action. Controller#render, Controller#redirect_to and Controller#head set the status and body; use this object for headers and cookies. Content-Length is always computed, and Content-Type defaults to text/html; charset=utf-8.

Examples:

response.set_header("Cache-Control", "no-store")
response.add_cookie(Cybertrain::Cookies.serialize("theme", "dark", max_age: 31_536_000))

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#body ⇒ String

The body, "" until set.

Returns:

  • (String)


36
37
38
# File 'cybertrain/http/response.rb', line 36

def body
  @body
end

#cookies ⇒ Array<String> (readonly)

The Set-Cookie values added so far.

Returns:

  • (Array<String>)


47
48
49
# File 'cybertrain/http/response.rb', line 47

def cookies
  @cookies
end

#headers ⇒ Hash{String => String} (readonly)

The headers set so far, in the case they were set with; change them through #set_header and #drop_header.

Returns:

  • (Hash{String => String})


42
43
44
# File 'cybertrain/http/response.rb', line 42

def headers
  @headers
end

#status ⇒ Integer

The status code, 200 until set. A code missing from STATUS_TEXT (401, 409, …) is sent with the reason phrase “Unknown”.

Returns:

  • (Integer)


31
32
33
# File 'cybertrain/http/response.rb', line 31

def status
  @status
end

Instance Method Details

Adds a Set-Cookie header; build the value with Cookies.serialize. Raises ArgumentError when the value contains CR or LF.

Parameters:

  • set_cookie_value (String)


150
151
152
153
# File 'cybertrain/http/response.rb', line 150

def add_cookie(set_cookie_value)
  reject_crlf!("Set-Cookie", set_cookie_value)
  @cookies << set_cookie_value
end

#content_type=(value) ⇒ Object

Parameters:

  • value (String) —

    e.g. "text/csv; charset=utf-8"



142
143
144
# File 'cybertrain/http/response.rb', line 142

def content_type=(value)
  set_header("Content-Type", value)
end

#drop_header(name) ⇒ nil

Removes the header of that name, whatever its case (every spelling when several were stored); a missing one is not an error. Returns nil (NOTES rules 10/34: one return type for the name). Edits @headers in place: headers is a public reader, so a Hash a before_action or middleware took earlier must still be the live one afterwards. The matching keys are collected first (never delete while iterating) and removed with Hash#delete; no block-taking Hash method is needed.

Parameters:

  • name (String)

Returns:

  • (nil)


125
126
127
128
129
130
# File 'cybertrain/http/response.rb', line 125

def drop_header(name)
  gone = []
  @headers.keys.each { |k| gone << k if HeaderName.same?(k, name) }
  gone.each { |k| @headers.delete(k) }
  nil
end

#header(name) ⇒ String?

Parameters:

  • name (String) —

    any case

Returns:

  • (String, nil)


135
136
137
138
# File 'cybertrain/http/response.rb', line 135

def header(name)
  key = header_key(name)
  key.nil? ? nil : @headers[key]
end

#set_header(name, value) ⇒ Object

Replaces any existing header of the same name, whatever its case, keeping its position in the output. Raises ArgumentError when the name or value contains CR or LF (header injection / response splitting).

Parameters:

  • name (String)
  • value (String)

Raises:

  • (ArgumentError) —

    on a CR or LF



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'cybertrain/http/response.rb', line 94

def set_header(name, value)
  reject_crlf!(name, name)
  reject_crlf!(name, value)
  existing = header_key(name)
  if existing.nil? || existing == name
    @headers[name] = value
  else
    # Rebuilt in place (clear, then put back in order): `headers` is a
    # public reader, so a Hash a before_action or middleware kept must
    # stay the live one.
    names = []
    values = []
    @headers.each do |k, v|
      names << (k == existing ? name : k)
      values << v
    end
    @headers.clear
    names.each_with_index { |k, i| @headers[k] = k == name ? value : values[i] }
  end
end