class Hanami::Action::Response

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

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

@see www.rubydoc.info/gems/rack/Rack/Response

@since 2.0.0 @api private

Constants

DEFAULT_VIEW_OPTIONS

@since 2.0.0 @api private

EMPTY_BODY

@since 2.0.0 @api private

FILE_SYSTEM_ROOT

@since 2.0.0 @api private

Attributes

charset[RW]

@since 2.0.0 @api private

env[R]

@since 2.0.0 @api private

exposures[R]

@since 2.0.0 @api private

request[R]

@since 2.0.0 @api private

view_options[R]

@since 2.0.0 @api private

Public Class Methods

build(status, env) click to toggle source

@since 2.0.0 @api private

# 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.detect_format(r.content_type), config)
  end
end
new(request:, config:, content_type: nil, env: {}, headers: {}, view_options: nil, session_enabled: false) click to toggle source

@since 2.0.0 @api private

Calls superclass method
# File lib/hanami/action/response.rb, line 51
def initialize(request:, config:, content_type: nil, env: {}, headers: {}, view_options: nil, session_enabled: false) # rubocop:disable Layout/LineLength, Metrics/ParameterLists
  super([], 200, headers.dup)
  self.content_type = content_type if content_type

  @request = request
  @config = config
  @charset = ::Rack::MediaType.params(content_type).fetch("charset", nil)
  @exposures = {}
  @env = env
  @view_options = view_options || DEFAULT_VIEW_OPTIONS

  @session_enabled = session_enabled
  @sending_file = false
end

Public Instance Methods

[](key) click to toggle source

Returns the exposure value for the given key.

@param key [Object]

@return [Object] the exposure value, if found

@raise [KeyError] if the exposure was not found

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 182
def [](key)
  @exposures.fetch(key)
end
[]=(key, value) click to toggle source

Sets an exposure value for the given key.

@param key [Object] @param value [Object]

@return [Object] the value

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 195
def []=(key, value)
  @exposures[key] = value
end
_send_file(send_file_response) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/action/response.rb, line 454
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?() click to toggle source

@since 2.0.0 @api private

# File lib/hanami/action/response.rb, line 436
def allow_redirect?
  return body.empty? if body.respond_to?(:empty?)

  !@sending_file
end
body=(str) click to toggle source

Sets the response body.

@param str [String] the body string

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 72
def body=(str)
  @length = 0
  @body   = EMPTY_BODY.dup

  if str.is_a?(::Rack::Files::BaseIterator)
    @body = str
  else
    write(str) unless str.nil? || str == EMPTY_BODY
  end
end
cache_control(*values) click to toggle source

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

Any number of non-value directives (‘:public`, `:private`, `:no_cache`, `:no_store`, `:must_revalidate`, `:proxy_revalidate`) may be passed along with a Hash of value directives (`:max_age`, `:min_stale`, `:s_max_age`).

See [RFC 2616 / 14.9](tools.ietf.org/html/rfc2616#section-14.9.1) for more on standard cache control directives.

@example

# 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"

@param values [Array<Symbol, Hash>] values to map to ‘Cache-Control` directives @option values [Symbol] :public @option values [Symbol] :private @option values [Symbol] :no_cache @option values [Symbol] :no_store @option values [Symbol] :must_validate @option values [Symbol] :proxy_revalidate @option values [Hash] :max_age @option values [Hash] :min_stale @option values [Hash] :s_max_age

@return void

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 354
def cache_control(*values)
  directives = Cache::CacheControl::Directives.new(*values)
  headers.merge!(directives.headers)
end
cookies() click to toggle source

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

@return [CookieJar]

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 255
def cookies
  @cookies ||= CookieJar.new(env.dup, headers, @config.cookies)
end
expires(amount, *values) click to toggle source

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}.

@example

# 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"

