Class: Hanami::Action::Response Private

Inherits:
Rack::Response
  • Object
show all
Defined in:
lib/hanami/action/response.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

The HTTP response for an action, given to #handle.

Inherits from Rack::Response, providing compatibility with Rack functionality.

Constant Summary collapse

DEFAULT_VIEW_OPTIONS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0

-> (*) { {} }.freeze
EMPTY_BODY =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0

[].freeze
FILE_SYSTEM_ROOT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0

Pathname.new("/").freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request:, config:, content_type: nil, charset: nil, env: {}, headers: {}, view_options: nil, session_enabled: false) ⇒ Response

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Response.

Since:

  • 2.0.0



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/hanami/action/response.rb', line 51

def initialize(request:, config:, content_type: nil, charset: nil, env: {}, headers: {}, view_options: nil, session_enabled: false) # rubocop:disable Layout/LineLength
  # `Rack::Response#initialize` copies the headers into its own internal store on both Rack
  # 2.2+ (`Utils::HeaderHash[headers]`) and Rack 3.x (`Headers.new` + per-entry copy), so it
  # never aliases the hash passed in. That means we can skip a defensive `headers.dup` here
  # and avoid an allocation per request without risk of pollution. See the regression spec
  # in `spec/unit/hanami/action/response_spec.rb` if you're tempted to add one back.
  super([], 200, headers)
  self.content_type = content_type if content_type

  @request = request
  @config = config
  @charset = charset
  @exposures = {}
  @env = env
  @view_options = view_options || DEFAULT_VIEW_OPTIONS

  @session_enabled = session_enabled
  @sending_file = false
end

Instance Attribute Details

#charsetObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



37
38
39
# File 'lib/hanami/action/response.rb', line 37

def charset
  @charset
end

#envObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



33
34
35
# File 'lib/hanami/action/response.rb', line 33

def env
  @env
end

#exposuresObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



33
34
35
# File 'lib/hanami/action/response.rb', line 33

def exposures
  @exposures
end

#requestObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



33
34
35
# File 'lib/hanami/action/response.rb', line 33

def request
  @request
end

#view_optionsObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



33
34
35
# File 'lib/hanami/action/response.rb', line 33

def view_options
  @view_options
end

Class Method Details

.build(status, env) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



41
42
43
44
45
46
47
# File 'lib/hanami/action/response.rb', line 41

def self.build(status, env)
  new(config: Action.config.dup, content_type: Mime.best_q_match(env[Action::HTTP_ACCEPT]), env: env).tap do |r|
    r.status = status
    r.body   = Http::Status.message_for(status)
    r.set_format(Mime.format_from_media_type(r.content_type), config)
  end
end

Instance Method Details

#[](key) ⇒ Object

Returns the exposure value for the given key.

Parameters:

  • key (Object)

Returns:

  • (Object)

    the exposure value, if found

Raises:

  • (KeyError)

    if the exposure was not found

Since:

  • 2.0.0



194
195
196
# File 'lib/hanami/action/response.rb', line 194

def [](key)
  @exposures.fetch(key)
end

#[]=(key, value) ⇒ Object

Sets an exposure value for the given key.

Parameters:

  • key (Object)
  • value (Object)

Returns:

  • (Object)

    the value

Since:

  • 2.0.0



207
208
209
# File 'lib/hanami/action/response.rb', line 207

def []=(key, value)
  @exposures[key] = value
end

#_send_file(send_file_response) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/hanami/action/response.rb', line 477

def _send_file(send_file_response)
  headers.merge!(send_file_response[Action::RESPONSE_HEADERS])

  if send_file_response[Action::RESPONSE_CODE] == Action::NOT_FOUND
    headers.delete(Action::X_CASCADE)
    headers.delete(Action::CONTENT_LENGTH)
    Halt.call(Action::NOT_FOUND)
  else
    self.status = send_file_response[Action::RESPONSE_CODE]
    self.body = send_file_response[Action::RESPONSE_BODY]
    @sending_file = true
  end
end

#allow_redirect?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

Since:

  • 2.0.0



459
460
461
462
463
# File 'lib/hanami/action/response.rb', line 459

def allow_redirect?
  return body.empty? if body.respond_to?(:empty?)

  !@sending_file
end

#body=(str) ⇒ Object

Sets the response body.

Parameters:

  • str (String)

    the body string

Since:

  • 2.0.0



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/hanami/action/response.rb', line 77

def body=(str)
  @length = 0

  if str.nil? || str == EMPTY_BODY
    @body = EMPTY_BODY
    return
  end

  @body = []

  if str.is_a?(::Rack::Files::BaseIterator)
    @body = str
    buffered_body! # Ensure appropriate content-length is set
  else
    write(str)
  end
end

#cache_control(*values) ⇒ Object

Specifies the response freshness policy for HTTP caches using the Cache-Control header.

