Class: Dommy::Rack::Response

Inherits:
Object
  • Object
show all
Defined in:
lib/dommy/rack/response.rb

Overview

Wraps a single completed Rack response triple plus the absolute URL it was fetched at. The body is drained eagerly (Rack bodies are one-shot) and the Dommy document is parsed lazily on first access.

Constant Summary collapse

HTML_CONTENT_TYPES =
["text/html", "application/xhtml+xml"].freeze
REDIRECT_STATUSES =
[301, 302, 303, 307, 308].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(status, headers, body, url:) ⇒ Response

Returns a new instance of Response.



21
22
23
24
25
26
27
28
29
# File 'lib/dommy/rack/response.rb', line 21

def initialize(status, headers, body, url:)
  @status = status.to_i
  @headers = headers || {}
  @url = url
  @body = drain_body(body)
  @document_parsed = false
  @window = nil
  @redirects = []
end

Instance Attribute Details

#headersObject (readonly)

Returns the value of attribute headers.



14
15
16
# File 'lib/dommy/rack/response.rb', line 14

def headers
  @headers
end

#redirectsObject

The redirects followed to reach this response, oldest first. Each entry is url:, location:. Empty unless this was the final response of a followed redirect chain. Set by Navigation.



19
20
21
# File 'lib/dommy/rack/response.rb', line 19

def redirects
  @redirects
end

#statusObject (readonly)

Returns the value of attribute status.



14
15
16
# File 'lib/dommy/rack/response.rb', line 14

def status
  @status
end

#urlObject (readonly)

Returns the value of attribute url.



14
15
16
# File 'lib/dommy/rack/response.rb', line 14

def url
  @url
end

Instance Method Details

#bodyObject



31
32
33
# File 'lib/dommy/rack/response.rb', line 31

def body
  @body
end

#client_error?Boolean

Returns:

  • (Boolean)


70
# File 'lib/dommy/rack/response.rb', line 70

def client_error? = (400..499).cover?(@status)

#content_typeObject

Content-Type with any parameters (charset, boundary) stripped.



36
37
38
39
40
41
# File 'lib/dommy/rack/response.rb', line 36

def content_type
  raw = header("content-type")
  return nil unless raw

  raw.split(";", 2).first.to_s.strip.downcase
end

#documentObject



110
111
112
# File 'lib/dommy/rack/response.rb', line 110

def document
  window&.document
end

#error?Boolean

Returns:

  • (Boolean)


72
# File 'lib/dommy/rack/response.rb', line 72

def error? = @status >= 400

#html?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/dommy/rack/response.rb', line 43

def html?
  HTML_CONTENT_TYPES.include?(content_type)
end

#json(symbolize_names: false) ⇒ Object

The parsed JSON body. Parses regardless of Content-Type so that servers mislabeling JSON still work; raises JSON::ParserError on invalid JSON. Pass symbolize_names: true for symbol keys.



59
60
61
# File 'lib/dommy/rack/response.rb', line 59

def json(symbolize_names: false)
  JSON.parse(@body, symbolize_names: symbolize_names)
end

#json?Boolean

True when the response advertises a JSON content type, including structured-suffix types such as application/vnd.api+json.

Returns:

  • (Boolean)


49
50
51
52
53
54
# File 'lib/dommy/rack/response.rb', line 49

def json?
  ct = content_type
  return false unless ct

  ct == "application/json" || ct == "text/json" || ct.end_with?("+json")
end

#location_headerObject



75
76
77
# File 'lib/dommy/rack/response.rb', line 75

def location_header
  header("location")
end

#meta_refresh_urlObject

The redirect target of an immediate (delay 0) <meta http-equiv= “refresh”>, or nil. The HTML analog of location_header. A self-refresh with no URL is ignored to avoid reload loops; non-HTML responses and non-zero delays return nil.



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/dommy/rack/response.rb', line 83

def meta_refresh_url
  return nil unless html?

  meta = document&.query_selector_all("meta")
    &.find { |m| m.get_attribute("http-equiv")&.downcase == "refresh" }
  return nil unless meta

  delay, _, rest = meta.get_attribute("content").to_s.partition(";")
  return nil unless delay.strip.match?(/\A0+\z/)

  url = rest.strip.sub(/\Aurl\s*=\s*/i, "").delete("\"'").strip
  url.empty? ? nil : url
end

#not_found?Boolean

Returns:

  • (Boolean)


73
# File 'lib/dommy/rack/response.rb', line 73

def not_found? = @status == 404

#redirect?Boolean

Returns:

  • (Boolean)


63
64
65
# File 'lib/dommy/rack/response.rb', line 63

def redirect?
  REDIRECT_STATUSES.include?(@status)
end

#server_error?Boolean

Returns:

  • (Boolean)


71
# File 'lib/dommy/rack/response.rb', line 71

def server_error? = (500..599).cover?(@status)

All Set-Cookie values, handling both single-string (newline-joined) and array header shapes across Rack 2 and Rack 3.



99
100
101
102
# File 'lib/dommy/rack/response.rb', line 99

def set_cookie_strings
  values = lookup_header("set-cookie")
  Array(values).flat_map { |v| v.to_s.split("\n") }.reject(&:empty?)
end

#success?Boolean

— Status-class predicates —

Returns:

  • (Boolean)


69
# File 'lib/dommy/rack/response.rb', line 69

def success? = (200..299).cover?(@status)

#windowObject

The parsed Dommy window, or nil for non-HTML responses.



105
106
107
108
# File 'lib/dommy/rack/response.rb', line 105

def window
  parse_document! unless @document_parsed
  @window
end