@param amount [Integer, Time] number of seconds or point in time @param values [Array<Symbols>] values to map to ‘Cache-Control` directives via

{#cache_control}

@return void

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 383
def expires(amount, *values)
  directives = Cache::Expires::Directives.new(amount, *values)
  headers.merge!(directives.headers)
end
flash() click to toggle source

Returns the flash for the request.

This is the same flash object as the {Request}.

@return [Flash]

@raise [MissingSessionError] if sessions are not enabled

@see Request#flash

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 241
def flash
  unless session_enabled?
    raise Hanami::Action::MissingSessionError.new("Hanami::Action::Response#flash")
  end

  request.flash
end
format() click to toggle source

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`.

@example

response.format # => :json

@return [Symbol, nil]

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 133
def format
  @format ||= Mime.detect_format(content_type, @config)
end
format=(value) click to toggle source

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 {Hanami::Action::UnknownFormatError}.

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

@example Assigning via a format name symbol

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

@example Assigning via a content type string

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

@param value [Symbol, String] the format name or content type

@raise [Hanami::Action::UnknownFormatError] if an unknown format name is given

@see Config#formats

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 164
def format=(value)
  format, content_type = Mime.detect_format_and_content_type(value, @config)

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

  @format = format
end
fresh(options) click to toggle source

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.

@example

# 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

@param options [Hash] @option options [Integer] :etag for testing IfNoneMatch conditions @option options [Date] :last_modified for testing IfModifiedSince conditions

@return void

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 410
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?() click to toggle source

@since 2.0.0 @api private

# File lib/hanami/action/response.rb, line 448
def head?
  env[Action::REQUEST_METHOD] == Action::HEAD
end
redirect_to(url, status: 302) click to toggle source

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

@param url [String] @param status [Integer] the HTTP status to use for the redirect

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 266
def redirect_to(url, status: 302)
  return unless allow_redirect?

  redirect(::String.new(url), status)
  Halt.call(status)
end
render(view, **input) click to toggle source

Sets the response body from the rendered view.

@param view [Hanami::View] the view to render @param input [Hash] keyword arguments to pass to the view’s ‘#call` method

@api public @since 2.1.0

# File lib/hanami/action/response.rb, line 111
def render(view, **input)
  view_input = {
    **view_options.call(request, self),
    **exposures,
    **input
  }

  self.body = view.call(**view_input).to_str
end
renderable?() click to toggle source

@since 2.0.0 @api private

# File lib/hanami/action/response.rb, line 428
def renderable?
  return !head? && body.empty? if body.respond_to?(:empty?)

  !@sending_file && !head?
end
send_file(path) click to toggle source

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)

@param path [String] the file path

@return [void]

@see Hanami::Action::Config#public_directory @see Hanami::Action::Rack::File

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 292
def send_file(path)
  _send_file(
    Action::Rack::File.new(path, @config.public_directory).call(env)
  )
end
session() click to toggle source

Returns the session for the response.

This is the same session object as the {Request}.

@return [Hash] the session object

@raise [MissingSessionError] if sessions are not enabled

@see Request#session

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 221
def session
  unless session_enabled?
    raise Hanami::Action::MissingSessionError.new("Hanami::Action::Response#session")
  end

  request.session
end
session_enabled?() click to toggle source

Returns true if the session is enabled for the request.

@return [Boolean]

@api public @since 2.1.0

# File lib/hanami/action/response.rb, line 205
def session_enabled?
  @session_enabled
end
set_format(value) click to toggle source

@since 2.0.0 @api private

# File lib/hanami/action/response.rb, line 422
def set_format(value) # rubocop:disable Naming/AccessorMethodName
  @format = value
end
status=(code) click to toggle source

Sets the response status.

@param code [Integer, Symbol] the status code

@since 2.0.2 @api public

@raise [Hanami::Action::UnknownHttpStatusError] if the given code

cannot be associated to a known HTTP status

@example

response.status = :unprocessable_entity

@example

response.status = 422

@see guides.hanamirb.org/v2.0/actions/status-codes/

Calls superclass method
# File lib/hanami/action/response.rb, line 100
def status=(code)
  super(Http::Status.lookup(code))
end
unsafe_send_file(path) click to toggle source

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

@param path [String, Pathname] path to the file to be sent

@return [void]

@see send_file @see Hanami::Action::Rack::File

@since 2.0.0 @api public

# File lib/hanami/action/response.rb, line 309
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