Directives come in two forms:

  • Non-value directives (such as :public or :no_store) are passed as bare symbols.
  • Value directives (such as max_age: or s_maxage:) are passed as a trailing Hash.

See the @option entries below for the full set of supported directives. Passing an unknown directive raises an ArgumentError.

See MDN's Cache-Control reference for more on the standard cache control directives.

Examples:

# Set Cache-Control directives
response.cache_control :public, max_age: 900, s_maxage: 86400

# Overwrite previous Cache-Control directives
response.cache_control :private, :no_cache, :no_store

response.get_header("Cache-Control") # => "private, no-store, max-age=900"

Parameters:

  • values (Array<Symbol, Hash>)

    values to map to Cache-Control directives

Options Hash (*values):

  • :immutable (Symbol)
  • :must_revalidate (Symbol)
  • :must_understand (Symbol)
  • :no_cache (Symbol)
  • :no_store (Symbol)
  • :no_transform (Symbol)
  • :private (Symbol)
  • :proxy_revalidate (Symbol)
  • :public (Symbol)
  • :max_age (Hash)
  • :max_stale (Hash)
  • :min_fresh (Hash)
  • :s_maxage (Hash)
  • :stale_if_error (Hash)
  • :stale_while_revalidate (Hash)

Returns:

  • void

Since:

  • 2.0.0



377
378
379
380
# File 'lib/hanami/action/response.rb', line 377

def cache_control(*values)
  directives = Cache::CacheControl::Directives.new(*values)
  headers.merge!(directives.headers)
end

#cookiesCookieJar

Returns the set of cookies to be included in the response.

Returns:

Since:

  • 2.0.0



267
268
269
# File 'lib/hanami/action/response.rb', line 267

def cookies
  @cookies ||= CookieJar.new(env.dup, headers, @config.cookies)
end

#expires(amount, *values) ⇒ Object

Sets the Expires header and Cache-Control/max-age directive for the response.

You can provide an integer number of seconds in the future, or a Time object indicating when the response should be considered "stale". The remaining arguments are passed to #cache_control.

Examples:

# Set Cache-Control directives and Expires
response.expires 900, :public

# Overwrite Cache-Control directives and Expires
response.expires 300, :private, :no_cache, :no_store

response.get_header("Expires") # => "Thu, 26 Jun 2014 12:00:00 GMT"
response.get_header("Cache-Control") # => "private, no-cache, no-store max-age=300"

Parameters:

  • amount (Integer, Time)

    number of seconds or point in time

  • values (Array<Symbols>)

    values to map to Cache-Control directives via #cache_control

Returns:

  • void

Since:

  • 2.0.0



406
407
408
409
# File 'lib/hanami/action/response.rb', line 406

def expires(amount, *values)
  directives = Cache::Expires::Directives.new(amount, *values)
  headers.merge!(directives.headers)
end

#flashFlash

Returns the flash for the request.

This is the same flash object as the Hanami::Action::Request.

Returns:

Raises:

See Also:

Since:

  • 2.0.0



253
254
255
256
257
258
259
# File 'lib/hanami/action/response.rb', line 253

def flash
  unless session_enabled?
    raise Hanami::Action::MissingSessionError.new("Hanami::Action::Response#flash")
  end

  request.flash
end

#formatSymbol?

Returns the format for the response.

Returns nil if a format has not been assigned and also cannot be determined from the response's #content_type.

Examples:

response.format # => :json

Returns:

  • (Symbol, nil)

Since:

  • 2.0.0



145
146
147
# File 'lib/hanami/action/response.rb', line 145

def format
  @format ||= Mime.format_from_media_type(content_type, @config)
end

#format=(value) ⇒ Object

Sets the format and associated content type for the response.

Either a format name (:json) or a MIME type ("application/json") may be given. In either case, the format or content type will be derived from the given value, and both will be set.

Providing an unknown format name will raise an UnknownFormatError.

Providing an unknown MIME type will set the content type and set the format as nil.

Examples:

Assigning via a format name symbol

response.format = :json
response.content_type # => "application/json"
response.headers["Content-Type"] # => "application/json"

Assigning via a content type string

response.format = "application/json"
response.format # => :json
response.content_type # => "application/json"

Parameters:

  • value (Symbol, String)

    the format name or content type

Raises:

See Also:

Since:

  • 2.0.0



176
177
178
179
180
181
182
# File 'lib/hanami/action/response.rb', line 176

def format=(value)
  format, content_type = Mime.format_and_media_type(value, @config)

  self.content_type = Mime.content_type_with_charset(content_type, charset)

  @format = format
end

#fresh(options) ⇒ Object

Sets the etag and/or last_modified headers on the response and halts with a 304 Not Modified response if the request is still fresh according to the IfNoneMatch and IfModifiedSince request headers.

Examples:

# Set etag header and halt 304 if request matches IF_NONE_MATCH header
response.fresh etag: some_resource.updated_at.to_i

# Set last_modified header and halt 304 if request matches IF_MODIFIED_SINCE
response.fresh last_modified: some_resource.updated_at

# Set etag and last_modified header and halt 304 if request matches IF_MODIFIED_SINCE and IF_NONE_MATCH
response.fresh last_modified: some_resource.updated_at

Parameters:

  • options (Hash)

Options Hash (options):

  • :etag (Integer)

    for testing IfNoneMatch conditions

  • :last_modified (Date)

    for testing IfModifiedSince conditions

Returns:

  • void

Since:

  • 2.0.0



433
434
435
436
437
438
439
440
441
# File 'lib/hanami/action/response.rb', line 433

def fresh(options)
  conditional_get = Cache::ConditionalGet.new(env, options)

  headers.merge!(conditional_get.headers)

  conditional_get.fresh? do
    Halt.call(304)
  end
end

#head?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

Since:

  • 2.0.0



471
472
473
# File 'lib/hanami/action/response.rb', line 471

def head?
  env[Action::REQUEST_METHOD] == Action::HEAD
end

#redirect_to(url, status: 302) ⇒ Object

Sets the response to redirect to the given URL and halts further handling.

Parameters:

  • url (String)
  • status (Integer) (defaults to: 302)

    the HTTP status to use for the redirect

Since:

  • 2.0.0



278
279
280
281
282
283
# File 'lib/hanami/action/response.rb', line 278

def redirect_to(url, status: 302)
  return unless allow_redirect?

  redirect(::String.new(url), status)
  Halt.call(status)
end

#render(view, **input) ⇒ Object

Sets the response body from the rendered view.

Parameters:

  • view (Hanami::View)

    the view to render

  • input (Hash)

    keyword arguments to pass to the view's #call method

Since:

  • 2.1.0



123
124
125
126
127
128
129
130
131
# File 'lib/hanami/action/response.rb', line 123

def render(view, **input)
  view_input = {
    **view_options.call(request, self),
    **exposures,
    **input
  }

  self.body = view.call(**view_input).to_str
end

#renderable?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)

Since:

  • 2.0.0



451
452
453
454
455
# File 'lib/hanami/action/response.rb', line 451

def renderable?
  return !head? && body.empty? if body.respond_to?(:empty?)

  !@sending_file && !head?
end

#send_file(path) ⇒ void

This method returns an undefined value.

Sends the file at the given path as the response, for any file within the configured public_directory.

Handles the following aspects for file responses:

  • Setting Content-Type and Content-Length headers
  • File Not Found responses (returns a 404)
  • Conditional GET (via If-Modified-Since header)
  • Range requests (via Range header)

Parameters:

  • path (String)

    the file path

See Also:

Since:

  • 2.0.0



304
305
306
307
308
# File 'lib/hanami/action/response.rb', line 304

def send_file(path)
  _send_file(
    Action::Rack::File.new(path, @config.public_directory).call(env)
  )
end

#sessionHash

Returns the session for the response.

This is the same session object as the Hanami::Action::Request.

Returns:

  • (Hash)

    the session object

Raises:

See Also:

Since:

  • 2.0.0



233
234
235
236
237
238
239
# File 'lib/hanami/action/response.rb', line 233

def session
  unless session_enabled?
    raise Hanami::Action::MissingSessionError.new("Hanami::Action::Response#session")
  end

  request.session
end

#session_enabled?Boolean

Returns true if the session is enabled for the request.

Returns:

  • (Boolean)

Since:

  • 2.1.0



217
218
219
# File 'lib/hanami/action/response.rb', line 217

def session_enabled?
  @session_enabled
end

#set_format(value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Since:

  • 2.0.0



445
446
447
# File 'lib/hanami/action/response.rb', line 445

def set_format(value) # rubocop:disable Naming/AccessorMethodName
  @format = value
end

#status=(code) ⇒ Object

Sets the response status.

Examples:

response.status = :unprocessable_entity
response.status = 422

Parameters:

  • code (Integer, Symbol)

    the status code

Raises:

See Also:

Since:

  • 2.0.2



112
113
114
# File 'lib/hanami/action/response.rb', line 112

def status=(code)
  super(Http::Status.lookup(code))
end

#unsafe_send_file(path) ⇒ void

This method returns an undefined value.

Send the file at the given path as the response, for a file anywhere in the file system.

Parameters:

  • path (String, Pathname)

    path to the file to be sent

See Also:

Since:

  • 2.0.0



321
322
323
324
325
326
327
328
329
330
331
# File 'lib/hanami/action/response.rb', line 321

def unsafe_send_file(path)
  directory = if Pathname.new(path).relative?
                @config.root_directory
              else
                FILE_SYSTEM_ROOT
              end

  _send_file(
    Action::Rack::File.new(path, directory).call(env)
  )
end