Transform actionpack documentation to Markdown
This commit is contained in:
parent
3079e8b0f8
commit
d6bf4de7dc
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_pack"
|
require "action_pack"
|
||||||
require "active_support"
|
require "active_support"
|
||||||
require "active_support/rails"
|
require "active_support/rails"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
module AssetPaths # :nodoc:
|
module AssetPaths # :nodoc:
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "abstract_controller/error"
|
require "abstract_controller/error"
|
||||||
require "active_support/configurable"
|
require "active_support/configurable"
|
||||||
require "active_support/descendants_tracker"
|
require "active_support/descendants_tracker"
|
||||||
@ -26,12 +28,12 @@ def corrections # :nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Abstract Controller \Base
|
# # Abstract Controller Base
|
||||||
#
|
#
|
||||||
# AbstractController::Base is a low-level API. Nobody should be
|
# AbstractController::Base is a low-level API. Nobody should be using it
|
||||||
# using it directly, and subclasses (like ActionController::Base) are
|
# directly, and subclasses (like ActionController::Base) are expected to provide
|
||||||
# expected to provide their own +render+ method, since rendering means
|
# their own `render` method, since rendering means different things depending on
|
||||||
# different things depending on the context.
|
# the context.
|
||||||
class Base
|
class Base
|
||||||
##
|
##
|
||||||
# Returns the body of the HTTP response sent by the controller.
|
# Returns the body of the HTTP response sent by the controller.
|
||||||
@ -52,27 +54,26 @@ class << self
|
|||||||
attr_reader :abstract
|
attr_reader :abstract
|
||||||
alias_method :abstract?, :abstract
|
alias_method :abstract?, :abstract
|
||||||
|
|
||||||
# Define a controller as abstract. See internal_methods for more
|
# Define a controller as abstract. See internal_methods for more details.
|
||||||
# details.
|
|
||||||
def abstract!
|
def abstract!
|
||||||
@abstract = true
|
@abstract = true
|
||||||
end
|
end
|
||||||
|
|
||||||
def inherited(klass) # :nodoc:
|
def inherited(klass) # :nodoc:
|
||||||
# Define the abstract ivar on subclasses so that we don't get
|
# Define the abstract ivar on subclasses so that we don't get uninitialized ivar
|
||||||
# uninitialized ivar warnings
|
# warnings
|
||||||
unless klass.instance_variable_defined?(:@abstract)
|
unless klass.instance_variable_defined?(:@abstract)
|
||||||
klass.instance_variable_set(:@abstract, false)
|
klass.instance_variable_set(:@abstract, false)
|
||||||
end
|
end
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
# A list of all internal methods for a controller. This finds the first
|
# A list of all internal methods for a controller. This finds the first abstract
|
||||||
# abstract superclass of a controller, and gets a list of all public
|
# superclass of a controller, and gets a list of all public instance methods on
|
||||||
# instance methods on that abstract class. Public instance methods of
|
# that abstract class. Public instance methods of a controller would normally be
|
||||||
# a controller would normally be considered action methods, so methods
|
# considered action methods, so methods declared on abstract classes are being
|
||||||
# declared on abstract classes are being removed.
|
# removed. (ActionController::Metal and ActionController::Base are defined as
|
||||||
# (ActionController::Metal and ActionController::Base are defined as abstract)
|
# abstract)
|
||||||
def internal_methods
|
def internal_methods
|
||||||
controller = self
|
controller = self
|
||||||
methods = []
|
methods = []
|
||||||
@ -85,18 +86,18 @@ def internal_methods
|
|||||||
controller.public_instance_methods(true) - methods
|
controller.public_instance_methods(true) - methods
|
||||||
end
|
end
|
||||||
|
|
||||||
# A list of method names that should be considered actions. This
|
# A list of method names that should be considered actions. This includes all
|
||||||
# includes all public instance methods on a controller, less
|
# public instance methods on a controller, less any internal methods (see
|
||||||
# any internal methods (see internal_methods), adding back in
|
# internal_methods), adding back in any methods that are internal, but still
|
||||||
# any methods that are internal, but still exist on the class
|
# exist on the class itself.
|
||||||
# itself.
|
#
|
||||||
|
# #### Returns
|
||||||
|
# * `Set` - A set of all methods that should be considered actions.
|
||||||
#
|
#
|
||||||
# ==== Returns
|
|
||||||
# * <tt>Set</tt> - A set of all methods that should be considered actions.
|
|
||||||
def action_methods
|
def action_methods
|
||||||
@action_methods ||= begin
|
@action_methods ||= begin
|
||||||
# All public instance methods of this class, including ancestors
|
# All public instance methods of this class, including ancestors except for
|
||||||
# except for public instance methods of Base and its ancestors.
|
# public instance methods of Base and its ancestors.
|
||||||
methods = public_instance_methods(true) - internal_methods
|
methods = public_instance_methods(true) - internal_methods
|
||||||
# Be sure to include shadowed public instance methods of this class.
|
# Be sure to include shadowed public instance methods of this class.
|
||||||
methods.concat(public_instance_methods(false))
|
methods.concat(public_instance_methods(false))
|
||||||
@ -105,23 +106,24 @@ def action_methods
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# action_methods are cached and there is sometimes a need to refresh
|
# action_methods are cached and there is sometimes a need to refresh them.
|
||||||
# them. ::clear_action_methods! allows you to do that, so next time
|
# ::clear_action_methods! allows you to do that, so next time you run
|
||||||
# you run action_methods, they will be recalculated.
|
# action_methods, they will be recalculated.
|
||||||
def clear_action_methods!
|
def clear_action_methods!
|
||||||
@action_methods = nil
|
@action_methods = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the full controller name, underscored, without the ending Controller.
|
# Returns the full controller name, underscored, without the ending Controller.
|
||||||
#
|
#
|
||||||
# class MyApp::MyPostsController < AbstractController::Base
|
# class MyApp::MyPostsController < AbstractController::Base
|
||||||
#
|
#
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
|
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
|
||||||
|
#
|
||||||
|
# #### Returns
|
||||||
|
# * `String`
|
||||||
#
|
#
|
||||||
# ==== Returns
|
|
||||||
# * <tt>String</tt>
|
|
||||||
def controller_path
|
def controller_path
|
||||||
@controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
|
@controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
|
||||||
end
|
end
|
||||||
@ -142,12 +144,13 @@ def eager_load! # :nodoc:
|
|||||||
|
|
||||||
# Calls the action going through the entire Action Dispatch stack.
|
# Calls the action going through the entire Action Dispatch stack.
|
||||||
#
|
#
|
||||||
# The actual method that is called is determined by calling
|
# The actual method that is called is determined by calling #method_for_action.
|
||||||
# #method_for_action. If no method can handle the action, then an
|
# If no method can handle the action, then an AbstractController::ActionNotFound
|
||||||
# AbstractController::ActionNotFound error is raised.
|
# error is raised.
|
||||||
|
#
|
||||||
|
# #### Returns
|
||||||
|
# * `self`
|
||||||
#
|
#
|
||||||
# ==== Returns
|
|
||||||
# * <tt>self</tt>
|
|
||||||
def process(action, ...)
|
def process(action, ...)
|
||||||
@_action_name = action.to_s
|
@_action_name = action.to_s
|
||||||
|
|
||||||
@ -170,31 +173,30 @@ def action_methods
|
|||||||
self.class.action_methods
|
self.class.action_methods
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if a method for the action is available and
|
# Returns true if a method for the action is available and can be dispatched,
|
||||||
# can be dispatched, false otherwise.
|
# false otherwise.
|
||||||
#
|
#
|
||||||
# Notice that <tt>action_methods.include?("foo")</tt> may return
|
# Notice that `action_methods.include?("foo")` may return false and
|
||||||
# false and <tt>available_action?("foo")</tt> returns true because
|
# `available_action?("foo")` returns true because this method considers actions
|
||||||
# this method considers actions that are also available
|
# that are also available through other means, for example, implicit render
|
||||||
# through other means, for example, implicit render ones.
|
# ones.
|
||||||
|
#
|
||||||
|
# #### Parameters
|
||||||
|
# * `action_name` - The name of an action to be tested
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
|
||||||
# * <tt>action_name</tt> - The name of an action to be tested
|
|
||||||
def available_action?(action_name)
|
def available_action?(action_name)
|
||||||
_find_action_name(action_name)
|
_find_action_name(action_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tests if a response body is set. Used to determine if the
|
# Tests if a response body is set. Used to determine if the `process_action`
|
||||||
# +process_action+ callback needs to be terminated in
|
# callback needs to be terminated in AbstractController::Callbacks.
|
||||||
# AbstractController::Callbacks.
|
|
||||||
def performed?
|
def performed?
|
||||||
response_body
|
response_body
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the given controller is capable of rendering
|
# Returns true if the given controller is capable of rendering a path. A
|
||||||
# a path. A subclass of +AbstractController::Base+
|
# subclass of `AbstractController::Base` may return false. An Email controller
|
||||||
# may return false. An Email controller for example does not
|
# for example does not support paths, only full URLs.
|
||||||
# support paths, only full URLs.
|
|
||||||
def self.supports_path?
|
def self.supports_path?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
@ -204,80 +206,83 @@ def inspect # :nodoc:
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Returns true if the name can be considered an action because
|
# Returns true if the name can be considered an action because it has a method
|
||||||
# it has a method defined in the controller.
|
# defined in the controller.
|
||||||
|
#
|
||||||
|
# #### Parameters
|
||||||
|
# * `name` - The name of an action to be tested
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
|
||||||
# * <tt>name</tt> - The name of an action to be tested
|
|
||||||
def action_method?(name)
|
def action_method?(name)
|
||||||
self.class.action_methods.include?(name)
|
self.class.action_methods.include?(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Call the action. Override this in a subclass to modify the
|
# Call the action. Override this in a subclass to modify the behavior around
|
||||||
# behavior around processing an action. This, and not #process,
|
# processing an action. This, and not #process, is the intended way to override
|
||||||
# is the intended way to override action dispatching.
|
# action dispatching.
|
||||||
#
|
#
|
||||||
# Notice that the first argument is the method to be dispatched
|
# Notice that the first argument is the method to be dispatched which is **not**
|
||||||
# which is *not* necessarily the same as the action name.
|
# necessarily the same as the action name.
|
||||||
def process_action(...)
|
def process_action(...)
|
||||||
send_action(...)
|
send_action(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Actually call the method associated with the action. Override
|
# Actually call the method associated with the action. Override this method if
|
||||||
# this method if you wish to change how action methods are called,
|
# you wish to change how action methods are called, not to add additional
|
||||||
# not to add additional behavior around it. For example, you would
|
# behavior around it. For example, you would override #send_action if you want
|
||||||
# override #send_action if you want to inject arguments into the
|
# to inject arguments into the method.
|
||||||
# method.
|
|
||||||
alias send_action send
|
alias send_action send
|
||||||
|
|
||||||
# If the action name was not found, but a method called "action_missing"
|
# If the action name was not found, but a method called "action_missing" was
|
||||||
# was found, #method_for_action will return "_handle_action_missing".
|
# found, #method_for_action will return "_handle_action_missing". This method
|
||||||
# This method calls #action_missing with the current action name.
|
# calls #action_missing with the current action name.
|
||||||
def _handle_action_missing(*args)
|
def _handle_action_missing(*args)
|
||||||
action_missing(@_action_name, *args)
|
action_missing(@_action_name, *args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Takes an action name and returns the name of the method that will
|
# Takes an action name and returns the name of the method that will handle the
|
||||||
# handle the action.
|
# action.
|
||||||
#
|
#
|
||||||
# It checks if the action name is valid and returns false otherwise.
|
# It checks if the action name is valid and returns false otherwise.
|
||||||
#
|
#
|
||||||
# See method_for_action for more information.
|
# See method_for_action for more information.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
# * <tt>action_name</tt> - An action name to find a method name for
|
# * `action_name` - An action name to find a method name for
|
||||||
#
|
#
|
||||||
# ==== Returns
|
#
|
||||||
# * <tt>string</tt> - The name of the method that handles the action
|
# #### Returns
|
||||||
# * false - No valid method name could be found.
|
# * `string` - The name of the method that handles the action
|
||||||
# Raise +AbstractController::ActionNotFound+.
|
# * false - No valid method name could be found.
|
||||||
|
#
|
||||||
|
# Raise `AbstractController::ActionNotFound`.
|
||||||
def _find_action_name(action_name)
|
def _find_action_name(action_name)
|
||||||
_valid_action_name?(action_name) && method_for_action(action_name)
|
_valid_action_name?(action_name) && method_for_action(action_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Takes an action name and returns the name of the method that will
|
# Takes an action name and returns the name of the method that will handle the
|
||||||
# handle the action. In normal cases, this method returns the same
|
# action. In normal cases, this method returns the same name as it receives. By
|
||||||
# name as it receives. By default, if #method_for_action receives
|
# default, if #method_for_action receives a name that is not an action, it will
|
||||||
# a name that is not an action, it will look for an #action_missing
|
# look for an #action_missing method and return "_handle_action_missing" if one
|
||||||
# method and return "_handle_action_missing" if one is found.
|
# is found.
|
||||||
#
|
#
|
||||||
# Subclasses may override this method to add additional conditions
|
# Subclasses may override this method to add additional conditions that should
|
||||||
# that should be considered an action. For instance, an HTTP controller
|
# be considered an action. For instance, an HTTP controller with a template
|
||||||
# with a template matching the action name is considered to exist.
|
# matching the action name is considered to exist.
|
||||||
#
|
#
|
||||||
# If you override this method to handle additional cases, you may
|
# If you override this method to handle additional cases, you may also provide a
|
||||||
# also provide a method (like +_handle_method_missing+) to handle
|
# method (like `_handle_method_missing`) to handle the case.
|
||||||
# the case.
|
|
||||||
#
|
#
|
||||||
# If none of these conditions are true, and +method_for_action+
|
# If none of these conditions are true, and `method_for_action` returns `nil`,
|
||||||
# returns +nil+, an +AbstractController::ActionNotFound+ exception will be raised.
|
# an `AbstractController::ActionNotFound` exception will be raised.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
# * <tt>action_name</tt> - An action name to find a method name for
|
# * `action_name` - An action name to find a method name for
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# #### Returns
|
||||||
|
# * `string` - The name of the method that handles the action
|
||||||
|
# * `nil` - No method name could be found.
|
||||||
#
|
#
|
||||||
# ==== Returns
|
|
||||||
# * <tt>string</tt> - The name of the method that handles the action
|
|
||||||
# * <tt>nil</tt> - No method name could be found.
|
|
||||||
def method_for_action(action_name)
|
def method_for_action(action_name)
|
||||||
if action_method?(action_name)
|
if action_method?(action_name)
|
||||||
action_name
|
action_name
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
module Caching
|
module Caching
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
module Caching
|
module Caching
|
||||||
# = Abstract Controller Caching \Fragments
|
# # Abstract Controller Caching Fragments
|
||||||
#
|
#
|
||||||
# Fragment caching is used for caching various blocks within
|
# Fragment caching is used for caching various blocks within views without
|
||||||
# views without caching the entire action as a whole. This is
|
# caching the entire action as a whole. This is useful when certain elements of
|
||||||
# useful when certain elements of an action change frequently or
|
# an action change frequently or depend on complicated state while other parts
|
||||||
# depend on complicated state while other parts rarely change or
|
# rarely change or can be shared amongst multiple parties. The caching is done
|
||||||
# can be shared amongst multiple parties. The caching is done using
|
# using the `cache` helper available in the Action View. See
|
||||||
# the +cache+ helper available in the Action View. See
|
|
||||||
# ActionView::Helpers::CacheHelper for more information.
|
# ActionView::Helpers::CacheHelper for more information.
|
||||||
#
|
#
|
||||||
# While it's strongly recommended that you use key-based cache
|
# While it's strongly recommended that you use key-based cache expiration (see
|
||||||
# expiration (see links in CacheHelper for more information),
|
# links in CacheHelper for more information), it is also possible to manually
|
||||||
# it is also possible to manually expire caches. For example:
|
# expire caches. For example:
|
||||||
#
|
#
|
||||||
# expire_fragment('name_of_cache')
|
# expire_fragment('name_of_cache')
|
||||||
module Fragments
|
module Fragments
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
@ -35,38 +36,35 @@ module Fragments
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Allows you to specify controller-wide key prefixes for
|
# Allows you to specify controller-wide key prefixes for cache fragments. Pass
|
||||||
# cache fragments. Pass either a constant +value+, or a block
|
# either a constant `value`, or a block which computes a value each time a cache
|
||||||
# which computes a value each time a cache key is generated.
|
# key is generated.
|
||||||
#
|
#
|
||||||
# For example, you may want to prefix all fragment cache keys
|
# For example, you may want to prefix all fragment cache keys with a global
|
||||||
# with a global version identifier, so you can easily
|
# version identifier, so you can easily invalidate all caches.
|
||||||
# invalidate all caches.
|
|
||||||
#
|
#
|
||||||
# class ApplicationController
|
# class ApplicationController
|
||||||
# fragment_cache_key "v1"
|
# fragment_cache_key "v1"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# When it's time to invalidate all fragments, simply change
|
# When it's time to invalidate all fragments, simply change the string constant.
|
||||||
# the string constant. Or, progressively roll out the cache
|
# Or, progressively roll out the cache invalidation using a computed value:
|
||||||
# invalidation using a computed value:
|
#
|
||||||
#
|
# class ApplicationController
|
||||||
# class ApplicationController
|
# fragment_cache_key do
|
||||||
# fragment_cache_key do
|
# @account.id.odd? ? "v1" : "v2"
|
||||||
# @account.id.odd? ? "v1" : "v2"
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
def fragment_cache_key(value = nil, &key)
|
def fragment_cache_key(value = nil, &key)
|
||||||
self.fragment_cache_keys += [key || -> { value }]
|
self.fragment_cache_keys += [key || -> { value }]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Given a key (as described in +expire_fragment+), returns
|
# Given a key (as described in `expire_fragment`), returns a key array suitable
|
||||||
# a key array suitable for use in reading, writing, or expiring a
|
# for use in reading, writing, or expiring a cached fragment. All keys begin
|
||||||
# cached fragment. All keys begin with <tt>:views</tt>,
|
# with `:views`, followed by `ENV["RAILS_CACHE_ID"]` or
|
||||||
# followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> if set,
|
# `ENV["RAILS_APP_VERSION"]` if set, followed by any controller-wide key prefix
|
||||||
# followed by any controller-wide key prefix values, ending
|
# values, ending with the specified `key` value.
|
||||||
# with the specified +key+ value.
|
|
||||||
def combined_fragment_cache_key(key)
|
def combined_fragment_cache_key(key)
|
||||||
head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
|
head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
|
||||||
tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
|
tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
|
||||||
@ -77,8 +75,8 @@ def combined_fragment_cache_key(key)
|
|||||||
cache_key
|
cache_key
|
||||||
end
|
end
|
||||||
|
|
||||||
# Writes +content+ to the location signified by
|
# Writes `content` to the location signified by `key` (see `expire_fragment` for
|
||||||
# +key+ (see +expire_fragment+ for acceptable formats).
|
# acceptable formats).
|
||||||
def write_fragment(key, content, options = nil)
|
def write_fragment(key, content, options = nil)
|
||||||
return content unless cache_configured?
|
return content unless cache_configured?
|
||||||
|
|
||||||
@ -90,8 +88,8 @@ def write_fragment(key, content, options = nil)
|
|||||||
content
|
content
|
||||||
end
|
end
|
||||||
|
|
||||||
# Reads a cached fragment from the location signified by +key+
|
# Reads a cached fragment from the location signified by `key` (see
|
||||||
# (see +expire_fragment+ for acceptable formats).
|
# `expire_fragment` for acceptable formats).
|
||||||
def read_fragment(key, options = nil)
|
def read_fragment(key, options = nil)
|
||||||
return unless cache_configured?
|
return unless cache_configured?
|
||||||
|
|
||||||
@ -102,8 +100,8 @@ def read_fragment(key, options = nil)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check if a cached fragment from the location signified by
|
# Check if a cached fragment from the location signified by `key` exists (see
|
||||||
# +key+ exists (see +expire_fragment+ for acceptable formats).
|
# `expire_fragment` for acceptable formats).
|
||||||
def fragment_exist?(key, options = nil)
|
def fragment_exist?(key, options = nil)
|
||||||
return unless cache_configured?
|
return unless cache_configured?
|
||||||
key = combined_fragment_cache_key(key)
|
key = combined_fragment_cache_key(key)
|
||||||
@ -115,22 +113,21 @@ def fragment_exist?(key, options = nil)
|
|||||||
|
|
||||||
# Removes fragments from the cache.
|
# Removes fragments from the cache.
|
||||||
#
|
#
|
||||||
# +key+ can take one of three forms:
|
# `key` can take one of three forms:
|
||||||
#
|
#
|
||||||
# * String - This would normally take the form of a path, like
|
# * String - This would normally take the form of a path, like
|
||||||
# <tt>pages/45/notes</tt>.
|
# `pages/45/notes`.
|
||||||
# * Hash - Treated as an implicit call to +url_for+, like
|
# * Hash - Treated as an implicit call to `url_for`, like `{ controller:
|
||||||
# <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
|
# 'pages', action: 'notes', id: 45}`
|
||||||
# * Regexp - Will remove any fragment that matches, so
|
# * Regexp - Will remove any fragment that matches, so `%r{pages/\d*/notes}`
|
||||||
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
|
# might remove all notes. Make sure you don't use anchors in the regex (`^`
|
||||||
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
|
# or `$`) because the actual filename matched looks like
|
||||||
# the actual filename matched looks like
|
# `./cache/filename/path.cache`. Note: Regexp expiration is only supported
|
||||||
# <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
|
# on caches that can iterate over all keys (unlike memcached).
|
||||||
# only supported on caches that can iterate over all keys (unlike
|
|
||||||
# memcached).
|
|
||||||
#
|
#
|
||||||
# +options+ is passed through to the cache store's +delete+
|
#
|
||||||
# method (or <tt>delete_matched</tt>, for Regexp keys).
|
# `options` is passed through to the cache store's `delete` method (or
|
||||||
|
# `delete_matched`, for Regexp keys).
|
||||||
def expire_fragment(key, options = nil)
|
def expire_fragment(key, options = nil)
|
||||||
return unless cache_configured?
|
return unless cache_configured?
|
||||||
key = combined_fragment_cache_key(key) unless key.is_a?(Regexp)
|
key = combined_fragment_cache_key(key) unless key.is_a?(Regexp)
|
||||||
|
@ -1,29 +1,32 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
# = Abstract Controller \Callbacks
|
# # Abstract Controller Callbacks
|
||||||
#
|
#
|
||||||
# Abstract Controller provides hooks during the life cycle of a controller action.
|
# Abstract Controller provides hooks during the life cycle of a controller
|
||||||
# Callbacks allow you to trigger logic during this cycle. Available callbacks are:
|
# action. Callbacks allow you to trigger logic during this cycle. Available
|
||||||
|
# callbacks are:
|
||||||
#
|
#
|
||||||
# * <tt>after_action</tt>
|
# * `after_action`
|
||||||
# * <tt>append_after_action</tt>
|
# * `append_after_action`
|
||||||
# * <tt>append_around_action</tt>
|
# * `append_around_action`
|
||||||
# * <tt>append_before_action</tt>
|
# * `append_before_action`
|
||||||
# * <tt>around_action</tt>
|
# * `around_action`
|
||||||
# * <tt>before_action</tt>
|
# * `before_action`
|
||||||
# * <tt>prepend_after_action</tt>
|
# * `prepend_after_action`
|
||||||
# * <tt>prepend_around_action</tt>
|
# * `prepend_around_action`
|
||||||
# * <tt>prepend_before_action</tt>
|
# * `prepend_before_action`
|
||||||
# * <tt>skip_after_action</tt>
|
# * `skip_after_action`
|
||||||
# * <tt>skip_around_action</tt>
|
# * `skip_around_action`
|
||||||
# * <tt>skip_before_action</tt>
|
# * `skip_before_action`
|
||||||
module Callbacks
|
module Callbacks
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
# Uses ActiveSupport::Callbacks as the base functionality. For
|
# Uses ActiveSupport::Callbacks as the base functionality. For more details on
|
||||||
# more details on the whole callback system, read the documentation
|
# the whole callback system, read the documentation for
|
||||||
# for ActiveSupport::Callbacks.
|
# ActiveSupport::Callbacks.
|
||||||
include ActiveSupport::Callbacks
|
include ActiveSupport::Callbacks
|
||||||
|
|
||||||
included do
|
included do
|
||||||
@ -69,25 +72,24 @@ def match?(controller)
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# If +:only+ or +:except+ are used, convert the options into the
|
# If `:only` or `:except` are used, convert the options into the `:if` and
|
||||||
# +:if+ and +:unless+ options of ActiveSupport::Callbacks.
|
# `:unless` options of ActiveSupport::Callbacks.
|
||||||
#
|
#
|
||||||
# The basic idea is that <tt>:only => :index</tt> gets converted to
|
# The basic idea is that `:only => :index` gets converted to `:if => proc {|c|
|
||||||
# <tt>:if => proc {|c| c.action_name == "index" }</tt>.
|
# c.action_name == "index" }`.
|
||||||
#
|
#
|
||||||
# Note that <tt>:only</tt> has priority over <tt>:if</tt> in case they
|
# Note that `:only` has priority over `:if` in case they are used together.
|
||||||
# are used together.
|
|
||||||
#
|
#
|
||||||
# only: :index, if: -> { true } # the :if option will be ignored.
|
# only: :index, if: -> { true } # the :if option will be ignored.
|
||||||
#
|
#
|
||||||
# Note that <tt>:if</tt> has priority over <tt>:except</tt> in case they
|
# Note that `:if` has priority over `:except` in case they are used together.
|
||||||
# are used together.
|
|
||||||
#
|
#
|
||||||
# except: :index, if: -> { true } # the :except option will be ignored.
|
# except: :index, if: -> { true } # the :except option will be ignored.
|
||||||
|
#
|
||||||
|
# #### Options
|
||||||
|
# * `only` - The callback should be run only for this action.
|
||||||
|
# * `except` - The callback should be run for all actions except this action.
|
||||||
#
|
#
|
||||||
# ==== Options
|
|
||||||
# * <tt>only</tt> - The callback should be run only for this action.
|
|
||||||
# * <tt>except</tt> - The callback should be run for all actions except this action.
|
|
||||||
def _normalize_callback_options(options)
|
def _normalize_callback_options(options)
|
||||||
_normalize_callback_option(options, :only, :if)
|
_normalize_callback_option(options, :only, :if)
|
||||||
_normalize_callback_option(options, :except, :unless)
|
_normalize_callback_option(options, :except, :unless)
|
||||||
@ -101,18 +103,20 @@ def _normalize_callback_option(options, from, to) # :nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Take callback names and an optional callback proc, normalize them,
|
# Take callback names and an optional callback proc, normalize them, then call
|
||||||
# then call the block with each callback. This allows us to abstract
|
# the block with each callback. This allows us to abstract the normalization
|
||||||
# the normalization across several methods that use it.
|
# across several methods that use it.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
# * <tt>callbacks</tt> - An array of callbacks, with an optional
|
# * `callbacks` - An array of callbacks, with an optional options hash as the
|
||||||
# options hash as the last parameter.
|
# last parameter.
|
||||||
# * <tt>block</tt> - A proc that should be added to the callbacks.
|
# * `block` - A proc that should be added to the callbacks.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# #### Block Parameters
|
||||||
|
# * `name` - The callback to be added.
|
||||||
|
# * `options` - A hash of options to be used when adding the callback.
|
||||||
#
|
#
|
||||||
# ==== Block Parameters
|
|
||||||
# * <tt>name</tt> - The callback to be added.
|
|
||||||
# * <tt>options</tt> - A hash of options to be used when adding the callback.
|
|
||||||
def _insert_callbacks(callbacks, block = nil)
|
def _insert_callbacks(callbacks, block = nil)
|
||||||
options = callbacks.extract_options!
|
options = callbacks.extract_options!
|
||||||
callbacks.push(block) if block
|
callbacks.push(block) if block
|
||||||
@ -131,20 +135,21 @@ def _insert_callbacks(callbacks, block = nil)
|
|||||||
#
|
#
|
||||||
# Append a callback before actions. See _insert_callbacks for parameter details.
|
# Append a callback before actions. See _insert_callbacks for parameter details.
|
||||||
#
|
#
|
||||||
# If the callback renders or redirects, the action will not run. If there
|
# If the callback renders or redirects, the action will not run. If there are
|
||||||
# are additional callbacks scheduled to run after that callback, they are
|
# additional callbacks scheduled to run after that callback, they are also
|
||||||
# also cancelled.
|
# cancelled.
|
||||||
|
|
||||||
##
|
##
|
||||||
# :method: prepend_before_action
|
# :method: prepend_before_action
|
||||||
#
|
#
|
||||||
# :call-seq: prepend_before_action(names, block)
|
# :call-seq: prepend_before_action(names, block)
|
||||||
#
|
#
|
||||||
# Prepend a callback before actions. See _insert_callbacks for parameter details.
|
# Prepend a callback before actions. See _insert_callbacks for parameter
|
||||||
|
# details.
|
||||||
#
|
#
|
||||||
# If the callback renders or redirects, the action will not run. If there
|
# If the callback renders or redirects, the action will not run. If there are
|
||||||
# are additional callbacks scheduled to run after that callback, they are
|
# additional callbacks scheduled to run after that callback, they are also
|
||||||
# also cancelled.
|
# cancelled.
|
||||||
|
|
||||||
##
|
##
|
||||||
# :method: skip_before_action
|
# :method: skip_before_action
|
||||||
@ -160,9 +165,9 @@ def _insert_callbacks(callbacks, block = nil)
|
|||||||
#
|
#
|
||||||
# Append a callback before actions. See _insert_callbacks for parameter details.
|
# Append a callback before actions. See _insert_callbacks for parameter details.
|
||||||
#
|
#
|
||||||
# If the callback renders or redirects, the action will not run. If there
|
# If the callback renders or redirects, the action will not run. If there are
|
||||||
# are additional callbacks scheduled to run after that callback, they are
|
# additional callbacks scheduled to run after that callback, they are also
|
||||||
# also cancelled.
|
# cancelled.
|
||||||
|
|
||||||
##
|
##
|
||||||
# :method: after_action
|
# :method: after_action
|
||||||
@ -204,7 +209,8 @@ def _insert_callbacks(callbacks, block = nil)
|
|||||||
#
|
#
|
||||||
# :call-seq: prepend_around_action(names, block)
|
# :call-seq: prepend_around_action(names, block)
|
||||||
#
|
#
|
||||||
# Prepend a callback around actions. See _insert_callbacks for parameter details.
|
# Prepend a callback around actions. See _insert_callbacks for parameter
|
||||||
|
# details.
|
||||||
|
|
||||||
##
|
##
|
||||||
# :method: skip_around_action
|
# :method: skip_around_action
|
||||||
@ -219,9 +225,8 @@ def _insert_callbacks(callbacks, block = nil)
|
|||||||
# :call-seq: append_around_action(names, block)
|
# :call-seq: append_around_action(names, block)
|
||||||
#
|
#
|
||||||
# Append a callback around actions. See _insert_callbacks for parameter details.
|
# Append a callback around actions. See _insert_callbacks for parameter details.
|
||||||
|
# set up before_action, prepend_before_action, skip_before_action, etc. for each
|
||||||
# set up before_action, prepend_before_action, skip_before_action, etc.
|
# of before, after, and around.
|
||||||
# for each of before, after, and around.
|
|
||||||
[:before, :after, :around].each do |callback|
|
[:before, :after, :around].each do |callback|
|
||||||
define_method "#{callback}_action" do |*names, &blk|
|
define_method "#{callback}_action" do |*names, &blk|
|
||||||
_insert_callbacks(names, blk) do |name, options|
|
_insert_callbacks(names, blk) do |name, options|
|
||||||
@ -235,8 +240,8 @@ def _insert_callbacks(callbacks, block = nil)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Skip a before, after or around callback. See _insert_callbacks
|
# Skip a before, after or around callback. See _insert_callbacks for details on
|
||||||
# for details on the allowed parameters.
|
# the allowed parameters.
|
||||||
define_method "skip_#{callback}_action" do |*names|
|
define_method "skip_#{callback}_action" do |*names|
|
||||||
_insert_callbacks(names) do |name, options|
|
_insert_callbacks(names) do |name, options|
|
||||||
skip_callback(:process_action, callback, name, options)
|
skip_callback(:process_action, callback, name, options)
|
||||||
@ -249,8 +254,8 @@ def _insert_callbacks(callbacks, block = nil)
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Override <tt>AbstractController::Base#process_action</tt> to run the
|
# Override `AbstractController::Base#process_action` to run the `process_action`
|
||||||
# <tt>process_action</tt> callbacks around the normal behavior.
|
# callbacks around the normal behavior.
|
||||||
def process_action(...)
|
def process_action(...)
|
||||||
run_callbacks(:process_action) do
|
run_callbacks(:process_action) do
|
||||||
super
|
super
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/http/mime_type"
|
require "action_dispatch/http/mime_type"
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
def self.deprecator # :nodoc:
|
def self.deprecator # :nodoc:
|
||||||
@deprecator ||= ActiveSupport::Deprecation.new
|
@deprecator ||= ActiveSupport::Deprecation.new
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
class Error < StandardError # :nodoc:
|
class Error < StandardError # :nodoc:
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/dependencies"
|
require "active_support/dependencies"
|
||||||
require "active_support/core_ext/name_error"
|
require "active_support/core_ext/name_error"
|
||||||
|
|
||||||
@ -10,8 +12,8 @@ module Helpers
|
|||||||
included do
|
included do
|
||||||
class_attribute :_helper_methods, default: Array.new
|
class_attribute :_helper_methods, default: Array.new
|
||||||
|
|
||||||
# This is here so that it is always higher in the inheritance chain than
|
# This is here so that it is always higher in the inheritance chain than the
|
||||||
# the definition in lib/action_view/rendering.rb
|
# definition in lib/action_view/rendering.rb
|
||||||
redefine_singleton_method(:_helpers) do
|
redefine_singleton_method(:_helpers) do
|
||||||
if @_helpers ||= nil
|
if @_helpers ||= nil
|
||||||
@_helpers
|
@_helpers
|
||||||
@ -60,9 +62,9 @@ def helper_modules_from_paths(paths)
|
|||||||
extend Resolution
|
extend Resolution
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# When a class is inherited, wrap its helper module in a new module.
|
# When a class is inherited, wrap its helper module in a new module. This
|
||||||
# This ensures that the parent class's module can be changed
|
# ensures that the parent class's module can be changed independently of the
|
||||||
# independently of the child class's.
|
# child class's.
|
||||||
def inherited(klass)
|
def inherited(klass)
|
||||||
# Inherited from parent by default
|
# Inherited from parent by default
|
||||||
klass._helpers = nil
|
klass._helpers = nil
|
||||||
@ -79,49 +81,48 @@ def inherited(klass)
|
|||||||
# :method: modules_for_helpers
|
# :method: modules_for_helpers
|
||||||
# :call-seq: modules_for_helpers(modules_or_helper_prefixes)
|
# :call-seq: modules_for_helpers(modules_or_helper_prefixes)
|
||||||
#
|
#
|
||||||
# Given an array of values like the ones accepted by +helper+, this method
|
# Given an array of values like the ones accepted by `helper`, this method
|
||||||
# returns an array with the corresponding modules, in the same order.
|
# returns an array with the corresponding modules, in the same order.
|
||||||
#
|
#
|
||||||
# ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"])
|
# ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"])
|
||||||
# # => [ApplicationHelper, ChartHelper, RubygemsHelper]
|
# # => [ApplicationHelper, ChartHelper, RubygemsHelper]
|
||||||
#
|
#
|
||||||
#--
|
#--
|
||||||
# Implemented by Resolution#modules_for_helpers.
|
# Implemented by Resolution#modules_for_helpers.
|
||||||
|
|
||||||
##
|
# :method: # all_helpers_from_path
|
||||||
# :method: all_helpers_from_path
|
|
||||||
# :call-seq: all_helpers_from_path(path)
|
# :call-seq: all_helpers_from_path(path)
|
||||||
#
|
#
|
||||||
# Returns a list of helper names in a given path.
|
# Returns a list of helper names in a given path.
|
||||||
#
|
#
|
||||||
# ActionController::Base.all_helpers_from_path 'app/helpers'
|
# ActionController::Base.all_helpers_from_path 'app/helpers'
|
||||||
# # => ["application", "chart", "rubygems"]
|
# # => ["application", "chart", "rubygems"]
|
||||||
#
|
#
|
||||||
#--
|
#--
|
||||||
# Implemented by Resolution#all_helpers_from_path.
|
# Implemented by Resolution#all_helpers_from_path.
|
||||||
|
|
||||||
# Declare a controller method as a helper. For example, the following
|
# Declare a controller method as a helper. For example, the following
|
||||||
# makes the +current_user+ and +logged_in?+ controller methods available
|
# makes the `current_user` and `logged_in?` controller methods available
|
||||||
# to the view:
|
# to the view:
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# helper_method :current_user, :logged_in?
|
# helper_method :current_user, :logged_in?
|
||||||
#
|
#
|
||||||
# private
|
# private
|
||||||
# def current_user
|
# def current_user
|
||||||
# @current_user ||= User.find_by(id: session[:user])
|
# @current_user ||= User.find_by(id: session[:user])
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def logged_in?
|
# def logged_in?
|
||||||
# current_user != nil
|
# current_user != nil
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# In a view:
|
# In a view:
|
||||||
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
|
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
# * <tt>method[, method]</tt> - A name or names of a method on the controller
|
# * `method[, method]` - A name or names of a method on the controller to be
|
||||||
# to be made available on the view.
|
# made available on the view.
|
||||||
def helper_method(*methods)
|
def helper_method(*methods)
|
||||||
methods.flatten!
|
methods.flatten!
|
||||||
self._helper_methods += methods
|
self._helper_methods += methods
|
||||||
@ -131,7 +132,7 @@ def helper_method(*methods)
|
|||||||
|
|
||||||
methods.each do |method|
|
methods.each do |method|
|
||||||
# def current_user(*args, &block)
|
# def current_user(*args, &block)
|
||||||
# controller.send(:'current_user', *args, &block)
|
# controller.send(:'current_user', *args, &block)
|
||||||
# end
|
# end
|
||||||
_helpers_for_modification.class_eval <<~ruby_eval.lines.map(&:strip).join(";"), file, line
|
_helpers_for_modification.class_eval <<~ruby_eval.lines.map(&:strip).join(";"), file, line
|
||||||
def #{method}(...)
|
def #{method}(...)
|
||||||
@ -143,54 +144,54 @@ def #{method}(...)
|
|||||||
|
|
||||||
# Includes the given modules in the template class.
|
# Includes the given modules in the template class.
|
||||||
#
|
#
|
||||||
# Modules can be specified in different ways. All of the following calls
|
# Modules can be specified in different ways. All of the following calls include
|
||||||
# include +FooHelper+:
|
# `FooHelper`:
|
||||||
#
|
#
|
||||||
# # Module, recommended.
|
# # Module, recommended.
|
||||||
# helper FooHelper
|
# helper FooHelper
|
||||||
#
|
#
|
||||||
# # String/symbol without the "helper" suffix, camel or snake case.
|
# # String/symbol without the "helper" suffix, camel or snake case.
|
||||||
# helper "Foo"
|
# helper "Foo"
|
||||||
# helper :Foo
|
# helper :Foo
|
||||||
# helper "foo"
|
# helper "foo"
|
||||||
# helper :foo
|
# helper :foo
|
||||||
#
|
#
|
||||||
# The last two assume that <tt>"foo".camelize</tt> returns "Foo".
|
# The last two assume that `"foo".camelize` returns "Foo".
|
||||||
#
|
#
|
||||||
# When strings or symbols are passed, the method finds the actual module
|
# When strings or symbols are passed, the method finds the actual module object
|
||||||
# object using String#constantize. Therefore, if the module has not been
|
# using String#constantize. Therefore, if the module has not been yet loaded, it
|
||||||
# yet loaded, it has to be autoloadable, which is normally the case.
|
# has to be autoloadable, which is normally the case.
|
||||||
#
|
#
|
||||||
# Namespaces are supported. The following calls include +Foo::BarHelper+:
|
# Namespaces are supported. The following calls include `Foo::BarHelper`:
|
||||||
#
|
#
|
||||||
# # Module, recommended.
|
# # Module, recommended.
|
||||||
# helper Foo::BarHelper
|
# helper Foo::BarHelper
|
||||||
#
|
#
|
||||||
# # String/symbol without the "helper" suffix, camel or snake case.
|
# # String/symbol without the "helper" suffix, camel or snake case.
|
||||||
# helper "Foo::Bar"
|
# helper "Foo::Bar"
|
||||||
# helper :"Foo::Bar"
|
# helper :"Foo::Bar"
|
||||||
# helper "foo/bar"
|
# helper "foo/bar"
|
||||||
# helper :"foo/bar"
|
# helper :"foo/bar"
|
||||||
#
|
#
|
||||||
# The last two assume that <tt>"foo/bar".camelize</tt> returns "Foo::Bar".
|
# The last two assume that `"foo/bar".camelize` returns "Foo::Bar".
|
||||||
#
|
#
|
||||||
# The method accepts a block too. If present, the block is evaluated in
|
# The method accepts a block too. If present, the block is evaluated in the
|
||||||
# the context of the controller helper module. This simple call makes the
|
# context of the controller helper module. This simple call makes the `wadus`
|
||||||
# +wadus+ method available in templates of the enclosing controller:
|
# method available in templates of the enclosing controller:
|
||||||
#
|
#
|
||||||
# helper do
|
# helper do
|
||||||
# def wadus
|
# def wadus
|
||||||
# "wadus"
|
# "wadus"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Furthermore, all the above styles can be mixed together:
|
# Furthermore, all the above styles can be mixed together:
|
||||||
#
|
#
|
||||||
# helper FooHelper, "woo", "bar/baz" do
|
# helper FooHelper, "woo", "bar/baz" do
|
||||||
# def wadus
|
# def wadus
|
||||||
# "wadus"
|
# "wadus"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
def helper(*args, &block)
|
def helper(*args, &block)
|
||||||
modules_for_helpers(args).each do |mod|
|
modules_for_helpers(args).each do |mod|
|
||||||
@ -201,8 +202,8 @@ def helper(*args, &block)
|
|||||||
_helpers_for_modification.module_eval(&block) if block_given?
|
_helpers_for_modification.module_eval(&block) if block_given?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Clears up all existing helpers in this class, only keeping the helper
|
# Clears up all existing helpers in this class, only keeping the helper with the
|
||||||
# with the same name as this class.
|
# same name as this class.
|
||||||
def clear_helpers
|
def clear_helpers
|
||||||
inherited_helper_methods = _helper_methods
|
inherited_helper_methods = _helper_methods
|
||||||
self._helpers = Module.new
|
self._helpers = Module.new
|
||||||
@ -221,8 +222,8 @@ def _helpers_for_modification
|
|||||||
|
|
||||||
private
|
private
|
||||||
def define_helpers_module(klass, helpers = nil)
|
def define_helpers_module(klass, helpers = nil)
|
||||||
# In some tests inherited is called explicitly. In that case, just
|
# In some tests inherited is called explicitly. In that case, just return the
|
||||||
# return the module from the first time it was defined
|
# module from the first time it was defined
|
||||||
return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
|
return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
|
||||||
|
|
||||||
mod = Module.new
|
mod = Module.new
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/benchmarkable"
|
require "active_support/benchmarkable"
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/module/introspection"
|
require "active_support/core_ext/module/introspection"
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "abstract_controller/error"
|
require "abstract_controller/error"
|
||||||
require "action_view"
|
require "action_view"
|
||||||
require "action_view/view_paths"
|
require "action_view/view_paths"
|
||||||
@ -19,9 +21,9 @@ module Rendering
|
|||||||
include ActionView::ViewPaths
|
include ActionView::ViewPaths
|
||||||
|
|
||||||
# Normalizes arguments and options, and then delegates to render_to_body and
|
# Normalizes arguments and options, and then delegates to render_to_body and
|
||||||
# sticks the result in <tt>self.response_body</tt>.
|
# sticks the result in `self.response_body`.
|
||||||
#
|
#
|
||||||
# Supported options depend on the underlying +render_to_body+ implementation.
|
# Supported options depend on the underlying `render_to_body` implementation.
|
||||||
def render(*args, &block)
|
def render(*args, &block)
|
||||||
options = _normalize_render(*args, &block)
|
options = _normalize_render(*args, &block)
|
||||||
rendered_body = render_to_body(options)
|
rendered_body = render_to_body(options)
|
||||||
@ -35,11 +37,11 @@ def render(*args, &block)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Similar to #render, but only returns the rendered template as a string,
|
# Similar to #render, but only returns the rendered template as a string,
|
||||||
# instead of setting +self.response_body+.
|
# instead of setting `self.response_body`.
|
||||||
#
|
#
|
||||||
# If a component extends the semantics of +response_body+ (as ActionController
|
# If a component extends the semantics of `response_body` (as ActionController
|
||||||
# extends it to be anything that responds to the method each), this method
|
# extends it to be anything that responds to the method each), this method needs
|
||||||
# needs to be overridden in order to still return a string.
|
# to be overridden in order to still return a string.
|
||||||
def render_to_string(*args, &block)
|
def render_to_string(*args, &block)
|
||||||
options = _normalize_render(*args, &block)
|
options = _normalize_render(*args, &block)
|
||||||
render_to_body(options)
|
render_to_body(options)
|
||||||
@ -49,15 +51,15 @@ def render_to_string(*args, &block)
|
|||||||
def render_to_body(options = {})
|
def render_to_body(options = {})
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns +Content-Type+ of rendered content.
|
# Returns `Content-Type` of rendered content.
|
||||||
def rendered_format
|
def rendered_format
|
||||||
Mime[:text]
|
Mime[:text]
|
||||||
end
|
end
|
||||||
|
|
||||||
DEFAULT_PROTECTED_INSTANCE_VARIABLES = %i(@_action_name @_response_body @_formats @_prefixes)
|
DEFAULT_PROTECTED_INSTANCE_VARIABLES = %i(@_action_name @_response_body @_formats @_prefixes)
|
||||||
|
|
||||||
# This method should return a hash with assigns.
|
# This method should return a hash with assigns. You can overwrite this
|
||||||
# You can overwrite this configuration per controller.
|
# configuration per controller.
|
||||||
def view_assigns
|
def view_assigns
|
||||||
variables = instance_variables - _protected_ivars
|
variables = instance_variables - _protected_ivars
|
||||||
|
|
||||||
@ -67,9 +69,8 @@ def view_assigns
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Normalize args by converting <tt>render "foo"</tt> to
|
# Normalize args by converting `render "foo"` to `render action: "foo"` and
|
||||||
# <tt>render action: "foo"</tt> and <tt>render "foo/bar"</tt> to
|
# `render "foo/bar"` to `render file: "foo/bar"`.
|
||||||
# <tt>render file: "foo/bar"</tt>.
|
|
||||||
def _normalize_args(action = nil, options = {}) # :doc:
|
def _normalize_args(action = nil, options = {}) # :doc:
|
||||||
if action.respond_to?(:permitted?)
|
if action.respond_to?(:permitted?)
|
||||||
if action.permitted?
|
if action.permitted?
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/html_safe_translation"
|
require "active_support/html_safe_translation"
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
module Translation
|
module Translation
|
||||||
# Delegates to <tt>I18n.translate</tt>.
|
# Delegates to `I18n.translate`.
|
||||||
#
|
#
|
||||||
# When the given key starts with a period, it will be scoped by the current
|
# When the given key starts with a period, it will be scoped by the current
|
||||||
# controller and action. So if you call <tt>translate(".foo")</tt> from
|
# controller and action. So if you call `translate(".foo")` from
|
||||||
# <tt>PeopleController#index</tt>, it will convert the call to
|
# `PeopleController#index`, it will convert the call to
|
||||||
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
|
# `I18n.translate("people.index.foo")`. This makes it less repetitive to
|
||||||
# to translate many keys within the same controller / action and gives you a
|
# translate many keys within the same controller / action and gives you a simple
|
||||||
# simple framework for scoping them consistently.
|
# framework for scoping them consistently.
|
||||||
def translate(key, **options)
|
def translate(key, **options)
|
||||||
if key&.start_with?(".")
|
if key&.start_with?(".")
|
||||||
path = controller_path.tr("/", ".")
|
path = controller_path.tr("/", ".")
|
||||||
@ -25,7 +27,7 @@ def translate(key, **options)
|
|||||||
end
|
end
|
||||||
alias :t :translate
|
alias :t :translate
|
||||||
|
|
||||||
# Delegates to <tt>I18n.localize</tt>.
|
# Delegates to `I18n.localize`.
|
||||||
def localize(object, **options)
|
def localize(object, **options)
|
||||||
I18n.localize(object, **options)
|
I18n.localize(object, **options)
|
||||||
end
|
end
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module AbstractController
|
module AbstractController
|
||||||
# = URL For
|
# # URL For
|
||||||
#
|
#
|
||||||
# Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
|
# Includes `url_for` into the host class (e.g. an abstract controller or
|
||||||
# has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
|
# mailer). The class has to provide a `RouteSet` by implementing the `_routes`
|
||||||
# exception will be raised.
|
# methods. Otherwise, an exception will be raised.
|
||||||
#
|
#
|
||||||
# Note that this module is completely decoupled from HTTP - the only requirement is a valid
|
# Note that this module is completely decoupled from HTTP - the only requirement
|
||||||
# <tt>_routes</tt> implementation.
|
# is a valid `_routes` implementation.
|
||||||
module UrlFor
|
module UrlFor
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include ActionDispatch::Routing::UrlFor
|
include ActionDispatch::Routing::UrlFor
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "abstract_controller"
|
require "abstract_controller"
|
||||||
require "action_dispatch"
|
require "action_dispatch"
|
||||||
require "action_controller/deprecator"
|
require "action_controller/deprecator"
|
||||||
require "action_controller/metal/strong_parameters"
|
require "action_controller/metal/strong_parameters"
|
||||||
require "action_controller/metal/exceptions"
|
require "action_controller/metal/exceptions"
|
||||||
|
|
||||||
# = Action Controller
|
# # Action Controller
|
||||||
#
|
#
|
||||||
# Action Controller is a module of Action Pack.
|
# Action Controller is a module of Action Pack.
|
||||||
#
|
#
|
||||||
|
@ -1,107 +1,108 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_view"
|
require "action_view"
|
||||||
require "action_controller"
|
require "action_controller"
|
||||||
require "action_controller/log_subscriber"
|
require "action_controller/log_subscriber"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \API
|
# # Action Controller API
|
||||||
#
|
#
|
||||||
# API Controller is a lightweight version of ActionController::Base,
|
# API Controller is a lightweight version of ActionController::Base, created for
|
||||||
# created for applications that don't require all functionalities that a complete
|
# applications that don't require all functionalities that a complete Rails
|
||||||
# \Rails controller provides, allowing you to create controllers with just the
|
# controller provides, allowing you to create controllers with just the features
|
||||||
# features that you need for API only applications.
|
# that you need for API only applications.
|
||||||
#
|
#
|
||||||
# An API Controller is different from a normal controller in the sense that
|
# An API Controller is different from a normal controller in the sense that by
|
||||||
# by default it doesn't include a number of features that are usually required
|
# default it doesn't include a number of features that are usually required by
|
||||||
# by browser access only: layouts and templates rendering,
|
# browser access only: layouts and templates rendering, flash, assets, and so
|
||||||
# flash, assets, and so on. This makes the entire controller stack thinner,
|
# on. This makes the entire controller stack thinner, suitable for API
|
||||||
# suitable for API applications. It doesn't mean you won't have such
|
# applications. It doesn't mean you won't have such features if you need them:
|
||||||
# features if you need them: they're all available for you to include in
|
# they're all available for you to include in your application, they're just not
|
||||||
# your application, they're just not part of the default API controller stack.
|
# part of the default API controller stack.
|
||||||
#
|
#
|
||||||
# Normally, +ApplicationController+ is the only controller that inherits from
|
# Normally, `ApplicationController` is the only controller that inherits from
|
||||||
# +ActionController::API+. All other controllers in turn inherit from
|
# `ActionController::API`. All other controllers in turn inherit from
|
||||||
# +ApplicationController+.
|
# `ApplicationController`.
|
||||||
#
|
#
|
||||||
# A sample controller could look like this:
|
# A sample controller could look like this:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# def index
|
# def index
|
||||||
# posts = Post.all
|
# posts = Post.all
|
||||||
# render json: posts
|
# render json: posts
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Request, response, and parameters objects all work the exact same way as
|
# Request, response, and parameters objects all work the exact same way as
|
||||||
# ActionController::Base.
|
# ActionController::Base.
|
||||||
#
|
#
|
||||||
# == Renders
|
# ## Renders
|
||||||
#
|
#
|
||||||
# The default API Controller stack includes all renderers, which means you
|
# The default API Controller stack includes all renderers, which means you can
|
||||||
# can use <tt>render :json</tt> and siblings freely in your controllers. Keep
|
# use `render :json` and siblings freely in your controllers. Keep in mind that
|
||||||
# in mind that templates are not going to be rendered, so you need to ensure
|
# templates are not going to be rendered, so you need to ensure your controller
|
||||||
# your controller is calling either <tt>render</tt> or <tt>redirect_to</tt> in
|
# is calling either `render` or `redirect_to` in all actions, otherwise it will
|
||||||
# all actions, otherwise it will return <tt>204 No Content</tt>.
|
# return `204 No Content`.
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# post = Post.find(params[:id])
|
# post = Post.find(params[:id])
|
||||||
# render json: post
|
# render json: post
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# == Redirects
|
# ## Redirects
|
||||||
#
|
#
|
||||||
# Redirects are used to move from one action to another. You can use the
|
# Redirects are used to move from one action to another. You can use the
|
||||||
# <tt>redirect_to</tt> method in your controllers in the same way as in
|
# `redirect_to` method in your controllers in the same way as in
|
||||||
# ActionController::Base. For example:
|
# ActionController::Base. For example:
|
||||||
#
|
#
|
||||||
# def create
|
# def create
|
||||||
# redirect_to root_url and return if not_authorized?
|
# redirect_to root_url and return if not_authorized?
|
||||||
# # do stuff here
|
# # do stuff here
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# == Adding New Behavior
|
# ## Adding New Behavior
|
||||||
#
|
#
|
||||||
# In some scenarios you may want to add back some functionality provided by
|
# In some scenarios you may want to add back some functionality provided by
|
||||||
# ActionController::Base that is not present by default in
|
# ActionController::Base that is not present by default in
|
||||||
# +ActionController::API+, for instance <tt>MimeResponds</tt>. This
|
# `ActionController::API`, for instance `MimeResponds`. This module gives you
|
||||||
# module gives you the <tt>respond_to</tt> method. Adding it is quite simple,
|
# the `respond_to` method. Adding it is quite simple, you just need to include
|
||||||
# you just need to include the module in a specific controller or in
|
# the module in a specific controller or in `ApplicationController` in case you
|
||||||
# +ApplicationController+ in case you want it available in your entire
|
# want it available in your entire application:
|
||||||
# application:
|
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::API
|
# class ApplicationController < ActionController::API
|
||||||
# include ActionController::MimeResponds
|
# include ActionController::MimeResponds
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# def index
|
# def index
|
||||||
# posts = Post.all
|
# posts = Post.all
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.json { render json: posts }
|
# format.json { render json: posts }
|
||||||
# format.xml { render xml: posts }
|
# format.xml { render xml: posts }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Make sure to check the modules included in ActionController::Base
|
# Make sure to check the modules included in ActionController::Base if you want
|
||||||
# if you want to use any other functionality that is not provided
|
# to use any other functionality that is not provided by `ActionController::API`
|
||||||
# by +ActionController::API+ out of the box.
|
# out of the box.
|
||||||
class API < Metal
|
class API < Metal
|
||||||
abstract!
|
abstract!
|
||||||
|
|
||||||
# Shortcut helper that returns all the ActionController::API modules except
|
# Shortcut helper that returns all the ActionController::API modules except the
|
||||||
# the ones passed as arguments:
|
# ones passed as arguments:
|
||||||
#
|
#
|
||||||
# class MyAPIBaseController < ActionController::Metal
|
# class MyAPIBaseController < ActionController::Metal
|
||||||
# ActionController::API.without_modules(:UrlFor).each do |left|
|
# ActionController::API.without_modules(:UrlFor).each do |left|
|
||||||
# include left
|
# include left
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# This gives better control over what you want to exclude and makes it easier
|
# This gives better control over what you want to exclude and makes it easier to
|
||||||
# to create an API controller class, instead of listing the modules required
|
# create an API controller class, instead of listing the modules required
|
||||||
# manually.
|
# manually.
|
||||||
def self.without_modules(*modules)
|
def self.without_modules(*modules)
|
||||||
modules = modules.map do |m|
|
modules = modules.map do |m|
|
||||||
@ -127,19 +128,19 @@ def self.without_modules(*modules)
|
|||||||
DefaultHeaders,
|
DefaultHeaders,
|
||||||
Logging,
|
Logging,
|
||||||
|
|
||||||
# Before callbacks should also be executed as early as possible, so
|
# Before callbacks should also be executed as early as possible, so also include
|
||||||
# also include them at the bottom.
|
# them at the bottom.
|
||||||
AbstractController::Callbacks,
|
AbstractController::Callbacks,
|
||||||
|
|
||||||
# Append rescue at the bottom to wrap as much as possible.
|
# Append rescue at the bottom to wrap as much as possible.
|
||||||
Rescue,
|
Rescue,
|
||||||
|
|
||||||
# Add instrumentations hooks at the bottom, to ensure they instrument
|
# Add instrumentations hooks at the bottom, to ensure they instrument all the
|
||||||
# all the methods properly.
|
# methods properly.
|
||||||
Instrumentation,
|
Instrumentation,
|
||||||
|
|
||||||
# Params wrapper should come before instrumentation so they are
|
# Params wrapper should come before instrumentation so they are properly showed
|
||||||
# properly showed in logs
|
# in logs
|
||||||
ParamsWrapper
|
ParamsWrapper
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module ApiRendering
|
module ApiRendering
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
@ -1,170 +1,205 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_view"
|
require "action_view"
|
||||||
require "action_controller/log_subscriber"
|
require "action_controller/log_subscriber"
|
||||||
require "action_controller/metal/params_wrapper"
|
require "action_controller/metal/params_wrapper"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \Base
|
# # Action Controller Base
|
||||||
#
|
#
|
||||||
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
|
# Action Controllers are the core of a web request in Rails. They are made up of
|
||||||
# on request and then either it renders a template or redirects to another action. An action is defined as a public method
|
# one or more actions that are executed on request and then either it renders a
|
||||||
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
|
# template or redirects to another action. An action is defined as a public
|
||||||
|
# method on the controller, which will automatically be made accessible to the
|
||||||
|
# web-server through Rails Routes.
|
||||||
#
|
#
|
||||||
# By default, only the ApplicationController in a \Rails application inherits from +ActionController::Base+. All other
|
# By default, only the ApplicationController in a Rails application inherits
|
||||||
# controllers inherit from ApplicationController. This gives you one class to configure things such as
|
# from `ActionController::Base`. All other controllers inherit from
|
||||||
|
# ApplicationController. This gives you one class to configure things such as
|
||||||
# request forgery protection and filtering of sensitive request parameters.
|
# request forgery protection and filtering of sensitive request parameters.
|
||||||
#
|
#
|
||||||
# A sample controller could look like this:
|
# A sample controller could look like this:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# def index
|
# def index
|
||||||
# @posts = Post.all
|
# @posts = Post.all
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def create
|
||||||
|
# @post = Post.create params[:post]
|
||||||
|
# redirect_to posts_path
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def create
|
# Actions, by default, render a template in the `app/views` directory
|
||||||
# @post = Post.create params[:post]
|
# corresponding to the name of the controller and action after executing code in
|
||||||
# redirect_to posts_path
|
# the action. For example, the `index` action of the PostsController would
|
||||||
|
# render the template `app/views/posts/index.html.erb` by default after
|
||||||
|
# populating the `@posts` instance variable.
|
||||||
|
#
|
||||||
|
# Unlike index, the create action will not render a template. After performing
|
||||||
|
# its main purpose (creating a new post), it initiates a redirect instead. This
|
||||||
|
# redirect works by returning an external `302 Moved` HTTP response that takes
|
||||||
|
# the user to the index action.
|
||||||
|
#
|
||||||
|
# These two methods represent the two basic action archetypes used in Action
|
||||||
|
# Controllers: Get-and-show and do-and-redirect. Most actions are variations on
|
||||||
|
# these themes.
|
||||||
|
#
|
||||||
|
# ## Requests
|
||||||
|
#
|
||||||
|
# For every request, the router determines the value of the `controller` and
|
||||||
|
# `action` keys. These determine which controller and action are called. The
|
||||||
|
# remaining request parameters, the session (if one is available), and the full
|
||||||
|
# request with all the HTTP headers are made available to the action through
|
||||||
|
# accessor methods. Then the action is performed.
|
||||||
|
#
|
||||||
|
# The full request object is available via the request accessor and is primarily
|
||||||
|
# used to query for HTTP headers:
|
||||||
|
#
|
||||||
|
# def server_ip
|
||||||
|
# location = request.env["REMOTE_ADDR"]
|
||||||
|
# render plain: "This server hosted at #{location}"
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
|
# ## Parameters
|
||||||
# after executing code in the action. For example, the +index+ action of the PostsController would render the
|
|
||||||
# template <tt>app/views/posts/index.html.erb</tt> by default after populating the <tt>@posts</tt> instance variable.
|
|
||||||
#
|
#
|
||||||
# Unlike index, the create action will not render a template. After performing its main purpose (creating a
|
# All request parameters, whether they come from a query string in the URL or
|
||||||
# new post), it initiates a redirect instead. This redirect works by returning an external
|
# form data submitted through a POST request are available through the `params`
|
||||||
# <tt>302 Moved</tt> HTTP response that takes the user to the index action.
|
# method which returns a hash. For example, an action that was performed through
|
||||||
|
# `/posts?category=All&limit=5` will include `{ "category" => "All", "limit" =>
|
||||||
|
# "5" }` in `params`.
|
||||||
#
|
#
|
||||||
# These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect.
|
# It's also possible to construct multi-dimensional parameter hashes by
|
||||||
# Most actions are variations on these themes.
|
# specifying keys using brackets, such as:
|
||||||
#
|
#
|
||||||
# == Requests
|
# <input type="text" name="post[name]" value="david">
|
||||||
|
# <input type="text" name="post[address]" value="hyacintvej">
|
||||||
#
|
#
|
||||||
# For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller
|
# A request coming from a form holding these inputs will include `{ "post" => {
|
||||||
# and action are called. The remaining request parameters, the session (if one is available), and the full request with
|
# "name" => "david", "address" => "hyacintvej" } }`. If the address input had
|
||||||
# all the HTTP headers are made available to the action through accessor methods. Then the action is performed.
|
# been named `post[address][street]`, the `params` would have included `{ "post"
|
||||||
|
# => { "address" => { "street" => "hyacintvej" } } }`. There's no limit to the
|
||||||
|
# depth of the nesting.
|
||||||
#
|
#
|
||||||
# The full request object is available via the request accessor and is primarily used to query for HTTP headers:
|
# ## Sessions
|
||||||
#
|
#
|
||||||
# def server_ip
|
# Sessions allow you to store objects in between requests. This is useful for
|
||||||
# location = request.env["REMOTE_ADDR"]
|
# objects that are not yet ready to be persisted, such as a Signup object
|
||||||
# render plain: "This server hosted at #{location}"
|
# constructed in a multi-paged process, or objects that don't change much and
|
||||||
# end
|
# are needed all the time, such as a User object for a system that requires
|
||||||
|
# login. The session should not be used, however, as a cache for objects where
|
||||||
|
# it's likely they could be changed unknowingly. It's usually too much work to
|
||||||
|
# keep it all synchronized -- something databases already excel at.
|
||||||
#
|
#
|
||||||
# == Parameters
|
# You can place objects in the session by using the `session` method, which
|
||||||
|
# accesses a hash:
|
||||||
#
|
#
|
||||||
# All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are
|
# session[:person] = Person.authenticate(user_name, password)
|
||||||
# available through the <tt>params</tt> method which returns a hash. For example, an action that was performed through
|
|
||||||
# <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in <tt>params</tt>.
|
|
||||||
#
|
|
||||||
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
|
|
||||||
#
|
|
||||||
# <input type="text" name="post[name]" value="david">
|
|
||||||
# <input type="text" name="post[address]" value="hyacintvej">
|
|
||||||
#
|
|
||||||
# A request coming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
|
|
||||||
# If the address input had been named <tt>post[address][street]</tt>, the <tt>params</tt> would have included
|
|
||||||
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
|
|
||||||
#
|
|
||||||
# == Sessions
|
|
||||||
#
|
|
||||||
# Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
|
|
||||||
# such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
|
|
||||||
# as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
|
|
||||||
# they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
|
|
||||||
#
|
|
||||||
# You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
|
|
||||||
#
|
|
||||||
# session[:person] = Person.authenticate(user_name, password)
|
|
||||||
#
|
#
|
||||||
# You can retrieve it again through the same hash:
|
# You can retrieve it again through the same hash:
|
||||||
#
|
#
|
||||||
# "Hello #{session[:person]}"
|
# "Hello #{session[:person]}"
|
||||||
#
|
#
|
||||||
# For removing objects from the session, you can either assign a single key to +nil+:
|
# For removing objects from the session, you can either assign a single key to
|
||||||
|
# `nil`:
|
||||||
#
|
#
|
||||||
# # removes :person from session
|
# # removes :person from session
|
||||||
# session[:person] = nil
|
# session[:person] = nil
|
||||||
#
|
#
|
||||||
# or you can remove the entire session with +reset_session+.
|
# or you can remove the entire session with `reset_session`.
|
||||||
#
|
#
|
||||||
# By default, sessions are stored in an encrypted browser cookie (see
|
# By default, sessions are stored in an encrypted browser cookie (see
|
||||||
# ActionDispatch::Session::CookieStore). Thus the user will not be able to
|
# ActionDispatch::Session::CookieStore). Thus the user will not be able to read
|
||||||
# read or edit the session data. However, the user can keep a copy of the
|
# or edit the session data. However, the user can keep a copy of the cookie even
|
||||||
# cookie even after it has expired, so you should avoid storing sensitive
|
# after it has expired, so you should avoid storing sensitive information in
|
||||||
# information in cookie-based sessions.
|
# cookie-based sessions.
|
||||||
#
|
#
|
||||||
# == Responses
|
# ## Responses
|
||||||
#
|
#
|
||||||
# Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
|
# Each action results in a response, which holds the headers and document to be
|
||||||
# object is generated automatically through the use of renders and redirects and requires no user intervention.
|
# sent to the user's browser. The actual response object is generated
|
||||||
|
# automatically through the use of renders and redirects and requires no user
|
||||||
|
# intervention.
|
||||||
#
|
#
|
||||||
# == Renders
|
# ## Renders
|
||||||
#
|
#
|
||||||
# Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
|
# Action Controller sends content to the user by using one of five rendering
|
||||||
# of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured.
|
# methods. The most versatile and common is the rendering of a template.
|
||||||
# The controller passes objects to the view by assigning instance variables:
|
# Included in the Action Pack is the Action View, which enables rendering of ERB
|
||||||
|
# templates. It's automatically configured. The controller passes objects to the
|
||||||
|
# view by assigning instance variables:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @post = Post.find(params[:id])
|
# @post = Post.find(params[:id])
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Which are then automatically available to the view:
|
# Which are then automatically available to the view:
|
||||||
#
|
#
|
||||||
# Title: <%= @post.title %>
|
# Title: <%= @post.title %>
|
||||||
#
|
#
|
||||||
# You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
|
# You don't have to rely on the automated rendering. For example, actions that
|
||||||
# will use the manual rendering methods:
|
# could result in the rendering of different templates will use the manual
|
||||||
|
# rendering methods:
|
||||||
#
|
#
|
||||||
# def search
|
# def search
|
||||||
# @results = Search.find(params[:query])
|
# @results = Search.find(params[:query])
|
||||||
# case @results.count
|
# case @results.count
|
||||||
# when 0 then render action: "no_results"
|
# when 0 then render action: "no_results"
|
||||||
# when 1 then render action: "show"
|
# when 1 then render action: "show"
|
||||||
# when 2..10 then render action: "show_many"
|
# when 2..10 then render action: "show_many"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Read more about writing ERB and Builder templates in ActionView::Base.
|
# Read more about writing ERB and Builder templates in ActionView::Base.
|
||||||
#
|
#
|
||||||
# == Redirects
|
# ## Redirects
|
||||||
#
|
#
|
||||||
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
|
# Redirects are used to move from one action to another. For example, after a
|
||||||
# database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
|
# `create` action, which stores a blog entry to the database, we might like to
|
||||||
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
|
# show the user the new entry. Because we're following good DRY principles
|
||||||
|
# (Don't Repeat Yourself), we're going to reuse (and redirect to) a `show`
|
||||||
|
# action that we'll assume has already been created. The code might look like
|
||||||
|
# this:
|
||||||
#
|
#
|
||||||
# def create
|
# def create
|
||||||
# @entry = Entry.new(params[:entry])
|
# @entry = Entry.new(params[:entry])
|
||||||
# if @entry.save
|
# if @entry.save
|
||||||
# # The entry was saved correctly, redirect to show
|
# # The entry was saved correctly, redirect to show
|
||||||
# redirect_to action: 'show', id: @entry.id
|
# redirect_to action: 'show', id: @entry.id
|
||||||
# else
|
# else
|
||||||
# # things didn't go so well, do something else
|
# # things didn't go so well, do something else
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed.
|
# In this case, after saving our new entry to the database, the user is
|
||||||
# Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action),
|
# redirected to the `show` method, which is then executed. Note that this is an
|
||||||
# and not some internal re-routing which calls both "create" and then "show" within one request.
|
# external HTTP-level redirection which will cause the browser to make a second
|
||||||
|
# request (a GET to the show action), and not some internal re-routing which
|
||||||
|
# calls both "create" and then "show" within one request.
|
||||||
#
|
#
|
||||||
# Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting.
|
# Learn more about `redirect_to` and what options you have in
|
||||||
|
# ActionController::Redirecting.
|
||||||
#
|
#
|
||||||
# == Calling multiple redirects or renders
|
# ## Calling multiple redirects or renders
|
||||||
#
|
#
|
||||||
# An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
|
# An action may contain only a single render or a single redirect. Attempting to
|
||||||
|
# try to do either again will result in a DoubleRenderError:
|
||||||
#
|
#
|
||||||
# def do_something
|
# def do_something
|
||||||
# redirect_to action: "elsewhere"
|
# redirect_to action: "elsewhere"
|
||||||
# render action: "overthere" # raises DoubleRenderError
|
# render action: "overthere" # raises DoubleRenderError
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
|
# If you need to redirect on the condition of something, then be sure to add
|
||||||
|
# "and return" to halt execution.
|
||||||
#
|
#
|
||||||
# def do_something
|
# def do_something
|
||||||
# redirect_to(action: "elsewhere") and return if monkeys.nil?
|
# redirect_to(action: "elsewhere") and return if monkeys.nil?
|
||||||
# render action: "overthere" # won't be called if monkeys is nil
|
# render action: "overthere" # won't be called if monkeys is nil
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
class Base < Metal
|
class Base < Metal
|
||||||
abstract!
|
abstract!
|
||||||
@ -172,15 +207,15 @@ class Base < Metal
|
|||||||
# Shortcut helper that returns all the modules included in
|
# Shortcut helper that returns all the modules included in
|
||||||
# ActionController::Base except the ones passed as arguments:
|
# ActionController::Base except the ones passed as arguments:
|
||||||
#
|
#
|
||||||
# class MyBaseController < ActionController::Metal
|
# class MyBaseController < ActionController::Metal
|
||||||
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
|
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
|
||||||
# include left
|
# include left
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# This gives better control over what you want to exclude and makes it
|
# This gives better control over what you want to exclude and makes it easier to
|
||||||
# easier to create a bare controller class, instead of listing the modules
|
# create a bare controller class, instead of listing the modules required
|
||||||
# required manually.
|
# manually.
|
||||||
def self.without_modules(*modules)
|
def self.without_modules(*modules)
|
||||||
modules = modules.map do |m|
|
modules = modules.map do |m|
|
||||||
m.is_a?(Symbol) ? ActionController.const_get(m) : m
|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
|
||||||
@ -224,19 +259,19 @@ def self.without_modules(*modules)
|
|||||||
DefaultHeaders,
|
DefaultHeaders,
|
||||||
Logging,
|
Logging,
|
||||||
|
|
||||||
# Before callbacks should also be executed as early as possible, so
|
# Before callbacks should also be executed as early as possible, so also include
|
||||||
# also include them at the bottom.
|
# them at the bottom.
|
||||||
AbstractController::Callbacks,
|
AbstractController::Callbacks,
|
||||||
|
|
||||||
# Append rescue at the bottom to wrap as much as possible.
|
# Append rescue at the bottom to wrap as much as possible.
|
||||||
Rescue,
|
Rescue,
|
||||||
|
|
||||||
# Add instrumentations hooks at the bottom, to ensure they instrument
|
# Add instrumentations hooks at the bottom, to ensure they instrument all the
|
||||||
# all the methods properly.
|
# methods properly.
|
||||||
Instrumentation,
|
Instrumentation,
|
||||||
|
|
||||||
# Params wrapper should come before instrumentation so they are
|
# Params wrapper should come before instrumentation so they are properly showed
|
||||||
# properly showed in logs
|
# in logs
|
||||||
ParamsWrapper
|
ParamsWrapper
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \Caching
|
# # Action Controller Caching
|
||||||
#
|
#
|
||||||
# \Caching is a cheap way of speeding up slow applications by keeping the result of
|
# Caching is a cheap way of speeding up slow applications by keeping the result
|
||||||
# calculations, renderings, and database calls around for subsequent requests.
|
# of calculations, renderings, and database calls around for subsequent
|
||||||
|
# requests.
|
||||||
#
|
#
|
||||||
# You can read more about each approach by clicking the modules below.
|
# You can read more about each approach by clicking the modules below.
|
||||||
#
|
#
|
||||||
# Note: To turn off all caching provided by Action Controller, set
|
# Note: To turn off all caching provided by Action Controller, set
|
||||||
# config.action_controller.perform_caching = false
|
# config.action_controller.perform_caching = false
|
||||||
#
|
#
|
||||||
# == \Caching stores
|
# ## Caching stores
|
||||||
#
|
#
|
||||||
# All the caching stores from ActiveSupport::Cache are available to be used as backends
|
# All the caching stores from ActiveSupport::Cache are available to be used as
|
||||||
# for Action Controller caching.
|
# backends for Action Controller caching.
|
||||||
#
|
#
|
||||||
# Configuration examples (FileStore is the default):
|
# Configuration examples (FileStore is the default):
|
||||||
#
|
#
|
||||||
# config.action_controller.cache_store = :memory_store
|
# config.action_controller.cache_store = :memory_store
|
||||||
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
|
# config.action_controller.cache_store = :file_store, '/path/to/cache/directory'
|
||||||
# config.action_controller.cache_store = :mem_cache_store, 'localhost'
|
# config.action_controller.cache_store = :mem_cache_store, 'localhost'
|
||||||
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
|
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
|
||||||
# config.action_controller.cache_store = MyOwnStore.new('parameter')
|
# config.action_controller.cache_store = MyOwnStore.new('parameter')
|
||||||
module Caching
|
module Caching
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
def self.deprecator # :nodoc:
|
def self.deprecator # :nodoc:
|
||||||
AbstractController.deprecator
|
AbstractController.deprecator
|
||||||
|
@ -1,31 +1,33 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller Form Builder
|
# # Action Controller Form Builder
|
||||||
#
|
#
|
||||||
# Override the default form builder for all views rendered by this
|
# Override the default form builder for all views rendered by this controller
|
||||||
# controller and any of its descendants. Accepts a subclass of
|
# and any of its descendants. Accepts a subclass of
|
||||||
# ActionView::Helpers::FormBuilder.
|
# ActionView::Helpers::FormBuilder.
|
||||||
#
|
#
|
||||||
# For example, given a form builder:
|
# For example, given a form builder:
|
||||||
#
|
#
|
||||||
# class AdminFormBuilder < ActionView::Helpers::FormBuilder
|
# class AdminFormBuilder < ActionView::Helpers::FormBuilder
|
||||||
# def special_field(name)
|
# def special_field(name)
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# The controller specifies a form builder as its default:
|
# The controller specifies a form builder as its default:
|
||||||
#
|
#
|
||||||
# class AdminAreaController < ApplicationController
|
# class AdminAreaController < ApplicationController
|
||||||
# default_form_builder AdminFormBuilder
|
# default_form_builder AdminFormBuilder
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Then in the view any form using +form_for+ will be an instance of the
|
# Then in the view any form using `form_for` will be an instance of the
|
||||||
# specified form builder:
|
# specified form builder:
|
||||||
#
|
#
|
||||||
# <%= form_for(@instance) do |builder| %>
|
# <%= form_for(@instance) do |builder| %>
|
||||||
# <%= builder.special_field(:name) %>
|
# <%= builder.special_field(:name) %>
|
||||||
# <% end %>
|
# <% end %>
|
||||||
module FormBuilder
|
module FormBuilder
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
@ -34,11 +36,12 @@ module FormBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Set the form builder to be used as the default for all forms
|
# Set the form builder to be used as the default for all forms in the views
|
||||||
# in the views rendered by this controller and its subclasses.
|
# rendered by this controller and its subclasses.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
# * <tt>builder</tt> - Default form builder, an instance of ActionView::Helpers::FormBuilder
|
# * `builder` - Default form builder, an instance of
|
||||||
|
# ActionView::Helpers::FormBuilder
|
||||||
def default_form_builder(builder)
|
def default_form_builder(builder)
|
||||||
self._default_form_builder = builder
|
self._default_form_builder = builder
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
class LogSubscriber < ActiveSupport::LogSubscriber
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
||||||
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/array/extract_options"
|
require "active_support/core_ext/array/extract_options"
|
||||||
require "action_dispatch/middleware/stack"
|
require "action_dispatch/middleware/stack"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \MiddlewareStack
|
# # Action Controller MiddlewareStack
|
||||||
#
|
#
|
||||||
# Extend ActionDispatch middleware stack to make it aware of options
|
# Extend ActionDispatch middleware stack to make it aware of options allowing
|
||||||
# allowing the following syntax in controllers:
|
# the following syntax in controllers:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# use AuthenticationMiddleware, except: [:index, :show]
|
# use AuthenticationMiddleware, except: [:index, :show]
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
class MiddlewareStack < ActionDispatch::MiddlewareStack # :nodoc:
|
class MiddlewareStack < ActionDispatch::MiddlewareStack # :nodoc:
|
||||||
class Middleware < ActionDispatch::MiddlewareStack::Middleware # :nodoc:
|
class Middleware < ActionDispatch::MiddlewareStack::Middleware # :nodoc:
|
||||||
@ -60,73 +62,71 @@ def build_middleware(klass, args, block)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Action Controller \Metal
|
# # Action Controller Metal
|
||||||
#
|
#
|
||||||
# +ActionController::Metal+ is the simplest possible controller, providing a
|
# `ActionController::Metal` is the simplest possible controller, providing a
|
||||||
# valid Rack interface without the additional niceties provided by
|
# valid Rack interface without the additional niceties provided by
|
||||||
# ActionController::Base.
|
# ActionController::Base.
|
||||||
#
|
#
|
||||||
# A sample metal controller might look like this:
|
# A sample metal controller might look like this:
|
||||||
#
|
#
|
||||||
# class HelloController < ActionController::Metal
|
# class HelloController < ActionController::Metal
|
||||||
# def index
|
# def index
|
||||||
# self.response_body = "Hello World!"
|
# self.response_body = "Hello World!"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# And then to route requests to your metal controller, you would add
|
# And then to route requests to your metal controller, you would add something
|
||||||
# something like this to <tt>config/routes.rb</tt>:
|
# like this to `config/routes.rb`:
|
||||||
#
|
#
|
||||||
# get 'hello', to: HelloController.action(:index)
|
# get 'hello', to: HelloController.action(:index)
|
||||||
#
|
#
|
||||||
# The +action+ method returns a valid Rack application for the \Rails
|
# The `action` method returns a valid Rack application for the Rails router to
|
||||||
# router to dispatch to.
|
# dispatch to.
|
||||||
#
|
#
|
||||||
# == \Rendering \Helpers
|
# ## Rendering Helpers
|
||||||
#
|
#
|
||||||
# +ActionController::Metal+ by default provides no utilities for rendering
|
# `ActionController::Metal` by default provides no utilities for rendering
|
||||||
# views, partials, or other responses aside from explicitly calling of
|
# views, partials, or other responses aside from explicitly calling of
|
||||||
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
|
# `response_body=`, `content_type=`, and `status=`. To add the render helpers
|
||||||
# add the render helpers you're used to having in a normal controller, you
|
# you're used to having in a normal controller, you can do the following:
|
||||||
# can do the following:
|
|
||||||
#
|
#
|
||||||
# class HelloController < ActionController::Metal
|
# class HelloController < ActionController::Metal
|
||||||
# include AbstractController::Rendering
|
# include AbstractController::Rendering
|
||||||
# include ActionView::Layouts
|
# include ActionView::Layouts
|
||||||
# append_view_path "#{Rails.root}/app/views"
|
# append_view_path "#{Rails.root}/app/views"
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# render "hello/index"
|
# render "hello/index"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# == Redirection \Helpers
|
# ## Redirection Helpers
|
||||||
#
|
#
|
||||||
# To add redirection helpers to your metal controller, do the following:
|
# To add redirection helpers to your metal controller, do the following:
|
||||||
#
|
#
|
||||||
# class HelloController < ActionController::Metal
|
# class HelloController < ActionController::Metal
|
||||||
# include ActionController::Redirecting
|
# include ActionController::Redirecting
|
||||||
# include Rails.application.routes.url_helpers
|
# include Rails.application.routes.url_helpers
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# redirect_to root_url
|
# redirect_to root_url
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# == Other \Helpers
|
# ## Other Helpers
|
||||||
#
|
|
||||||
# You can refer to the modules included in ActionController::Base to see
|
|
||||||
# other features you can bring into your metal controller.
|
|
||||||
#
|
#
|
||||||
|
# You can refer to the modules included in ActionController::Base to see other
|
||||||
|
# features you can bring into your metal controller.
|
||||||
class Metal < AbstractController::Base
|
class Metal < AbstractController::Base
|
||||||
abstract!
|
abstract!
|
||||||
|
|
||||||
# Returns the last part of the controller's name, underscored, without the ending
|
# Returns the last part of the controller's name, underscored, without the
|
||||||
# <tt>Controller</tt>. For instance, +PostsController+ returns <tt>posts</tt>.
|
# ending `Controller`. For instance, `PostsController` returns `posts`.
|
||||||
# Namespaces are left out, so +Admin::PostsController+ returns <tt>posts</tt> as well.
|
# Namespaces are left out, so `Admin::PostsController` returns `posts` as well.
|
||||||
#
|
#
|
||||||
# ==== Returns
|
# #### Returns
|
||||||
# * <tt>string</tt>
|
# * `string`
|
||||||
def self.controller_name
|
def self.controller_name
|
||||||
@controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
|
@controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
|
||||||
end
|
end
|
||||||
@ -172,7 +172,7 @@ def controller_name
|
|||||||
##
|
##
|
||||||
# The ActionDispatch::Request::Session instance for the current request.
|
# The ActionDispatch::Request::Session instance for the current request.
|
||||||
# See further details in the
|
# See further details in the
|
||||||
# {Active Controller Session guide}[https://guides.rubyonrails.org/action_controller_overview.html#session].
|
# [Active Controller Session guide](https://guides.rubyonrails.org/action_controller_overview.html#session).
|
||||||
delegate :session, to: "@_request"
|
delegate :session, to: "@_request"
|
||||||
|
|
||||||
##
|
##
|
||||||
@ -201,7 +201,7 @@ def params=(val)
|
|||||||
|
|
||||||
alias :response_code :status # :nodoc:
|
alias :response_code :status # :nodoc:
|
||||||
|
|
||||||
# Basic \url_for that can be overridden for more robust functionality.
|
# Basic url_for that can be overridden for more robust functionality.
|
||||||
def url_for(string)
|
def url_for(string)
|
||||||
string
|
string
|
||||||
end
|
end
|
||||||
@ -238,7 +238,8 @@ def set_response!(response) # :nodoc:
|
|||||||
@_response = response
|
@_response = response
|
||||||
end
|
end
|
||||||
|
|
||||||
# Assign the response and mark it as committed. No further processing will occur.
|
# Assign the response and mark it as committed. No further processing will
|
||||||
|
# occur.
|
||||||
def response=(response)
|
def response=(response)
|
||||||
set_response!(response)
|
set_response!(response)
|
||||||
|
|
||||||
@ -271,15 +272,15 @@ def use(...)
|
|||||||
|
|
||||||
# The middleware stack used by this controller.
|
# The middleware stack used by this controller.
|
||||||
#
|
#
|
||||||
# By default uses a variation of ActionDispatch::MiddlewareStack which
|
# By default uses a variation of ActionDispatch::MiddlewareStack which allows
|
||||||
# allows for the following syntax:
|
# for the following syntax:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# use AuthenticationMiddleware, except: [:index, :show]
|
# use AuthenticationMiddleware, except: [:index, :show]
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Read more about {Rails middleware
|
# Read more about [Rails middleware stack]
|
||||||
# stack}[https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack]
|
# (https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
|
||||||
# in the guides.
|
# in the guides.
|
||||||
def self.middleware
|
def self.middleware
|
||||||
middleware_stack
|
middleware_stack
|
||||||
@ -300,8 +301,8 @@ def self.action(name)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Direct dispatch to the controller. Instantiates the controller, then
|
# Direct dispatch to the controller. Instantiates the controller, then executes
|
||||||
# executes the action named +name+.
|
# the action named `name`.
|
||||||
def self.dispatch(name, req, res)
|
def self.dispatch(name, req, res)
|
||||||
if middleware_stack.any?
|
if middleware_stack.any?
|
||||||
middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
|
middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
|
||||||
|
@ -1,41 +1,48 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module AllowBrowser
|
module AllowBrowser
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Specify the browser versions that will be allowed to access all actions (or some, as limited by <tt>only:</tt> or <tt>except:</tt>).
|
# Specify the browser versions that will be allowed to access all actions (or
|
||||||
# Only browsers matched in the hash or named set passed to <tt>versions:</tt> will be blocked if they're below the versions specified.
|
# some, as limited by `only:` or `except:`). Only browsers matched in the hash
|
||||||
# This means that all other browsers, as well as agents that aren't reporting a user-agent header, will be allowed access.
|
# or named set passed to `versions:` will be blocked if they're below the
|
||||||
|
# versions specified. This means that all other browsers, as well as agents that
|
||||||
|
# aren't reporting a user-agent header, will be allowed access.
|
||||||
#
|
#
|
||||||
# A browser that's blocked will by default be served the file in public/426.html with a HTTP status code of "426 Upgrade Required".
|
# A browser that's blocked will by default be served the file in public/426.html
|
||||||
|
# with a HTTP status code of "426 Upgrade Required".
|
||||||
#
|
#
|
||||||
# In addition to specifically named browser versions, you can also pass <tt>:modern</tt> as the set to restrict support to browsers
|
# In addition to specifically named browser versions, you can also pass
|
||||||
# natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
# `:modern` as the set to restrict support to browsers natively supporting webp
|
||||||
# This includes Safari 17.2+, Chrome 119+, Firefox 121+, Opera 104+.
|
# images, web push, badges, import maps, CSS nesting, and CSS :has. This
|
||||||
|
# includes Safari 17.2+, Chrome 119+, Firefox 121+, Opera 104+.
|
||||||
#
|
#
|
||||||
# You can use https://caniuse.com to check for browser versions supporting the features you use.
|
# You can use https://caniuse.com to check for browser versions supporting the
|
||||||
|
# features you use.
|
||||||
#
|
#
|
||||||
# You can use +ActiveSupport::Notifications+ to subscribe to events of browsers being blocked using the +browser_block.action_controller+
|
# You can use `ActiveSupport::Notifications` to subscribe to events of browsers
|
||||||
# event name.
|
# being blocked using the `browser_block.action_controller` event name.
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
|
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
|
||||||
# allow_browser versions: :modern
|
# allow_browser versions: :modern
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
|
# # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
|
||||||
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
|
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class MessagesController < ApplicationController
|
# class MessagesController < ApplicationController
|
||||||
# # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
|
# # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
|
||||||
# allow_browser versions: { opera: 104, chrome: 119 }, only: :show
|
# allow_browser versions: { opera: 104, chrome: 119 }, only: :show
|
||||||
# end
|
# end
|
||||||
def allow_browser(versions:, block: -> { render file: Rails.root.join("public/426.html"), layout: false, status: :upgrade_required }, **options)
|
def allow_browser(versions:, block: -> { render file: Rails.root.join("public/426.html"), layout: false, status: :upgrade_required }, **options)
|
||||||
before_action -> { allow_browser(versions: versions, block: block) }, **options
|
before_action -> { allow_browser(versions: versions, block: block) }, **options
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module BasicImplicitRender # :nodoc:
|
module BasicImplicitRender # :nodoc:
|
||||||
def send_action(method, *args)
|
def send_action(method, *args)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/object/try"
|
require "active_support/core_ext/object/try"
|
||||||
require "active_support/core_ext/integer/time"
|
require "active_support/core_ext/integer/time"
|
||||||
|
|
||||||
@ -14,116 +16,118 @@ module ConditionalGet
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Allows you to consider additional controller-wide information when generating an ETag.
|
# Allows you to consider additional controller-wide information when generating
|
||||||
# For example, if you serve pages tailored depending on who's logged in at the moment, you
|
# an ETag. For example, if you serve pages tailored depending on who's logged in
|
||||||
# may want to add the current user id to be part of the ETag to prevent unauthorized displaying
|
# at the moment, you may want to add the current user id to be part of the ETag
|
||||||
# of cached pages.
|
# to prevent unauthorized displaying of cached pages.
|
||||||
#
|
#
|
||||||
# class InvoicesController < ApplicationController
|
# class InvoicesController < ApplicationController
|
||||||
# etag { current_user&.id }
|
# etag { current_user&.id }
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# # Etag will differ even for the same invoice when it's viewed by a different current_user
|
# # Etag will differ even for the same invoice when it's viewed by a different current_user
|
||||||
# @invoice = Invoice.find(params[:id])
|
# @invoice = Invoice.find(params[:id])
|
||||||
# fresh_when etag: @invoice
|
# fresh_when etag: @invoice
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
def etag(&etagger)
|
def etag(&etagger)
|
||||||
self.etaggers += [etagger]
|
self.etaggers += [etagger]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the +etag+, +last_modified+, or both on the response, and renders a
|
# Sets the `etag`, `last_modified`, or both on the response, and renders a `304
|
||||||
# <tt>304 Not Modified</tt> response if the request is already fresh.
|
# Not Modified` response if the request is already fresh.
|
||||||
#
|
#
|
||||||
# ==== Options
|
# #### Options
|
||||||
#
|
#
|
||||||
# [+:etag+]
|
# `:etag`
|
||||||
# Sets a "weak" ETag validator on the response. See the +:weak_etag+ option.
|
# : Sets a "weak" ETag validator on the response. See the `:weak_etag` option.
|
||||||
# [+:weak_etag+]
|
# `:weak_etag`
|
||||||
# Sets a "weak" ETag validator on the response. Requests that specify an
|
# : Sets a "weak" ETag validator on the response. Requests that specify an
|
||||||
# +If-None-Match+ header may receive a <tt>304 Not Modified</tt> response
|
# `If-None-Match` header may receive a `304 Not Modified` response if the
|
||||||
# if the ETag matches exactly.
|
# ETag matches exactly.
|
||||||
#
|
#
|
||||||
# A weak ETag indicates semantic equivalence, not byte-for-byte equality,
|
# A weak ETag indicates semantic equivalence, not byte-for-byte equality, so
|
||||||
# so they're good for caching HTML pages in browser caches. They can't be
|
# they're good for caching HTML pages in browser caches. They can't be used
|
||||||
# used for responses that must be byte-identical, like serving +Range+
|
# for responses that must be byte-identical, like serving `Range` requests
|
||||||
# requests within a PDF file.
|
# within a PDF file.
|
||||||
# [+:strong_etag+]
|
# `:strong_etag`
|
||||||
# Sets a "strong" ETag validator on the response. Requests that specify an
|
# : Sets a "strong" ETag validator on the response. Requests that specify an
|
||||||
# +If-None-Match+ header may receive a <tt>304 Not Modified</tt> response
|
# `If-None-Match` header may receive a `304 Not Modified` response if the
|
||||||
# if the ETag matches exactly.
|
# ETag matches exactly.
|
||||||
#
|
#
|
||||||
# A strong ETag implies exact equality -- the response must match byte for
|
# A strong ETag implies exact equality -- the response must match byte for
|
||||||
# byte. This is necessary for serving +Range+ requests within a large
|
# byte. This is necessary for serving `Range` requests within a large video
|
||||||
# video or PDF file, for example, or for compatibility with some CDNs that
|
# or PDF file, for example, or for compatibility with some CDNs that don't
|
||||||
# don't support weak ETags.
|
# support weak ETags.
|
||||||
# [+:last_modified+]
|
# `:last_modified`
|
||||||
# Sets a "weak" last-update validator on the response. Subsequent requests
|
# : Sets a "weak" last-update validator on the response. Subsequent requests
|
||||||
# that specify an +If-Modified-Since+ header may receive a <tt>304 Not Modified</tt>
|
# that specify an `If-Modified-Since` header may receive a `304 Not
|
||||||
# response if +last_modified+ <= +If-Modified-Since+.
|
# Modified` response if `last_modified` <= `If-Modified-Since`.
|
||||||
# [+:public+]
|
# `:public`
|
||||||
# By default the +Cache-Control+ header is private. Set this option to
|
# : By default the `Cache-Control` header is private. Set this option to
|
||||||
# +true+ if you want your application to be cacheable by other devices,
|
# `true` if you want your application to be cacheable by other devices, such
|
||||||
# such as proxy caches.
|
# as proxy caches.
|
||||||
# [+:cache_control+]
|
# `:cache_control`
|
||||||
# When given, will overwrite an existing +Cache-Control+ header. For a
|
# : When given, will overwrite an existing `Cache-Control` header. For a list
|
||||||
# list of +Cache-Control+ directives, see the {article on
|
# of `Cache-Control` directives, see the [article on
|
||||||
# MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control].
|
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Contr
|
||||||
# [+:template+]
|
# ol).
|
||||||
# By default, the template digest for the current controller/action is
|
# `:template`
|
||||||
# included in ETags. If the action renders a different template, you can
|
# : By default, the template digest for the current controller/action is
|
||||||
# include its digest instead. If the action doesn't render a template at
|
# included in ETags. If the action renders a different template, you can
|
||||||
# all, you can pass <tt>template: false</tt> to skip any attempt to check
|
# include its digest instead. If the action doesn't render a template at
|
||||||
# for a template digest.
|
# all, you can pass `template: false` to skip any attempt to check for a
|
||||||
|
# template digest.
|
||||||
#
|
#
|
||||||
# ==== Examples
|
|
||||||
#
|
#
|
||||||
# def show
|
# #### Examples
|
||||||
# @article = Article.find(params[:id])
|
|
||||||
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
|
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# This will send a <tt>304 Not Modified</tt> response if the request
|
# def show
|
||||||
# specifies a matching ETag and +If-Modified-Since+ header. Otherwise, it
|
# @article = Article.find(params[:id])
|
||||||
# will render the +show+ template.
|
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# This will send a `304 Not Modified` response if the request specifies a
|
||||||
|
# matching ETag and `If-Modified-Since` header. Otherwise, it will render the
|
||||||
|
# `show` template.
|
||||||
#
|
#
|
||||||
# You can also just pass a record:
|
# You can also just pass a record:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
# fresh_when(@article)
|
# fresh_when(@article)
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# +etag+ will be set to the record, and +last_modified+ will be set to the
|
# `etag` will be set to the record, and `last_modified` will be set to the
|
||||||
# record's +updated_at+.
|
# record's `updated_at`.
|
||||||
#
|
#
|
||||||
# You can also pass an object that responds to +maximum+, such as a
|
# You can also pass an object that responds to `maximum`, such as a collection
|
||||||
# collection of records:
|
# of records:
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# @articles = Article.all
|
# @articles = Article.all
|
||||||
# fresh_when(@articles)
|
# fresh_when(@articles)
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# In this case, +etag+ will be set to the collection, and +last_modified+
|
# In this case, `etag` will be set to the collection, and `last_modified` will
|
||||||
# will be set to <tt>maximum(:updated_at)</tt> (the timestamp of the most
|
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
|
||||||
# recently updated record).
|
# record).
|
||||||
#
|
#
|
||||||
# When passing a record or a collection, you can still specify other
|
# When passing a record or a collection, you can still specify other options,
|
||||||
# options, such as +:public+ and +:cache_control+:
|
# such as `:public` and `:cache_control`:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
# fresh_when(@article, public: true, cache_control: { no_cache: true })
|
# fresh_when(@article, public: true, cache_control: { no_cache: true })
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
|
# The above will set `Cache-Control: public, no-cache` in the response.
|
||||||
#
|
#
|
||||||
# When rendering a different template than the controller/action's default
|
# When rendering a different template than the controller/action's default
|
||||||
# template, you can indicate which digest to include in the ETag:
|
# template, you can indicate which digest to include in the ETag:
|
||||||
#
|
#
|
||||||
# before_action { fresh_when @article, template: "widgets/show" }
|
# before_action { fresh_when @article, template: "widgets/show" }
|
||||||
#
|
#
|
||||||
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
|
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
|
||||||
response.cache_control.delete(:no_store)
|
response.cache_control.delete(:no_store)
|
||||||
@ -145,131 +149,132 @@ def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_m
|
|||||||
head :not_modified if request.fresh?(response)
|
head :not_modified if request.fresh?(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the +etag+ and/or +last_modified+ on the response and checks them
|
# Sets the `etag` and/or `last_modified` on the response and checks them against
|
||||||
# against the request. If the request doesn't match the provided options, it
|
# the request. If the request doesn't match the provided options, it is
|
||||||
# is considered stale, and the response should be rendered from scratch.
|
# considered stale, and the response should be rendered from scratch. Otherwise,
|
||||||
# Otherwise, it is fresh, and a <tt>304 Not Modified</tt> is sent.
|
# it is fresh, and a `304 Not Modified` is sent.
|
||||||
#
|
#
|
||||||
# ==== Options
|
# #### Options
|
||||||
#
|
#
|
||||||
# See #fresh_when for supported options.
|
# See #fresh_when for supported options.
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# #### Examples
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
#
|
#
|
||||||
# if stale?(etag: @article, last_modified: @article.updated_at)
|
# if stale?(etag: @article, last_modified: @article.updated_at)
|
||||||
# @statistics = @article.really_expensive_call
|
# @statistics = @article.really_expensive_call
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# # all the supported formats
|
# # all the supported formats
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# You can also just pass a record:
|
# You can also just pass a record:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
#
|
#
|
||||||
# if stale?(@article)
|
# if stale?(@article)
|
||||||
# @statistics = @article.really_expensive_call
|
# @statistics = @article.really_expensive_call
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# # all the supported formats
|
# # all the supported formats
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# +etag+ will be set to the record, and +last_modified+ will be set to the
|
# `etag` will be set to the record, and `last_modified` will be set to the
|
||||||
# record's +updated_at+.
|
# record's `updated_at`.
|
||||||
#
|
#
|
||||||
# You can also pass an object that responds to +maximum+, such as a
|
# You can also pass an object that responds to `maximum`, such as a collection
|
||||||
# collection of records:
|
# of records:
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# @articles = Article.all
|
# @articles = Article.all
|
||||||
#
|
#
|
||||||
# if stale?(@articles)
|
# if stale?(@articles)
|
||||||
# @statistics = @articles.really_expensive_call
|
# @statistics = @articles.really_expensive_call
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# # all the supported formats
|
# # all the supported formats
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# In this case, +etag+ will be set to the collection, and +last_modified+
|
# In this case, `etag` will be set to the collection, and `last_modified` will
|
||||||
# will be set to <tt>maximum(:updated_at)</tt> (the timestamp of the most
|
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
|
||||||
# recently updated record).
|
# record).
|
||||||
#
|
#
|
||||||
# When passing a record or a collection, you can still specify other
|
# When passing a record or a collection, you can still specify other options,
|
||||||
# options, such as +:public+ and +:cache_control+:
|
# such as `:public` and `:cache_control`:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @article = Article.find(params[:id])
|
# @article = Article.find(params[:id])
|
||||||
#
|
#
|
||||||
# if stale?(@article, public: true, cache_control: { no_cache: true })
|
# if stale?(@article, public: true, cache_control: { no_cache: true })
|
||||||
# @statistics = @articles.really_expensive_call
|
# @statistics = @articles.really_expensive_call
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# # all the supported formats
|
# # all the supported formats
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
|
# The above will set `Cache-Control: public, no-cache` in the response.
|
||||||
#
|
#
|
||||||
# When rendering a different template than the controller/action's default
|
# When rendering a different template than the controller/action's default
|
||||||
# template, you can indicate which digest to include in the ETag:
|
# template, you can indicate which digest to include in the ETag:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# super if stale?(@article, template: "widgets/show")
|
# super if stale?(@article, template: "widgets/show")
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
def stale?(object = nil, **freshness_kwargs)
|
def stale?(object = nil, **freshness_kwargs)
|
||||||
fresh_when(object, **freshness_kwargs)
|
fresh_when(object, **freshness_kwargs)
|
||||||
!request.fresh?(response)
|
!request.fresh?(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the +Cache-Control+ header, overwriting existing directives. This
|
# Sets the `Cache-Control` header, overwriting existing directives. This method
|
||||||
# method will also ensure an HTTP +Date+ header for client compatibility.
|
# will also ensure an HTTP `Date` header for client compatibility.
|
||||||
#
|
#
|
||||||
# Defaults to issuing the +private+ directive, so that intermediate caches
|
# Defaults to issuing the `private` directive, so that intermediate caches must
|
||||||
# must not cache the response.
|
# not cache the response.
|
||||||
#
|
#
|
||||||
# ==== Options
|
# #### Options
|
||||||
#
|
#
|
||||||
# [+:public+]
|
# `:public`
|
||||||
# If true, replaces the default +private+ directive with the +public+
|
# : If true, replaces the default `private` directive with the `public`
|
||||||
# directive.
|
# directive.
|
||||||
# [+:must_revalidate+]
|
# `:must_revalidate`
|
||||||
# If true, adds the +must-revalidate+ directive.
|
# : If true, adds the `must-revalidate` directive.
|
||||||
# [+:stale_while_revalidate+]
|
# `:stale_while_revalidate`
|
||||||
# Sets the value of the +stale-while-revalidate+ directive.
|
# : Sets the value of the `stale-while-revalidate` directive.
|
||||||
# [+:stale_if_error+]
|
# `:stale_if_error`
|
||||||
# Sets the value of the +stale-if-error+ directive.
|
# : Sets the value of the `stale-if-error` directive.
|
||||||
#
|
#
|
||||||
# Any additional key-value pairs are concatenated as directives. For a list
|
|
||||||
# of supported +Cache-Control+ directives, see the {article on
|
|
||||||
# MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control].
|
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# Any additional key-value pairs are concatenated as directives. For a list of
|
||||||
|
# supported `Cache-Control` directives, see the [article on
|
||||||
|
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
|
||||||
#
|
#
|
||||||
# expires_in 10.minutes
|
# #### Examples
|
||||||
# # => Cache-Control: max-age=600, private
|
|
||||||
#
|
#
|
||||||
# expires_in 10.minutes, public: true
|
# expires_in 10.minutes
|
||||||
# # => Cache-Control: max-age=600, public
|
# # => Cache-Control: max-age=600, private
|
||||||
#
|
#
|
||||||
# expires_in 10.minutes, public: true, must_revalidate: true
|
# expires_in 10.minutes, public: true
|
||||||
# # => Cache-Control: max-age=600, public, must-revalidate
|
# # => Cache-Control: max-age=600, public
|
||||||
#
|
#
|
||||||
# expires_in 1.hour, stale_while_revalidate: 60.seconds
|
# expires_in 10.minutes, public: true, must_revalidate: true
|
||||||
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
|
# # => Cache-Control: max-age=600, public, must-revalidate
|
||||||
#
|
#
|
||||||
# expires_in 1.hour, stale_if_error: 5.minutes
|
# expires_in 1.hour, stale_while_revalidate: 60.seconds
|
||||||
# # => Cache-Control: max-age=3600, private, stale-if-error=300
|
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
|
||||||
#
|
#
|
||||||
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
|
# expires_in 1.hour, stale_if_error: 5.minutes
|
||||||
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
|
# # => Cache-Control: max-age=3600, private, stale-if-error=300
|
||||||
|
#
|
||||||
|
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
|
||||||
|
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
|
||||||
#
|
#
|
||||||
def expires_in(seconds, options = {})
|
def expires_in(seconds, options = {})
|
||||||
response.cache_control.delete(:no_store)
|
response.cache_control.delete(:no_store)
|
||||||
@ -286,8 +291,8 @@ def expires_in(seconds, options = {})
|
|||||||
response.date = Time.now unless response.date?
|
response.date = Time.now unless response.date?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets an HTTP 1.1 +Cache-Control+ header of <tt>no-cache</tt>. This means the
|
# Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
|
||||||
# resource will be marked as stale, so clients must always revalidate.
|
# will be marked as stale, so clients must always revalidate.
|
||||||
# Intermediate/browser caches may still store the asset.
|
# Intermediate/browser caches may still store the asset.
|
||||||
def expires_now
|
def expires_now
|
||||||
response.cache_control.replace(no_cache: true)
|
response.cache_control.replace(no_cache: true)
|
||||||
@ -295,12 +300,13 @@ def expires_now
|
|||||||
|
|
||||||
# Cache or yield the block. The cache is supposed to never expire.
|
# Cache or yield the block. The cache is supposed to never expire.
|
||||||
#
|
#
|
||||||
# You can use this method when you have an HTTP response that never changes,
|
# You can use this method when you have an HTTP response that never changes, and
|
||||||
# and the browser and proxies should cache it indefinitely.
|
# the browser and proxies should cache it indefinitely.
|
||||||
|
#
|
||||||
|
# * `public`: By default, HTTP responses are private, cached only on the
|
||||||
|
# user's web browser. To allow proxies to cache the response, set `true` to
|
||||||
|
# indicate that they can serve the cached response to all users.
|
||||||
#
|
#
|
||||||
# * +public+: By default, HTTP responses are private, cached only on the
|
|
||||||
# user's web browser. To allow proxies to cache the response, set +true+ to
|
|
||||||
# indicate that they can serve the cached response to all users.
|
|
||||||
def http_cache_forever(public: false)
|
def http_cache_forever(public: false)
|
||||||
expires_in 100.years, public: public
|
expires_in 100.years, public: public
|
||||||
|
|
||||||
@ -309,8 +315,8 @@ def http_cache_forever(public: false)
|
|||||||
public: public)
|
public: public)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets an HTTP 1.1 +Cache-Control+ header of <tt>no-store</tt>. This means the
|
# Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
|
||||||
# resource may not be stored in any cache.
|
# may not be stored in any cache.
|
||||||
def no_store
|
def no_store
|
||||||
response.cache_control.replace(no_store: true)
|
response.cache_control.replace(no_store: true)
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module ContentSecurityPolicy
|
module ContentSecurityPolicy
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -13,29 +15,28 @@ module ContentSecurityPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Overrides parts of the globally configured +Content-Security-Policy+
|
# Overrides parts of the globally configured `Content-Security-Policy` header:
|
||||||
# header:
|
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# content_security_policy do |policy|
|
# content_security_policy do |policy|
|
||||||
# policy.base_uri "https://www.example.com"
|
# policy.base_uri "https://www.example.com"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Options can be passed similar to +before_action+. For example, pass
|
# Options can be passed similar to `before_action`. For example, pass `only:
|
||||||
# <tt>only: :index</tt> to override the header on the index action only:
|
# :index` to override the header on the index action only:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# content_security_policy(only: :index) do |policy|
|
# content_security_policy(only: :index) do |policy|
|
||||||
# policy.default_src :self, :https
|
# policy.default_src :self, :https
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Pass +false+ to remove the +Content-Security-Policy+ header:
|
# Pass `false` to remove the `Content-Security-Policy` header:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# content_security_policy false, only: :index
|
# content_security_policy false, only: :index
|
||||||
# end
|
# end
|
||||||
def content_security_policy(enabled = true, **options, &block)
|
def content_security_policy(enabled = true, **options, &block)
|
||||||
before_action(options) do
|
before_action(options) do
|
||||||
if block_given?
|
if block_given?
|
||||||
@ -50,18 +51,18 @@ def content_security_policy(enabled = true, **options, &block)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overrides the globally configured +Content-Security-Policy-Report-Only+
|
# Overrides the globally configured `Content-Security-Policy-Report-Only`
|
||||||
# header:
|
# header:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# content_security_policy_report_only only: :index
|
# content_security_policy_report_only only: :index
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Pass +false+ to remove the +Content-Security-Policy-Report-Only+ header:
|
# Pass `false` to remove the `Content-Security-Policy-Report-Only` header:
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# content_security_policy_report_only false, only: :index
|
# content_security_policy_report_only false, only: :index
|
||||||
# end
|
# end
|
||||||
def content_security_policy_report_only(report_only = true, **options)
|
def content_security_policy_report_only(report_only = true, **options)
|
||||||
before_action(options) do
|
before_action(options) do
|
||||||
request.content_security_policy_report_only = report_only
|
request.content_security_policy_report_only = report_only
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module Cookies
|
module Cookies
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -9,8 +11,8 @@ module Cookies
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# The cookies for the current request. See ActionDispatch::Cookies for
|
# The cookies for the current request. See ActionDispatch::Cookies for more
|
||||||
# more information.
|
# information.
|
||||||
def cookies # :doc:
|
def cookies # :doc:
|
||||||
request.cookie_jar
|
request.cookie_jar
|
||||||
end
|
end
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_controller/metal/exceptions"
|
require "action_controller/metal/exceptions"
|
||||||
require "action_dispatch/http/content_disposition"
|
require "action_dispatch/http/content_disposition"
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
# = Action Controller Data \Streaming
|
# # Action Controller Data Streaming
|
||||||
#
|
#
|
||||||
# Methods for sending arbitrary data and for streaming files to the browser,
|
# Methods for sending arbitrary data and for streaming files to the browser,
|
||||||
# instead of rendering.
|
# instead of rendering.
|
||||||
@ -17,57 +19,60 @@ module DataStreaming
|
|||||||
DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc:
|
DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc:
|
||||||
|
|
||||||
private
|
private
|
||||||
# Sends the file. This uses a server-appropriate method (such as +X-Sendfile+)
|
# Sends the file. This uses a server-appropriate method (such as `X-Sendfile`)
|
||||||
# via the +Rack::Sendfile+ middleware. The header to use is set via
|
# via the `Rack::Sendfile` middleware. The header to use is set via
|
||||||
# +config.action_dispatch.x_sendfile_header+.
|
# `config.action_dispatch.x_sendfile_header`. Your server can also configure
|
||||||
# Your server can also configure this for you by setting the +X-Sendfile-Type+ header.
|
# this for you by setting the `X-Sendfile-Type` header.
|
||||||
#
|
#
|
||||||
# Be careful to sanitize the path parameter if it is coming from a web
|
# Be careful to sanitize the path parameter if it is coming from a web page.
|
||||||
# page. <tt>send_file(params[:path])</tt> allows a malicious user to
|
# `send_file(params[:path])` allows a malicious user to download any file on
|
||||||
# download any file on your server.
|
# your server.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
# * `:filename` - suggests a filename for the browser to use. Defaults to
|
||||||
# Defaults to <tt>File.basename(path)</tt>.
|
# `File.basename(path)`.
|
||||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
# * `:type` - specifies an HTTP content type. You can specify either a string
|
||||||
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example +:json+.
|
# or a symbol for a registered type with `Mime::Type.register`, for example
|
||||||
# If omitted, the type will be inferred from the file extension specified in <tt>:filename</tt>.
|
# `:json`. If omitted, the type will be inferred from the file extension
|
||||||
# If no content type is registered for the extension, the default type +application/octet-stream+ will be used.
|
# specified in `:filename`. If no content type is registered for the
|
||||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
# extension, the default type `application/octet-stream` will be used.
|
||||||
# Valid values are <tt>"inline"</tt> and <tt>"attachment"</tt> (default).
|
# * `:disposition` - specifies whether the file will be shown inline or
|
||||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
|
# downloaded. Valid values are `"inline"` and `"attachment"` (default).
|
||||||
# * <tt>:url_based_filename</tt> - set to +true+ if you want the browser to guess the filename from
|
# * `:status` - specifies the status code to send with the response. Defaults
|
||||||
# the URL, which is necessary for i18n filenames on certain browsers
|
# to 200.
|
||||||
# (setting <tt>:filename</tt> overrides this option).
|
# * `:url_based_filename` - set to `true` if you want the browser to guess the
|
||||||
|
# filename from the URL, which is necessary for i18n filenames on certain
|
||||||
|
# browsers (setting `:filename` overrides this option).
|
||||||
#
|
#
|
||||||
# The default +Content-Type+ and +Content-Disposition+ headers are
|
#
|
||||||
# set to download arbitrary binary files in as many browsers as
|
# The default `Content-Type` and `Content-Disposition` headers are set to
|
||||||
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
|
# download arbitrary binary files in as many browsers as possible. IE versions
|
||||||
# a variety of quirks (especially when downloading over SSL).
|
# 4, 5, 5.5, and 6 are all known to have a variety of quirks (especially when
|
||||||
|
# downloading over SSL).
|
||||||
#
|
#
|
||||||
# Simple download:
|
# Simple download:
|
||||||
#
|
#
|
||||||
# send_file '/path/to.zip'
|
# send_file '/path/to.zip'
|
||||||
#
|
#
|
||||||
# Show a JPEG in the browser:
|
# Show a JPEG in the browser:
|
||||||
#
|
#
|
||||||
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
|
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
|
||||||
#
|
#
|
||||||
# Show a 404 page in the browser:
|
# Show a 404 page in the browser:
|
||||||
#
|
#
|
||||||
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
|
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
|
||||||
#
|
#
|
||||||
# You can use other <tt>Content-*</tt> HTTP headers to provide additional
|
# You can use other `Content-*` HTTP headers to provide additional information
|
||||||
# information to the client. See MDN for a
|
# to the client. See MDN for a [list of HTTP
|
||||||
# {list of HTTP headers}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers].
|
# headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
|
||||||
#
|
#
|
||||||
# Also be aware that the document may be cached by proxies and browsers.
|
# Also be aware that the document may be cached by proxies and browsers. The
|
||||||
# The +Pragma+ and +Cache-Control+ headers declare how the file may be cached
|
# `Pragma` and `Cache-Control` headers declare how the file may be cached by
|
||||||
# by intermediaries. They default to require clients to validate with
|
# intermediaries. They default to require clients to validate with the server
|
||||||
# the server before releasing cached responses. See
|
# before releasing cached responses. See https://www.mnot.net/cache_docs/ for an
|
||||||
# https://www.mnot.net/cache_docs/ for an overview of web caching and
|
# overview of web caching and [RFC
|
||||||
# {RFC 9111}[https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control]
|
# 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control) for the
|
||||||
# for the +Cache-Control+ header spec.
|
# `Cache-Control` header spec.
|
||||||
def send_file(path, options = {}) # :doc:
|
def send_file(path, options = {}) # :doc:
|
||||||
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
|
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
|
||||||
|
|
||||||
@ -79,35 +84,39 @@ def send_file(path, options = {}) # :doc:
|
|||||||
response.send_file path
|
response.send_file path
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sends the given binary data to the browser. This method is similar to
|
# Sends the given binary data to the browser. This method is similar to `render
|
||||||
# <tt>render plain: data</tt>, but also allows you to specify whether
|
# plain: data`, but also allows you to specify whether the browser should
|
||||||
# the browser should display the response as a file attachment (i.e. in a
|
# display the response as a file attachment (i.e. in a download dialog) or as
|
||||||
# download dialog) or as inline data. You may also set the content type,
|
# inline data. You may also set the content type, the file name, and other
|
||||||
# the file name, and other things.
|
# things.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
# * `:filename` - suggests a filename for the browser to use.
|
||||||
# * <tt>:type</tt> - specifies an HTTP content type. Defaults to +application/octet-stream+.
|
# * `:type` - specifies an HTTP content type. Defaults to
|
||||||
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example +:json+.
|
# `application/octet-stream`. You can specify either a string or a symbol
|
||||||
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
# for a registered type with `Mime::Type.register`, for example `:json`. If
|
||||||
# If no content type is registered for the extension, the default type +application/octet-stream+ will be used.
|
# omitted, type will be inferred from the file extension specified in
|
||||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
# `:filename`. If no content type is registered for the extension, the
|
||||||
# Valid values are <tt>"inline"</tt> and <tt>"attachment"</tt> (default).
|
# default type `application/octet-stream` will be used.
|
||||||
# * <tt>:status</tt> - specifies the status code to send with the response. Defaults to 200.
|
# * `:disposition` - specifies whether the file will be shown inline or
|
||||||
|
# downloaded. Valid values are `"inline"` and `"attachment"` (default).
|
||||||
|
# * `:status` - specifies the status code to send with the response. Defaults
|
||||||
|
# to 200.
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# Generic data download:
|
# Generic data download:
|
||||||
#
|
#
|
||||||
# send_data buffer
|
# send_data buffer
|
||||||
#
|
#
|
||||||
# Download a dynamically-generated tarball:
|
# Download a dynamically-generated tarball:
|
||||||
#
|
#
|
||||||
# send_data generate_tgz('dir'), filename: 'dir.tgz'
|
# send_data generate_tgz('dir'), filename: 'dir.tgz'
|
||||||
#
|
#
|
||||||
# Display an image Active Record in the browser:
|
# Display an image Active Record in the browser:
|
||||||
#
|
#
|
||||||
# send_data image.data, type: image.content_type, disposition: 'inline'
|
# send_data image.data, type: image.content_type, disposition: 'inline'
|
||||||
#
|
#
|
||||||
# See +send_file+ for more information on HTTP <tt>Content-*</tt> headers and caching.
|
# See `send_file` for more information on HTTP `Content-*` headers and caching.
|
||||||
def send_data(data, options = {}) # :doc:
|
def send_data(data, options = {}) # :doc:
|
||||||
send_file_headers! options
|
send_file_headers! options
|
||||||
render options.slice(:status, :content_type).merge(body: data)
|
render options.slice(:status, :content_type).merge(body: data)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller Default Headers
|
# # Action Controller Default Headers
|
||||||
#
|
#
|
||||||
# Allows configuring default headers that will be automatically merged into
|
# Allows configuring default headers that will be automatically merged into each
|
||||||
# each response.
|
# response.
|
||||||
module DefaultHeaders
|
module DefaultHeaders
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller Etag With \Flash
|
# # Action Controller Etag With Flash
|
||||||
#
|
#
|
||||||
# When you're using the flash, it's generally used as a conditional on the view.
|
# When you're using the flash, it's generally used as a conditional on the view.
|
||||||
# This means the content of the view depends on the flash. Which in turn means
|
# This means the content of the view depends on the flash. Which in turn means
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller Etag With Template \Digest
|
# # Action Controller Etag With Template Digest
|
||||||
#
|
#
|
||||||
# When our views change, they should bubble up into HTTP cache freshness
|
# When our views change, they should bubble up into HTTP cache freshness and
|
||||||
# and bust browser caches. So the template digest for the current action
|
# bust browser caches. So the template digest for the current action is
|
||||||
# is automatically included in the ETag.
|
# automatically included in the ETag.
|
||||||
#
|
#
|
||||||
# Enabled by default for apps that use Action View. Disable by setting
|
# Enabled by default for apps that use Action View. Disable by setting
|
||||||
#
|
#
|
||||||
# config.action_controller.etag_with_template_digest = false
|
# config.action_controller.etag_with_template_digest = false
|
||||||
#
|
#
|
||||||
# Override the template to digest by passing +:template+ to +fresh_when+
|
# Override the template to digest by passing `:template` to `fresh_when` and
|
||||||
# and +stale?+ calls. For example:
|
# `stale?` calls. For example:
|
||||||
#
|
#
|
||||||
# # We're going to render widgets/show, not posts/show
|
# # We're going to render widgets/show, not posts/show
|
||||||
# fresh_when @post, template: 'widgets/show'
|
# fresh_when @post, template: 'widgets/show'
|
||||||
#
|
#
|
||||||
# # We're not going to render a template, so omit it from the ETag.
|
# # We're not going to render a template, so omit it from the ETag.
|
||||||
# fresh_when @post, template: false
|
# fresh_when @post, template: false
|
||||||
#
|
#
|
||||||
module EtagWithTemplateDigest
|
module EtagWithTemplateDigest
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -40,10 +42,10 @@ def determine_template_etag(options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Pick the template digest to include in the ETag. If the +:template+ option
|
# Pick the template digest to include in the ETag. If the `:template` option is
|
||||||
# is present, use the named template. If +:template+ is +nil+ or absent, use
|
# present, use the named template. If `:template` is `nil` or absent, use the
|
||||||
# the default controller/action template. If +:template+ is false, omit the
|
# default controller/action template. If `:template` is false, omit the template
|
||||||
# template digest from the ETag.
|
# digest from the ETag.
|
||||||
def pick_template_for_etag(options)
|
def pick_template_for_etag(options)
|
||||||
unless options[:template] == false
|
unless options[:template] == false
|
||||||
options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path
|
options[:template] || lookup_context.find_all(action_name, _prefixes).first&.virtual_path
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
class ActionControllerError < StandardError # :nodoc:
|
class ActionControllerError < StandardError # :nodoc:
|
||||||
end
|
end
|
||||||
@ -73,16 +75,16 @@ class UnknownHttpMethod < ActionControllerError # :nodoc:
|
|||||||
class UnknownFormat < ActionControllerError # :nodoc:
|
class UnknownFormat < ActionControllerError # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
# Raised when a nested respond_to is triggered and the content types of each
|
# Raised when a nested respond_to is triggered and the content types of each are
|
||||||
# are incompatible. For example:
|
# incompatible. For example:
|
||||||
#
|
#
|
||||||
# respond_to do |outer_type|
|
# respond_to do |outer_type|
|
||||||
# outer_type.js do
|
# outer_type.js do
|
||||||
# respond_to do |inner_type|
|
# respond_to do |inner_type|
|
||||||
# inner_type.html { render body: "HTML" }
|
# inner_type.html { render body: "HTML" }
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
class RespondToMismatchError < ActionControllerError
|
class RespondToMismatchError < ActionControllerError
|
||||||
DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."
|
DEFAULT_MESSAGE = "respond_to was called multiple times and matched with conflicting formats in this action. Please note that you may only call respond_to and match on a single format per action."
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module Flash
|
module Flash
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -13,19 +15,19 @@ module Flash
|
|||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Creates new flash types. You can pass as many types as you want to create
|
# Creates new flash types. You can pass as many types as you want to create
|
||||||
# flash types other than the default <tt>alert</tt> and <tt>notice</tt> in
|
# flash types other than the default `alert` and `notice` in your controllers
|
||||||
# your controllers and views. For instance:
|
# and views. For instance:
|
||||||
#
|
#
|
||||||
# # in application_controller.rb
|
# # in application_controller.rb
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# add_flash_types :warning
|
# add_flash_types :warning
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# # in your controller
|
# # in your controller
|
||||||
# redirect_to user_path(@user), warning: "Incomplete profile"
|
# redirect_to user_path(@user), warning: "Incomplete profile"
|
||||||
#
|
#
|
||||||
# # in your view
|
# # in your view
|
||||||
# <%= warning %>
|
# <%= warning %>
|
||||||
#
|
#
|
||||||
# This method will automatically define a new method for each of the given
|
# This method will automatically define a new method for each of the given
|
||||||
# names, and it will be available in your views.
|
# names, and it will be available in your views.
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Head
|
module Head
|
||||||
# Returns a response that has no content (merely headers). The options
|
# Returns a response that has no content (merely headers). The options argument
|
||||||
# argument is interpreted to be a hash of header names and values.
|
# is interpreted to be a hash of header names and values. This allows you to
|
||||||
# This allows you to easily return a response that consists only of
|
# easily return a response that consists only of significant headers:
|
||||||
# significant headers:
|
|
||||||
#
|
#
|
||||||
# head :created, location: person_path(@person)
|
# head :created, location: person_path(@person)
|
||||||
#
|
#
|
||||||
# head :created, location: @person
|
# head :created, location: @person
|
||||||
#
|
#
|
||||||
# It can also be used to return exceptional conditions:
|
# It can also be used to return exceptional conditions:
|
||||||
#
|
#
|
||||||
# return head(:method_not_allowed) unless request.post?
|
# return head(:method_not_allowed) unless request.post?
|
||||||
# return head(:bad_request) unless valid_request?
|
# return head(:bad_request) unless valid_request?
|
||||||
# render
|
# render
|
||||||
#
|
#
|
||||||
# See +Rack::Utils::SYMBOL_TO_STATUS_CODE+ for a full list of valid +status+ symbols.
|
# See `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list of valid `status`
|
||||||
|
# symbols.
|
||||||
def head(status, options = nil)
|
def head(status, options = nil)
|
||||||
if status.is_a?(Hash)
|
if status.is_a?(Hash)
|
||||||
raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
|
raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
|
||||||
|
@ -1,59 +1,64 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \Helpers
|
# # Action Controller Helpers
|
||||||
#
|
#
|
||||||
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
|
# The Rails framework provides a large number of helpers for working with
|
||||||
# numbers and model objects, to name a few. These helpers are available to all templates
|
# assets, dates, forms, numbers and model objects, to name a few. These helpers
|
||||||
# by default.
|
# are available to all templates by default.
|
||||||
#
|
#
|
||||||
# In addition to using the standard template helpers provided, creating custom helpers to
|
# In addition to using the standard template helpers provided, creating custom
|
||||||
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
|
# helpers to extract complicated logic or reusable functionality is strongly
|
||||||
# will include all helpers. These helpers are only accessible on the controller through <tt>#helpers</tt>
|
# encouraged. By default, each controller will include all helpers. These
|
||||||
|
# helpers are only accessible on the controller through `#helpers`
|
||||||
#
|
#
|
||||||
# In previous versions of \Rails the controller will include a helper which
|
# In previous versions of Rails the controller will include a helper which
|
||||||
# matches the name of the controller, e.g., <tt>MyController</tt> will automatically
|
# matches the name of the controller, e.g., `MyController` will automatically
|
||||||
# include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
|
# include `MyHelper`. You can revert to the old behavior with the following:
|
||||||
#
|
#
|
||||||
# # config/application.rb
|
# # config/application.rb
|
||||||
# class Application < Rails::Application
|
# class Application < Rails::Application
|
||||||
# config.action_controller.include_all_helpers = false
|
# config.action_controller.include_all_helpers = false
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
|
|
||||||
# controller which inherits from it.
|
|
||||||
#
|
|
||||||
# The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if
|
|
||||||
# a \Time object is blank:
|
|
||||||
#
|
|
||||||
# module FormattedTimeHelper
|
|
||||||
# def format_time(time, format=:long, blank_message=" ")
|
|
||||||
# time.blank? ? blank_message : time.to_fs(format)
|
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
|
# Additional helpers can be specified using the `helper` class method in
|
||||||
|
# ActionController::Base or any controller which inherits from it.
|
||||||
#
|
#
|
||||||
# class EventsController < ActionController::Base
|
# The `to_s` method from the Time class can be wrapped in a helper method to
|
||||||
# helper FormattedTimeHelper
|
# display a custom message if a Time object is blank:
|
||||||
# def index
|
#
|
||||||
# @events = Event.all
|
# module FormattedTimeHelper
|
||||||
|
# def format_time(time, format=:long, blank_message=" ")
|
||||||
|
# time.blank? ? blank_message : time.to_fs(format)
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Then, in any view rendered by <tt>EventsController</tt>, the <tt>format_time</tt> method can be called:
|
# FormattedTimeHelper can now be included in a controller, using the `helper`
|
||||||
|
# class method:
|
||||||
#
|
#
|
||||||
# <% @events.each do |event| -%>
|
# class EventsController < ActionController::Base
|
||||||
# <p>
|
# helper FormattedTimeHelper
|
||||||
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
|
# def index
|
||||||
# </p>
|
# @events = Event.all
|
||||||
# <% end -%>
|
# end
|
||||||
|
# end
|
||||||
#
|
#
|
||||||
# Finally, assuming we have two event instances, one which has a time and one which does not,
|
# Then, in any view rendered by `EventsController`, the `format_time` method can
|
||||||
# the output might look like this:
|
# be called:
|
||||||
#
|
#
|
||||||
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
|
# <% @events.each do |event| -%>
|
||||||
# N/A | Carolina Railhawks Training Workshop
|
# <p>
|
||||||
|
# <%= format_time(event.time, :short, "N/A") %> | <%= event.name %>
|
||||||
|
# </p>
|
||||||
|
# <% end -%>
|
||||||
|
#
|
||||||
|
# Finally, assuming we have two event instances, one which has a time and one
|
||||||
|
# which does not, the output might look like this:
|
||||||
|
#
|
||||||
|
# 23 Aug 11:30 | Carolina Railhawks Soccer Match
|
||||||
|
# N/A | Carolina Railhawks Training Workshop
|
||||||
#
|
#
|
||||||
module Helpers
|
module Helpers
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -68,23 +73,24 @@ class << self; attr_accessor :helpers_path; end
|
|||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Declares helper accessors for controller attributes. For example, the
|
# Declares helper accessors for controller attributes. For example, the
|
||||||
# following adds new +name+ and <tt>name=</tt> instance methods to a
|
# following adds new `name` and `name=` instance methods to a controller and
|
||||||
# controller and makes them available to the view:
|
# makes them available to the view:
|
||||||
# attr_accessor :name
|
# attr_accessor :name
|
||||||
# helper_attr :name
|
# helper_attr :name
|
||||||
|
#
|
||||||
|
# #### Parameters
|
||||||
|
# * `attrs` - Names of attributes to be converted into helpers.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
|
||||||
# * <tt>attrs</tt> - Names of attributes to be converted into helpers.
|
|
||||||
def helper_attr(*attrs)
|
def helper_attr(*attrs)
|
||||||
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Provides a proxy to access helper methods from outside the view.
|
# Provides a proxy to access helper methods from outside the view.
|
||||||
#
|
#
|
||||||
# Note that the proxy is rendered under a different view context.
|
# Note that the proxy is rendered under a different view context. This may cause
|
||||||
# This may cause incorrect behavior with capture methods. Consider
|
# incorrect behavior with capture methods. Consider using
|
||||||
# using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
|
# [helper](rdoc-ref:AbstractController::Helpers::ClassMethods#helper) instead
|
||||||
# instead when using +capture+.
|
# when using `capture`.
|
||||||
def helpers
|
def helpers
|
||||||
@helper_proxy ||= begin
|
@helper_proxy ||= begin
|
||||||
proxy = ActionView::Base.empty
|
proxy = ActionView::Base.empty
|
||||||
@ -93,21 +99,23 @@ def helpers
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Override modules_for_helpers to accept +:all+ as argument, which loads
|
# Override modules_for_helpers to accept `:all` as argument, which loads all
|
||||||
# all helpers in helpers_path.
|
# helpers in helpers_path.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
# * <tt>args</tt> - A list of helpers
|
# * `args` - A list of helpers
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# #### Returns
|
||||||
|
# * `array` - A normalized list of modules for the list of helpers provided.
|
||||||
#
|
#
|
||||||
# ==== Returns
|
|
||||||
# * <tt>array</tt> - A normalized list of modules for the list of helpers provided.
|
|
||||||
def modules_for_helpers(args)
|
def modules_for_helpers(args)
|
||||||
args += all_application_helpers if args.delete(:all)
|
args += all_application_helpers if args.delete(:all)
|
||||||
super(args)
|
super(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt>
|
# Extract helper names from files in `app/helpers/***/**_helper.rb`
|
||||||
def all_application_helpers
|
def all_application_helpers
|
||||||
all_helpers_from_path(helpers_path)
|
all_helpers_from_path(helpers_path)
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "base64"
|
require "base64"
|
||||||
require "active_support/security_utils"
|
require "active_support/security_utils"
|
||||||
require "active_support/core_ext/array/access"
|
require "active_support/core_ext/array/access"
|
||||||
@ -7,62 +9,63 @@
|
|||||||
module ActionController
|
module ActionController
|
||||||
# HTTP Basic, Digest, and Token authentication.
|
# HTTP Basic, Digest, and Token authentication.
|
||||||
module HttpAuthentication
|
module HttpAuthentication
|
||||||
# = HTTP \Basic authentication
|
# # HTTP Basic authentication
|
||||||
#
|
#
|
||||||
# === Simple \Basic example
|
# ### Simple Basic example
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
|
# http_basic_authenticate_with name: "dhh", password: "secret", except: :index
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# render plain: "Everyone can see me!"
|
# render plain: "Everyone can see me!"
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def edit
|
|
||||||
# render plain: "I'm only accessible if you know the password"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# === Advanced \Basic example
|
|
||||||
#
|
|
||||||
# Here is a more advanced \Basic example where only Atom feeds and the XML API are protected by HTTP authentication.
|
|
||||||
# The regular HTML interface is protected by a session approach:
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# before_action :set_account, :authenticate
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
# def set_account
|
|
||||||
# @account = Account.find_by(url_name: request.subdomains.first)
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def authenticate
|
# def edit
|
||||||
# case request.format
|
# render plain: "I'm only accessible if you know the password"
|
||||||
# when Mime[:xml], Mime[:atom]
|
# end
|
||||||
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
|
# end
|
||||||
# @current_user = user
|
#
|
||||||
|
# ### Advanced Basic example
|
||||||
|
#
|
||||||
|
# Here is a more advanced Basic example where only Atom feeds and the XML API
|
||||||
|
# are protected by HTTP authentication. The regular HTML interface is protected
|
||||||
|
# by a session approach:
|
||||||
|
#
|
||||||
|
# class ApplicationController < ActionController::Base
|
||||||
|
# before_action :set_account, :authenticate
|
||||||
|
#
|
||||||
|
# private
|
||||||
|
# def set_account
|
||||||
|
# @account = Account.find_by(url_name: request.subdomains.first)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def authenticate
|
||||||
|
# case request.format
|
||||||
|
# when Mime[:xml], Mime[:atom]
|
||||||
|
# if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
|
||||||
|
# @current_user = user
|
||||||
|
# else
|
||||||
|
# request_http_basic_authentication
|
||||||
|
# end
|
||||||
# else
|
# else
|
||||||
# request_http_basic_authentication
|
# if session_authenticated?
|
||||||
# end
|
# @current_user = @account.users.find(session[:authenticated][:user_id])
|
||||||
# else
|
# else
|
||||||
# if session_authenticated?
|
# redirect_to(login_url) and return false
|
||||||
# @current_user = @account.users.find(session[:authenticated][:user_id])
|
# end
|
||||||
# else
|
|
||||||
# redirect_to(login_url) and return false
|
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# In your integration tests, you can do something like this:
|
# In your integration tests, you can do something like this:
|
||||||
#
|
#
|
||||||
# def test_access_granted_from_xml
|
# def test_access_granted_from_xml
|
||||||
# authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
|
# authorization = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
|
||||||
#
|
#
|
||||||
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
|
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
|
||||||
#
|
#
|
||||||
# assert_equal 200, status
|
# assert_equal 200, status
|
||||||
# end
|
# end
|
||||||
module Basic
|
module Basic
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
@ -70,7 +73,7 @@ module ControllerMethods
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Enables HTTP \Basic authentication.
|
# Enables HTTP Basic authentication.
|
||||||
#
|
#
|
||||||
# See ActionController::HttpAuthentication::Basic for example usage.
|
# See ActionController::HttpAuthentication::Basic for example usage.
|
||||||
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
|
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
|
||||||
@ -82,8 +85,8 @@ def http_basic_authenticate_with(name:, password:, realm: nil, **options)
|
|||||||
|
|
||||||
def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
|
def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
|
||||||
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
|
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
|
||||||
# This comparison uses & so that it doesn't short circuit and
|
# This comparison uses & so that it doesn't short circuit and uses
|
||||||
# uses `secure_compare` so that length information isn't leaked.
|
# `secure_compare` so that length information isn't leaked.
|
||||||
ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
|
ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
|
||||||
ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
|
ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
|
||||||
end
|
end
|
||||||
@ -140,67 +143,68 @@ def authentication_request(controller, realm, message)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = HTTP \Digest authentication
|
# # HTTP Digest authentication
|
||||||
#
|
#
|
||||||
# === Simple \Digest example
|
# ### Simple Digest example
|
||||||
#
|
#
|
||||||
# require "openssl"
|
# require "openssl"
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# REALM = "SuperSecret"
|
# REALM = "SuperSecret"
|
||||||
# USERS = {"dhh" => "secret", #plain text password
|
# USERS = {"dhh" => "secret", #plain text password
|
||||||
# "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
# "dap" => OpenSSL::Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
|
||||||
#
|
#
|
||||||
# before_action :authenticate, except: [:index]
|
# before_action :authenticate, except: [:index]
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# render plain: "Everyone can see me!"
|
# render plain: "Everyone can see me!"
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def edit
|
|
||||||
# render plain: "I'm only accessible if you know the password"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
# def authenticate
|
|
||||||
# authenticate_or_request_with_http_digest(REALM) do |username|
|
|
||||||
# USERS[username]
|
|
||||||
# end
|
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# === Notes
|
# def edit
|
||||||
|
# render plain: "I'm only accessible if you know the password"
|
||||||
|
# end
|
||||||
#
|
#
|
||||||
# The +authenticate_or_request_with_http_digest+ block must return the user's password
|
# private
|
||||||
# or the ha1 digest hash so the framework can appropriately hash to check the user's
|
# def authenticate
|
||||||
# credentials. Returning +nil+ will cause authentication to fail.
|
# authenticate_or_request_with_http_digest(REALM) do |username|
|
||||||
|
# USERS[username]
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
#
|
#
|
||||||
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
|
# ### Notes
|
||||||
# the password file or database is compromised, the attacker would be able to use the ha1 hash to
|
|
||||||
# authenticate as the user at this +realm+, but would not have the user's password to try using at
|
|
||||||
# other sites.
|
|
||||||
#
|
#
|
||||||
# In rare instances, web servers or front proxies strip authorization headers before
|
# The `authenticate_or_request_with_http_digest` block must return the user's
|
||||||
# they reach your application. You can debug this situation by logging all environment
|
# password or the ha1 digest hash so the framework can appropriately hash to
|
||||||
# variables, and check for HTTP_AUTHORIZATION, amongst others.
|
# check the user's credentials. Returning `nil` will cause authentication to
|
||||||
|
# fail.
|
||||||
|
#
|
||||||
|
# Storing the ha1 hash: MD5(username:realm:password), is better than storing a
|
||||||
|
# plain password. If the password file or database is compromised, the attacker
|
||||||
|
# would be able to use the ha1 hash to authenticate as the user at this `realm`,
|
||||||
|
# but would not have the user's password to try using at other sites.
|
||||||
|
#
|
||||||
|
# In rare instances, web servers or front proxies strip authorization headers
|
||||||
|
# before they reach your application. You can debug this situation by logging
|
||||||
|
# all environment variables, and check for HTTP_AUTHORIZATION, amongst others.
|
||||||
module Digest
|
module Digest
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
module ControllerMethods
|
module ControllerMethods
|
||||||
# Authenticate using an HTTP \Digest, or otherwise render an HTTP header
|
# Authenticate using an HTTP Digest, or otherwise render an HTTP header
|
||||||
# requesting the client to send a \Digest.
|
# requesting the client to send a Digest.
|
||||||
#
|
#
|
||||||
# See ActionController::HttpAuthentication::Digest for example usage.
|
# See ActionController::HttpAuthentication::Digest for example usage.
|
||||||
def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
|
def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure)
|
||||||
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
|
authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Authenticate using an HTTP \Digest. Returns true if authentication is
|
# Authenticate using an HTTP Digest. Returns true if authentication is
|
||||||
# successful, false otherwise.
|
# successful, false otherwise.
|
||||||
def authenticate_with_http_digest(realm = "Application", &password_procedure)
|
def authenticate_with_http_digest(realm = "Application", &password_procedure)
|
||||||
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
|
HttpAuthentication::Digest.authenticate(request, realm, &password_procedure)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Render an HTTP header requesting the client to send a \Digest for
|
# Render an HTTP header requesting the client to send a Digest for
|
||||||
# authentication.
|
# authentication.
|
||||||
def request_http_digest_authentication(realm = "Application", message = nil)
|
def request_http_digest_authentication(realm = "Application", message = nil)
|
||||||
HttpAuthentication::Digest.authentication_request(self, realm, message)
|
HttpAuthentication::Digest.authentication_request(self, realm, message)
|
||||||
@ -212,9 +216,9 @@ def authenticate(request, realm, &password_procedure)
|
|||||||
request.authorization && validate_digest_response(request, realm, &password_procedure)
|
request.authorization && validate_digest_response(request, realm, &password_procedure)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns false unless the request credentials response value matches the expected value.
|
# Returns false unless the request credentials response value matches the
|
||||||
# First try the password as a ha1 digest password. If this fails, then try it as a plain
|
# expected value. First try the password as a ha1 digest password. If this
|
||||||
# text password.
|
# fails, then try it as a plain text password.
|
||||||
def validate_digest_response(request, realm, &password_procedure)
|
def validate_digest_response(request, realm, &password_procedure)
|
||||||
secret_key = secret_token(request)
|
secret_key = secret_token(request)
|
||||||
credentials = decode_credentials_header(request)
|
credentials = decode_credentials_header(request)
|
||||||
@ -237,9 +241,10 @@ def validate_digest_response(request, realm, &password_procedure)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
|
# Returns the expected response for a request of `http_method` to `uri` with the
|
||||||
# Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead
|
# decoded `credentials` and the expected `password` Optional parameter
|
||||||
# of a plain-text password.
|
# `password_is_ha1` is set to `true` by default, since best practice is to store
|
||||||
|
# ha1 digest instead of a plain-text password.
|
||||||
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
|
def expected_response(http_method, uri, credentials, password, password_is_ha1 = true)
|
||||||
ha1 = password_is_ha1 ? password : ha1(credentials, password)
|
ha1 = password_is_ha1 ? password : ha1(credentials, password)
|
||||||
ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
ha2 = OpenSSL::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":"))
|
||||||
@ -288,36 +293,40 @@ def secret_token(request)
|
|||||||
|
|
||||||
# Uses an MD5 digest based on time to generate a value to be used only once.
|
# Uses an MD5 digest based on time to generate a value to be used only once.
|
||||||
#
|
#
|
||||||
# A server-specified data string which should be uniquely generated each time a 401 response is made.
|
# A server-specified data string which should be uniquely generated each time a
|
||||||
# It is recommended that this string be base64 or hexadecimal data.
|
# 401 response is made. It is recommended that this string be base64 or
|
||||||
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
|
# hexadecimal data. Specifically, since the string is passed in the header lines
|
||||||
|
# as a quoted string, the double-quote character is not allowed.
|
||||||
#
|
#
|
||||||
# The contents of the nonce are implementation dependent.
|
# The contents of the nonce are implementation dependent. The quality of the
|
||||||
# The quality of the implementation depends on a good choice.
|
# implementation depends on a good choice. A nonce might, for example, be
|
||||||
# A nonce might, for example, be constructed as the base 64 encoding of
|
# constructed as the base 64 encoding of
|
||||||
#
|
#
|
||||||
# time-stamp H(time-stamp ":" ETag ":" private-key)
|
# time-stamp H(time-stamp ":" ETag ":" private-key)
|
||||||
#
|
#
|
||||||
# where time-stamp is a server-generated time or other non-repeating value,
|
# where time-stamp is a server-generated time or other non-repeating value, ETag
|
||||||
# ETag is the value of the HTTP ETag header associated with the requested entity,
|
# is the value of the HTTP ETag header associated with the requested entity, and
|
||||||
# and private-key is data known only to the server.
|
# private-key is data known only to the server. With a nonce of this form a
|
||||||
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
|
# server would recalculate the hash portion after receiving the client
|
||||||
# reject the request if it did not match the nonce from that header or
|
# authentication header and reject the request if it did not match the nonce
|
||||||
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
|
# from that header or if the time-stamp value is not recent enough. In this way
|
||||||
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
|
# the server can limit the time of the nonce's validity. The inclusion of the
|
||||||
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
|
# ETag prevents a replay request for an updated version of the resource. (Note:
|
||||||
# to limit the reuse of the nonce to the same client that originally got it.
|
# including the IP address of the client in the nonce would appear to offer the
|
||||||
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
|
# server the ability to limit the reuse of the nonce to the same client that
|
||||||
# Also, IP address spoofing is not that hard.)
|
# originally got it. However, that would break proxy farms, where requests from
|
||||||
|
# a single user often go through different proxies in the farm. Also, IP address
|
||||||
|
# spoofing is not that hard.)
|
||||||
#
|
#
|
||||||
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
|
# An implementation might choose not to accept a previously used nonce or a
|
||||||
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
|
# previously used digest, in order to protect against a replay attack. Or, an
|
||||||
# POST, PUT, or PATCH requests, and a time-stamp for GET requests. For more details on the issues involved see Section 4
|
# implementation might choose to use one-time nonces or digests for POST, PUT,
|
||||||
# of this document.
|
# or PATCH requests, and a time-stamp for GET requests. For more details on the
|
||||||
|
# issues involved see Section 4 of this document.
|
||||||
#
|
#
|
||||||
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
|
# The nonce is opaque to the client. Composed of Time, and hash of Time with
|
||||||
# key from the \Rails session secret generated upon creation of project. Ensures
|
# secret key from the Rails session secret generated upon creation of project.
|
||||||
# the time cannot be modified by client.
|
# Ensures the time cannot be modified by client.
|
||||||
def nonce(secret_key, time = Time.now)
|
def nonce(secret_key, time = Time.now)
|
||||||
t = time.to_i
|
t = time.to_i
|
||||||
hashed = [t, secret_key]
|
hashed = [t, secret_key]
|
||||||
@ -325,11 +334,10 @@ def nonce(secret_key, time = Time.now)
|
|||||||
::Base64.strict_encode64("#{t}:#{digest}")
|
::Base64.strict_encode64("#{t}:#{digest}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Might want a shorter timeout depending on whether the request
|
# Might want a shorter timeout depending on whether the request is a PATCH, PUT,
|
||||||
# is a PATCH, PUT, or POST, and if the client is a browser or web service.
|
# or POST, and if the client is a browser or web service. Can be much shorter if
|
||||||
# Can be much shorter if the Stale directive is implemented. This would
|
# the Stale directive is implemented. This would allow a user to use new nonce
|
||||||
# allow a user to use new nonce without prompting the user again for their
|
# without prompting the user again for their username and password.
|
||||||
# username and password.
|
|
||||||
def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
|
def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60)
|
||||||
return false if value.nil?
|
return false if value.nil?
|
||||||
t = ::Base64.decode64(value).split(":").first.to_i
|
t = ::Base64.decode64(value).split(":").first.to_i
|
||||||
@ -342,80 +350,78 @@ def opaque(secret_key)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = HTTP \Token authentication
|
# # HTTP Token authentication
|
||||||
#
|
#
|
||||||
# === Simple \Token example
|
# ### Simple Token example
|
||||||
#
|
#
|
||||||
# class PostsController < ApplicationController
|
# class PostsController < ApplicationController
|
||||||
# TOKEN = "secret"
|
# TOKEN = "secret"
|
||||||
#
|
#
|
||||||
# before_action :authenticate, except: [ :index ]
|
# before_action :authenticate, except: [ :index ]
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# render plain: "Everyone can see me!"
|
# render plain: "Everyone can see me!"
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def edit
|
|
||||||
# render plain: "I'm only accessible if you know the password"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
# def authenticate
|
|
||||||
# authenticate_or_request_with_http_token do |token, options|
|
|
||||||
# # Compare the tokens in a time-constant manner, to mitigate
|
|
||||||
# # timing attacks.
|
|
||||||
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Here is a more advanced Token example where only Atom feeds and the XML API are protected by HTTP token authentication.
|
|
||||||
# The regular HTML interface is protected by a session approach:
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# before_action :set_account, :authenticate
|
|
||||||
#
|
|
||||||
# private
|
|
||||||
# def set_account
|
|
||||||
# @account = Account.find_by(url_name: request.subdomains.first)
|
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def authenticate
|
# def edit
|
||||||
# case request.format
|
# render plain: "I'm only accessible if you know the password"
|
||||||
# when Mime[:xml], Mime[:atom]
|
# end
|
||||||
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
|
#
|
||||||
# @current_user = user
|
# private
|
||||||
# else
|
# def authenticate
|
||||||
# request_http_token_authentication
|
# authenticate_or_request_with_http_token do |token, options|
|
||||||
# end
|
# # Compare the tokens in a time-constant manner, to mitigate
|
||||||
# else
|
# # timing attacks.
|
||||||
# if session_authenticated?
|
# ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
|
||||||
# @current_user = @account.users.find(session[:authenticated][:user_id])
|
|
||||||
# else
|
|
||||||
# redirect_to(login_url) and return false
|
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
|
# Here is a more advanced Token example where only Atom feeds and the XML API
|
||||||
|
# are protected by HTTP token authentication. The regular HTML interface is
|
||||||
|
# protected by a session approach:
|
||||||
|
#
|
||||||
|
# class ApplicationController < ActionController::Base
|
||||||
|
# before_action :set_account, :authenticate
|
||||||
|
#
|
||||||
|
# private
|
||||||
|
# def set_account
|
||||||
|
# @account = Account.find_by(url_name: request.subdomains.first)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def authenticate
|
||||||
|
# case request.format
|
||||||
|
# when Mime[:xml], Mime[:atom]
|
||||||
|
# if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) }
|
||||||
|
# @current_user = user
|
||||||
|
# else
|
||||||
|
# request_http_token_authentication
|
||||||
|
# end
|
||||||
|
# else
|
||||||
|
# if session_authenticated?
|
||||||
|
# @current_user = @account.users.find(session[:authenticated][:user_id])
|
||||||
|
# else
|
||||||
|
# redirect_to(login_url) and return false
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
#
|
#
|
||||||
# In your integration tests, you can do something like this:
|
# In your integration tests, you can do something like this:
|
||||||
#
|
#
|
||||||
# def test_access_granted_from_xml
|
# def test_access_granted_from_xml
|
||||||
# authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
|
# authorization = ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token)
|
||||||
#
|
#
|
||||||
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
|
# get "/notes/1.xml", headers: { 'HTTP_AUTHORIZATION' => authorization }
|
||||||
#
|
#
|
||||||
# assert_equal 200, status
|
# assert_equal 200, status
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
#
|
# On shared hosts, Apache sometimes doesn't pass authentication headers to FCGI
|
||||||
# On shared hosts, Apache sometimes doesn't pass authentication headers to
|
# instances. If your environment matches this description and you cannot
|
||||||
# FCGI instances. If your environment matches this description and you cannot
|
|
||||||
# authenticate, try this rule in your Apache setup:
|
# authenticate, try this rule in your Apache setup:
|
||||||
#
|
#
|
||||||
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
|
||||||
module Token
|
module Token
|
||||||
TOKEN_KEY = "token="
|
TOKEN_KEY = "token="
|
||||||
TOKEN_REGEX = /^(Token|Bearer)\s+/
|
TOKEN_REGEX = /^(Token|Bearer)\s+/
|
||||||
@ -423,19 +429,18 @@ module Token
|
|||||||
extend self
|
extend self
|
||||||
|
|
||||||
module ControllerMethods
|
module ControllerMethods
|
||||||
# Authenticate using an HTTP Bearer token, or otherwise render an HTTP
|
# Authenticate using an HTTP Bearer token, or otherwise render an HTTP header
|
||||||
# header requesting the client to send a Bearer token. For the authentication
|
# requesting the client to send a Bearer token. For the authentication to be
|
||||||
# to be considered successful, +login_procedure+ should return a non-nil
|
# considered successful, `login_procedure` should return a non-nil value.
|
||||||
# value. Typically, the authenticated user is returned.
|
# Typically, the authenticated user is returned.
|
||||||
#
|
#
|
||||||
# See ActionController::HttpAuthentication::Token for example usage.
|
# See ActionController::HttpAuthentication::Token for example usage.
|
||||||
def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
|
def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
|
||||||
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
|
authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Authenticate using an HTTP Bearer token.
|
# Authenticate using an HTTP Bearer token. Returns the return value of
|
||||||
# Returns the return value of +login_procedure+ if a
|
# `login_procedure` if a token is found. Returns `nil` if no token is found.
|
||||||
# token is found. Returns +nil+ if no token is found.
|
|
||||||
#
|
#
|
||||||
# See ActionController::HttpAuthentication::Token for example usage.
|
# See ActionController::HttpAuthentication::Token for example usage.
|
||||||
def authenticate_with_http_token(&login_procedure)
|
def authenticate_with_http_token(&login_procedure)
|
||||||
@ -449,19 +454,20 @@ def request_http_token_authentication(realm = "Application", message = nil)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If token Authorization header is present, call the login
|
# If token Authorization header is present, call the login procedure with the
|
||||||
# procedure with the present token and options.
|
# present token and options.
|
||||||
#
|
#
|
||||||
# Returns the return value of +login_procedure+ if a
|
# Returns the return value of `login_procedure` if a token is found. Returns
|
||||||
# token is found. Returns +nil+ if no token is found.
|
# `nil` if no token is found.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
#
|
#
|
||||||
# * +controller+ - ActionController::Base instance for the current request.
|
# * `controller` - ActionController::Base instance for the current request.
|
||||||
# * +login_procedure+ - Proc to call if a token is present. The Proc
|
# * `login_procedure` - Proc to call if a token is present. The Proc should
|
||||||
# should take two arguments:
|
# take two arguments:
|
||||||
|
#
|
||||||
|
# authenticate(controller) { |token, options| ... }
|
||||||
#
|
#
|
||||||
# authenticate(controller) { |token, options| ... }
|
|
||||||
#
|
#
|
||||||
def authenticate(controller, &login_procedure)
|
def authenticate(controller, &login_procedure)
|
||||||
token, options = token_and_options(controller.request)
|
token, options = token_and_options(controller.request)
|
||||||
@ -470,21 +476,21 @@ def authenticate(controller, &login_procedure)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Parses the token and options out of the token Authorization header.
|
# Parses the token and options out of the token Authorization header. The value
|
||||||
# The value for the Authorization header is expected to have the prefix
|
# for the Authorization header is expected to have the prefix `"Token"` or
|
||||||
# <tt>"Token"</tt> or <tt>"Bearer"</tt>. If the header looks like this:
|
# `"Bearer"`. If the header looks like this:
|
||||||
#
|
#
|
||||||
# Authorization: Token token="abc", nonce="def"
|
# Authorization: Token token="abc", nonce="def"
|
||||||
#
|
#
|
||||||
# Then the returned token is <tt>"abc"</tt>, and the options are
|
# Then the returned token is `"abc"`, and the options are `{nonce: "def"}`.
|
||||||
# <tt>{nonce: "def"}</tt>.
|
|
||||||
#
|
#
|
||||||
# Returns an +Array+ of <tt>[String, Hash]</tt> if a token is present.
|
# Returns an `Array` of `[String, Hash]` if a token is present. Returns `nil` if
|
||||||
# Returns +nil+ if no token is found.
|
# no token is found.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
|
#
|
||||||
|
# * `request` - ActionDispatch::Request instance with the current headers.
|
||||||
#
|
#
|
||||||
# * +request+ - ActionDispatch::Request instance with the current headers.
|
|
||||||
def token_and_options(request)
|
def token_and_options(request)
|
||||||
authorization_request = request.authorization.to_s
|
authorization_request = request.authorization.to_s
|
||||||
if authorization_request[TOKEN_REGEX]
|
if authorization_request[TOKEN_REGEX]
|
||||||
@ -497,12 +503,12 @@ def token_params_from(auth)
|
|||||||
rewrite_param_values params_array_from raw_params auth
|
rewrite_param_values params_array_from raw_params auth
|
||||||
end
|
end
|
||||||
|
|
||||||
# Takes +raw_params+ and turns it into an array of parameters.
|
# Takes `raw_params` and turns it into an array of parameters.
|
||||||
def params_array_from(raw_params)
|
def params_array_from(raw_params)
|
||||||
raw_params.map { |param| param.split %r/=(.+)?/ }
|
raw_params.map { |param| param.split %r/=(.+)?/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
# This removes the <tt>"</tt> characters wrapping the value.
|
# This removes the `"` characters wrapping the value.
|
||||||
def rewrite_param_values(array_params)
|
def rewrite_param_values(array_params)
|
||||||
array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
|
array_params.each { |param| (param[1] || +"").gsub! %r/^"|"$/, "" }
|
||||||
end
|
end
|
||||||
@ -510,9 +516,9 @@ def rewrite_param_values(array_params)
|
|||||||
WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
|
WHITESPACED_AUTHN_PAIR_DELIMITERS = /\s*#{AUTHN_PAIR_DELIMITERS}\s*/
|
||||||
private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
|
private_constant :WHITESPACED_AUTHN_PAIR_DELIMITERS
|
||||||
|
|
||||||
# This method takes an authorization body and splits up the key-value
|
# This method takes an authorization body and splits up the key-value pairs by
|
||||||
# pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
|
# the standardized `:`, `;`, or `\t` delimiters defined in
|
||||||
# delimiters defined in +AUTHN_PAIR_DELIMITERS+.
|
# `AUTHN_PAIR_DELIMITERS`.
|
||||||
def raw_params(auth)
|
def raw_params(auth)
|
||||||
_raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
|
_raw_params = auth.sub(TOKEN_REGEX, "").split(WHITESPACED_AUTHN_PAIR_DELIMITERS)
|
||||||
_raw_params.reject!(&:empty?)
|
_raw_params.reject!(&:empty?)
|
||||||
@ -528,10 +534,11 @@ def raw_params(auth)
|
|||||||
#
|
#
|
||||||
# Returns String.
|
# Returns String.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
|
#
|
||||||
|
# * `token` - String token.
|
||||||
|
# * `options` - Optional Hash of the options.
|
||||||
#
|
#
|
||||||
# * +token+ - String token.
|
|
||||||
# * +options+ - Optional Hash of the options.
|
|
||||||
def encode_credentials(token, options = {})
|
def encode_credentials(token, options = {})
|
||||||
values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
|
values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
|
||||||
"#{key}=#{value.to_s.inspect}"
|
"#{key}=#{value.to_s.inspect}"
|
||||||
@ -543,10 +550,11 @@ def encode_credentials(token, options = {})
|
|||||||
#
|
#
|
||||||
# Returns nothing.
|
# Returns nothing.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
|
#
|
||||||
|
# * `controller` - ActionController::Base instance for the outgoing response.
|
||||||
|
# * `realm` - String realm to use in the header.
|
||||||
#
|
#
|
||||||
# * +controller+ - ActionController::Base instance for the outgoing response.
|
|
||||||
# * +realm+ - String realm to use in the header.
|
|
||||||
def authentication_request(controller, realm, message = nil)
|
def authentication_request(controller, realm, message = nil)
|
||||||
message ||= "HTTP Token: Access denied.\n"
|
message ||= "HTTP Token: Access denied.\n"
|
||||||
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
|
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"', "")}")
|
||||||
|
@ -1,33 +1,35 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller Implicit Render
|
# # Action Controller Implicit Render
|
||||||
#
|
#
|
||||||
# Handles implicit rendering for a controller action that does not
|
# Handles implicit rendering for a controller action that does not explicitly
|
||||||
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
|
# respond with `render`, `respond_to`, `redirect`, or `head`.
|
||||||
#
|
#
|
||||||
# For API controllers, the implicit response is always <tt>204 No Content</tt>.
|
# For API controllers, the implicit response is always `204 No Content`.
|
||||||
#
|
#
|
||||||
# For all other controllers, we use these heuristics to decide whether to
|
# For all other controllers, we use these heuristics to decide whether to render
|
||||||
# render a template, raise an error for a missing template, or respond with
|
# a template, raise an error for a missing template, or respond with `204 No
|
||||||
# <tt>204 No Content</tt>:
|
# Content`:
|
||||||
#
|
#
|
||||||
# First, if we DO find a template, it's rendered. Template lookup accounts
|
# First, if we DO find a template, it's rendered. Template lookup accounts for
|
||||||
# for the action name, locales, format, variant, template handlers, and more
|
# the action name, locales, format, variant, template handlers, and more (see
|
||||||
# (see +render+ for details).
|
# `render` for details).
|
||||||
#
|
#
|
||||||
# Second, if we DON'T find a template but the controller action does have
|
# Second, if we DON'T find a template but the controller action does have
|
||||||
# templates for other formats, variants, etc., then we trust that you meant
|
# templates for other formats, variants, etc., then we trust that you meant to
|
||||||
# to provide a template for this response, too, and we raise
|
# provide a template for this response, too, and we raise
|
||||||
# ActionController::UnknownFormat with an explanation.
|
# ActionController::UnknownFormat with an explanation.
|
||||||
#
|
#
|
||||||
# Third, if we DON'T find a template AND the request is a page load in a web
|
# Third, if we DON'T find a template AND the request is a page load in a web
|
||||||
# browser (technically, a non-XHR GET request for an HTML response) where
|
# browser (technically, a non-XHR GET request for an HTML response) where you
|
||||||
# you reasonably expect to have rendered a template, then we raise
|
# reasonably expect to have rendered a template, then we raise
|
||||||
# ActionController::MissingExactTemplate with an explanation.
|
# ActionController::MissingExactTemplate with an explanation.
|
||||||
#
|
#
|
||||||
# Finally, if we DON'T find a template AND the request isn't a browser page
|
# Finally, if we DON'T find a template AND the request isn't a browser page
|
||||||
# load, then we implicitly respond with <tt>204 No Content</tt>.
|
# load, then we implicitly respond with `204 No Content`.
|
||||||
module ImplicitRender
|
module ImplicitRender
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
include BasicImplicitRender
|
include BasicImplicitRender
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "benchmark"
|
require "benchmark"
|
||||||
require "abstract_controller/logger"
|
require "abstract_controller/logger"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \Instrumentation
|
# # Action Controller Instrumentation
|
||||||
#
|
#
|
||||||
# Adds instrumentation to several ends in ActionController::Base. It also provides
|
# Adds instrumentation to several ends in ActionController::Base. It also
|
||||||
# some hooks related with process_action. This allows an ORM like Active Record
|
# provides some hooks related with process_action. This allows an ORM like
|
||||||
# and/or DataMapper to plug in ActionController and show related information.
|
# Active Record and/or DataMapper to plug in ActionController and show related
|
||||||
|
# information.
|
||||||
#
|
#
|
||||||
# Check ActiveRecord::Railties::ControllerRuntime for an example.
|
# Check ActiveRecord::Railties::ControllerRuntime for an example.
|
||||||
module Instrumentation
|
module Instrumentation
|
||||||
@ -91,23 +94,23 @@ def halted_callback_hook(filter, _)
|
|||||||
# A hook which allows you to clean up any time, wrongly taken into account in
|
# A hook which allows you to clean up any time, wrongly taken into account in
|
||||||
# views, like database querying time.
|
# views, like database querying time.
|
||||||
#
|
#
|
||||||
# def cleanup_view_runtime
|
# def cleanup_view_runtime
|
||||||
# super - time_taken_in_something_expensive
|
# super - time_taken_in_something_expensive
|
||||||
# end
|
# end
|
||||||
def cleanup_view_runtime # :doc:
|
def cleanup_view_runtime # :doc:
|
||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
|
|
||||||
# Every time after an action is processed, this method is invoked
|
# Every time after an action is processed, this method is invoked with the
|
||||||
# with the payload, so you can add more information.
|
# payload, so you can add more information.
|
||||||
def append_info_to_payload(payload) # :doc:
|
def append_info_to_payload(payload) # :doc:
|
||||||
payload[:view_runtime] = view_runtime
|
payload[:view_runtime] = view_runtime
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# A hook which allows other frameworks to log what happened during
|
# A hook which allows other frameworks to log what happened during controller
|
||||||
# controller process action. This method should return an array
|
# process action. This method should return an array with the messages to be
|
||||||
# with the messages to be added.
|
# added.
|
||||||
def log_process_action(payload) # :nodoc:
|
def log_process_action(payload) # :nodoc:
|
||||||
messages, view_runtime = [], payload[:view_runtime]
|
messages, view_runtime = [], payload[:view_runtime]
|
||||||
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
|
||||||
|
@ -1,56 +1,58 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/http/response"
|
require "action_dispatch/http/response"
|
||||||
require "delegate"
|
require "delegate"
|
||||||
require "active_support/json"
|
require "active_support/json"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \Live
|
# # Action Controller Live
|
||||||
#
|
#
|
||||||
# Mix this module into your controller, and all actions in that controller
|
# Mix this module into your controller, and all actions in that controller will
|
||||||
# will be able to stream data to the client as it's written.
|
# be able to stream data to the client as it's written.
|
||||||
#
|
#
|
||||||
# class MyController < ActionController::Base
|
# class MyController < ActionController::Base
|
||||||
# include ActionController::Live
|
# include ActionController::Live
|
||||||
#
|
#
|
||||||
# def stream
|
# def stream
|
||||||
# response.headers['Content-Type'] = 'text/event-stream'
|
# response.headers['Content-Type'] = 'text/event-stream'
|
||||||
# 100.times {
|
# 100.times {
|
||||||
# response.stream.write "hello world\n"
|
# response.stream.write "hello world\n"
|
||||||
# sleep 1
|
# sleep 1
|
||||||
# }
|
# }
|
||||||
# ensure
|
# ensure
|
||||||
# response.stream.close
|
# response.stream.close
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# There are a few caveats with this module. You *cannot* write headers after the
|
# There are a few caveats with this module. You **cannot** write headers after
|
||||||
# response has been committed (Response#committed? will return truthy).
|
# the response has been committed (Response#committed? will return truthy).
|
||||||
# Calling +write+ or +close+ on the response stream will cause the response
|
# Calling `write` or `close` on the response stream will cause the response
|
||||||
# object to be committed. Make sure all headers are set before calling write
|
# object to be committed. Make sure all headers are set before calling write or
|
||||||
# or close on your stream.
|
# close on your stream.
|
||||||
#
|
#
|
||||||
# You *must* call close on your stream when you're finished, otherwise the
|
# You **must** call close on your stream when you're finished, otherwise the
|
||||||
# socket may be left open forever.
|
# socket may be left open forever.
|
||||||
#
|
#
|
||||||
# The final caveat is that your actions are executed in a separate thread than
|
# The final caveat is that your actions are executed in a separate thread than
|
||||||
# the main thread. Make sure your actions are thread safe, and this shouldn't
|
# the main thread. Make sure your actions are thread safe, and this shouldn't be
|
||||||
# be a problem (don't share state across threads, etc).
|
# a problem (don't share state across threads, etc).
|
||||||
#
|
#
|
||||||
# Note that \Rails includes +Rack::ETag+ by default, which will buffer your
|
# Note that Rails includes `Rack::ETag` by default, which will buffer your
|
||||||
# response. As a result, streaming responses may not work properly with Rack
|
# response. As a result, streaming responses may not work properly with Rack
|
||||||
# 2.2.x, and you may need to implement workarounds in your application.
|
# 2.2.x, and you may need to implement workarounds in your application. You can
|
||||||
# You can either set the +ETag+ or +Last-Modified+ response headers or remove
|
# either set the `ETag` or `Last-Modified` response headers or remove
|
||||||
# +Rack::ETag+ from the middleware stack to address this issue.
|
# `Rack::ETag` from the middleware stack to address this issue.
|
||||||
#
|
#
|
||||||
# Here's an example of how you can set the +Last-Modified+ header if your Rack
|
# Here's an example of how you can set the `Last-Modified` header if your Rack
|
||||||
# version is 2.2.x:
|
# version is 2.2.x:
|
||||||
#
|
#
|
||||||
# def stream
|
# def stream
|
||||||
# response.headers["Content-Type"] = "text/event-stream"
|
# response.headers["Content-Type"] = "text/event-stream"
|
||||||
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
|
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
|
||||||
# ...
|
# ...
|
||||||
# end
|
# end
|
||||||
module Live
|
module Live
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
@ -66,44 +68,44 @@ def make_response!(request)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Action Controller \Live Server Sent Events
|
# # Action Controller Live Server Sent Events
|
||||||
#
|
#
|
||||||
# This class provides the ability to write an SSE (Server Sent Event)
|
# This class provides the ability to write an SSE (Server Sent Event) to an IO
|
||||||
# to an IO stream. The class is initialized with a stream and can be used
|
# stream. The class is initialized with a stream and can be used to either write
|
||||||
# to either write a JSON string or an object which can be converted to JSON.
|
# a JSON string or an object which can be converted to JSON.
|
||||||
#
|
#
|
||||||
# Writing an object will convert it into standard SSE format with whatever
|
# Writing an object will convert it into standard SSE format with whatever
|
||||||
# options you have configured. You may choose to set the following options:
|
# options you have configured. You may choose to set the following options:
|
||||||
#
|
#
|
||||||
# 1) Event. If specified, an event with this name will be dispatched on
|
# 1) Event. If specified, an event with this name will be dispatched on
|
||||||
# the browser.
|
# the browser.
|
||||||
# 2) Retry. The reconnection time in milliseconds used when attempting
|
# 2) Retry. The reconnection time in milliseconds used when attempting
|
||||||
# to send the event.
|
# to send the event.
|
||||||
# 3) Id. If the connection dies while sending an SSE to the browser, then
|
# 3) Id. If the connection dies while sending an SSE to the browser, then
|
||||||
# the server will receive a +Last-Event-ID+ header with value equal to +id+.
|
# the server will receive a +Last-Event-ID+ header with value equal to +id+.
|
||||||
#
|
#
|
||||||
# After setting an option in the constructor of the SSE object, all future
|
# After setting an option in the constructor of the SSE object, all future SSEs
|
||||||
# SSEs sent across the stream will use those options unless overridden.
|
# sent across the stream will use those options unless overridden.
|
||||||
#
|
#
|
||||||
# Example Usage:
|
# Example Usage:
|
||||||
#
|
#
|
||||||
# class MyController < ActionController::Base
|
# class MyController < ActionController::Base
|
||||||
# include ActionController::Live
|
# include ActionController::Live
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# response.headers['Content-Type'] = 'text/event-stream'
|
# response.headers['Content-Type'] = 'text/event-stream'
|
||||||
# sse = SSE.new(response.stream, retry: 300, event: "event-name")
|
# sse = SSE.new(response.stream, retry: 300, event: "event-name")
|
||||||
# sse.write({ name: 'John'})
|
# sse.write({ name: 'John'})
|
||||||
# sse.write({ name: 'John'}, id: 10)
|
# sse.write({ name: 'John'}, id: 10)
|
||||||
# sse.write({ name: 'John'}, id: 10, event: "other-event")
|
# sse.write({ name: 'John'}, id: 10, event: "other-event")
|
||||||
# sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
|
# sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
|
||||||
# ensure
|
# ensure
|
||||||
# sse.close
|
# sse.close
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Note: SSEs are not currently supported by IE. However, they are supported
|
# Note: SSEs are not currently supported by IE. However, they are supported by
|
||||||
# by Chrome, Firefox, Opera, and Safari.
|
# Chrome, Firefox, Opera, and Safari.
|
||||||
class SSE
|
class SSE
|
||||||
PERMITTED_OPTIONS = %w( retry event id )
|
PERMITTED_OPTIONS = %w( retry event id )
|
||||||
|
|
||||||
@ -153,10 +155,9 @@ class << self
|
|||||||
|
|
||||||
# Ignore that the client has disconnected.
|
# Ignore that the client has disconnected.
|
||||||
#
|
#
|
||||||
# If this value is `true`, calling `write` after the client
|
# If this value is `true`, calling `write` after the client disconnects will
|
||||||
# disconnects will result in the written content being silently
|
# result in the written content being silently discarded. If this value is
|
||||||
# discarded. If this value is `false` (the default), a
|
# `false` (the default), a ClientDisconnected exception will be raised.
|
||||||
# ClientDisconnected exception will be raised.
|
|
||||||
attr_accessor :ignore_disconnect
|
attr_accessor :ignore_disconnect
|
||||||
|
|
||||||
def initialize(response)
|
def initialize(response)
|
||||||
@ -167,9 +168,10 @@ def initialize(response)
|
|||||||
@ignore_disconnect = false
|
@ignore_disconnect = false
|
||||||
end
|
end
|
||||||
|
|
||||||
# ActionDispatch::Response delegates #to_ary to the internal ActionDispatch::Response::Buffer,
|
# ActionDispatch::Response delegates #to_ary to the internal
|
||||||
# defining #to_ary is an indicator that the response body can be buffered and/or cached by
|
# ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the
|
||||||
# Rack middlewares, this is not the case for Live responses so we undefine it for this Buffer subclass.
|
# response body can be buffered and/or cached by Rack middlewares, this is not
|
||||||
|
# the case for Live responses so we undefine it for this Buffer subclass.
|
||||||
undef_method :to_ary
|
undef_method :to_ary
|
||||||
|
|
||||||
def write(string)
|
def write(string)
|
||||||
@ -184,21 +186,20 @@ def write(string)
|
|||||||
@buf.clear
|
@buf.clear
|
||||||
|
|
||||||
unless @ignore_disconnect
|
unless @ignore_disconnect
|
||||||
# Raise ClientDisconnected, which is a RuntimeError (not an
|
# Raise ClientDisconnected, which is a RuntimeError (not an IOError), because
|
||||||
# IOError), because that's more appropriate for something beyond
|
# that's more appropriate for something beyond the developer's control.
|
||||||
# the developer's control.
|
|
||||||
raise ClientDisconnected, "client disconnected"
|
raise ClientDisconnected, "client disconnected"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Same as +write+ but automatically include a newline at the end of the string.
|
# Same as `write` but automatically include a newline at the end of the string.
|
||||||
def writeln(string)
|
def writeln(string)
|
||||||
write string.end_with?("\n") ? string : "#{string}\n"
|
write string.end_with?("\n") ? string : "#{string}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Write a 'close' event to the buffer; the producer/writing thread
|
# Write a 'close' event to the buffer; the producer/writing thread uses this to
|
||||||
# uses this to notify us that it's finished supplying content.
|
# notify us that it's finished supplying content.
|
||||||
#
|
#
|
||||||
# See also #abort.
|
# See also #abort.
|
||||||
def close
|
def close
|
||||||
@ -209,9 +210,8 @@ def close
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inform the producer/writing thread that the client has
|
# Inform the producer/writing thread that the client has disconnected; the
|
||||||
# disconnected; the reading thread is no longer interested in
|
# reading thread is no longer interested in anything that's being written.
|
||||||
# anything that's being written.
|
|
||||||
#
|
#
|
||||||
# See also #close.
|
# See also #close.
|
||||||
def abort
|
def abort
|
||||||
@ -223,8 +223,8 @@ def abort
|
|||||||
|
|
||||||
# Is the client still connected and waiting for content?
|
# Is the client still connected and waiting for content?
|
||||||
#
|
#
|
||||||
# The result of calling `write` when this is `false` is determined
|
# The result of calling `write` when this is `false` is determined by
|
||||||
# by `ignore_disconnect`.
|
# `ignore_disconnect`.
|
||||||
def connected?
|
def connected?
|
||||||
!@aborted
|
!@aborted
|
||||||
end
|
end
|
||||||
@ -275,15 +275,15 @@ def process(name)
|
|||||||
locals = t1.keys.map { |key| [key, t1[key]] }
|
locals = t1.keys.map { |key| [key, t1[key]] }
|
||||||
|
|
||||||
error = nil
|
error = nil
|
||||||
# This processes the action in a child thread. It lets us return the
|
# This processes the action in a child thread. It lets us return the response
|
||||||
# response code and headers back up the Rack stack, and still process
|
# code and headers back up the Rack stack, and still process the body in
|
||||||
# the body in parallel with sending data to the client.
|
# parallel with sending data to the client.
|
||||||
new_controller_thread {
|
new_controller_thread {
|
||||||
ActiveSupport::Dependencies.interlock.running do
|
ActiveSupport::Dependencies.interlock.running do
|
||||||
t2 = Thread.current
|
t2 = Thread.current
|
||||||
|
|
||||||
# Since we're processing the view in a different thread, copy the
|
# Since we're processing the view in a different thread, copy the thread locals
|
||||||
# thread locals from the main thread to the child thread. :'(
|
# from the main thread to the child thread. :'(
|
||||||
locals.each { |k, v| t2[k] = v }
|
locals.each { |k, v| t2[k] = v }
|
||||||
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
||||||
|
|
||||||
@ -321,27 +321,30 @@ def response_body=(body)
|
|||||||
response.close if response
|
response.close if response
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
|
# Sends a stream to the browser, which is helpful when you're generating exports
|
||||||
# don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
|
# or other running data where you don't want the entire file buffered in memory
|
||||||
|
# first. Similar to send_data, but where the data is generated live.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
# * `:filename` - suggests a filename for the browser to use.
|
||||||
# * <tt>:type</tt> - specifies an HTTP content type.
|
# * `:type` - specifies an HTTP content type. You can specify either a string
|
||||||
# You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
|
# or a symbol for a registered type with `Mime::Type.register`, for example
|
||||||
# If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
|
# :json. If omitted, type will be inferred from the file extension specified
|
||||||
# If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
|
# in `:filename`. If no content type is registered for the extension, the
|
||||||
# * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
|
# default type 'application/octet-stream' will be used.
|
||||||
# Valid values are 'inline' and 'attachment' (default).
|
# * `:disposition` - specifies whether the file will be shown inline or
|
||||||
|
# downloaded. Valid values are 'inline' and 'attachment' (default).
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# Example of generating a csv export:
|
# Example of generating a csv export:
|
||||||
#
|
#
|
||||||
# send_stream(filename: "subscribers.csv") do |stream|
|
# send_stream(filename: "subscribers.csv") do |stream|
|
||||||
# stream.write "email_address,updated_at\n"
|
# stream.write "email_address,updated_at\n"
|
||||||
#
|
#
|
||||||
# @subscribers.find_each do |subscriber|
|
# @subscribers.find_each do |subscriber|
|
||||||
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
|
# stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
def send_stream(filename:, disposition: "attachment", type: nil)
|
def send_stream(filename:, disposition: "attachment", type: nil)
|
||||||
payload = { filename: filename, disposition: disposition, type: type }
|
payload = { filename: filename, disposition: disposition, type: type }
|
||||||
ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do
|
ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do
|
||||||
@ -360,10 +363,10 @@ def send_stream(filename:, disposition: "attachment", type: nil)
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Spawn a new thread to serve up the controller in. This is to get
|
# Spawn a new thread to serve up the controller in. This is to get around the
|
||||||
# around the fact that Rack isn't based around IOs and we need to use
|
# fact that Rack isn't based around IOs and we need to use a thread to stream
|
||||||
# a thread to stream data from the response bodies. Nobody should call
|
# data from the response bodies. Nobody should call this method except in Rails
|
||||||
# this method except in Rails internals. Seriously!
|
# internals. Seriously!
|
||||||
def new_controller_thread # :nodoc:
|
def new_controller_thread # :nodoc:
|
||||||
Thread.new {
|
Thread.new {
|
||||||
t2 = Thread.current
|
t2 = Thread.current
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Logging
|
module Logging
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -7,10 +9,10 @@ module Logging
|
|||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Set a different log level per request.
|
# Set a different log level per request.
|
||||||
#
|
#
|
||||||
# # Use the debug log level if a particular cookie is set.
|
# # Use the debug log level if a particular cookie is set.
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# log_at :debug, if: -> { cookies[:debug] }
|
# log_at :debug, if: -> { cookies[:debug] }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
def log_at(level, **options)
|
def log_at(level, **options)
|
||||||
around_action ->(_, action) { logger.log_at(level, &action) }, **options
|
around_action ->(_, action) { logger.log_at(level, &action) }, **options
|
||||||
|
@ -1,203 +1,213 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "abstract_controller/collector"
|
require "abstract_controller/collector"
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module MimeResponds
|
module MimeResponds
|
||||||
# Without web-service support, an action which collects the data for displaying a list of people
|
# Without web-service support, an action which collects the data for displaying
|
||||||
# might look something like this:
|
# a list of people might look something like this:
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# @people = Person.all
|
# @people = Person.all
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# That action implicitly responds to all formats, but formats can also be explicitly enumerated:
|
# That action implicitly responds to all formats, but formats can also be
|
||||||
|
# explicitly enumerated:
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# @people = Person.all
|
# @people = Person.all
|
||||||
# respond_to :html, :js
|
# respond_to :html, :js
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Here's the same action, with web-service support baked in:
|
# Here's the same action, with web-service support baked in:
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# @people = Person.all
|
# @people = Person.all
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html
|
# format.html
|
||||||
# format.js
|
# format.js
|
||||||
# format.xml { render xml: @people }
|
# format.xml { render xml: @people }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# What that says is, "if the client wants HTML or JS in response to this action, just respond as we
|
# What that says is, "if the client wants HTML or JS in response to this action,
|
||||||
# would have before, but if the client wants XML, return them the list of people in XML format."
|
# just respond as we would have before, but if the client wants XML, return them
|
||||||
# (\Rails determines the desired response format from the HTTP Accept header submitted by the client.)
|
# the list of people in XML format." (Rails determines the desired response
|
||||||
|
# format from the HTTP Accept header submitted by the client.)
|
||||||
#
|
#
|
||||||
# Supposing you have an action that adds a new person, optionally creating their company
|
# Supposing you have an action that adds a new person, optionally creating their
|
||||||
# (by name) if it does not already exist, without web-services, it might look like this:
|
# company (by name) if it does not already exist, without web-services, it might
|
||||||
|
# look like this:
|
||||||
#
|
#
|
||||||
# def create
|
# def create
|
||||||
# @company = Company.find_or_create_by(name: params[:company][:name])
|
# @company = Company.find_or_create_by(name: params[:company][:name])
|
||||||
# @person = @company.people.create(params[:person])
|
# @person = @company.people.create(params[:person])
|
||||||
#
|
#
|
||||||
# redirect_to(person_list_url)
|
# redirect_to(person_list_url)
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Here's the same action, with web-service support baked in:
|
# Here's the same action, with web-service support baked in:
|
||||||
#
|
#
|
||||||
# def create
|
# def create
|
||||||
# company = params[:person].delete(:company)
|
# company = params[:person].delete(:company)
|
||||||
# @company = Company.find_or_create_by(name: company[:name])
|
# @company = Company.find_or_create_by(name: company[:name])
|
||||||
# @person = @company.people.create(params[:person])
|
# @person = @company.people.create(params[:person])
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html { redirect_to(person_list_url) }
|
# format.html { redirect_to(person_list_url) }
|
||||||
# format.js
|
# format.js
|
||||||
# format.xml { render xml: @person.to_xml(include: @company) }
|
# format.xml { render xml: @person.to_xml(include: @company) }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
|
# If the client wants HTML, we just redirect them back to the person list. If
|
||||||
# then it is an Ajax request and we render the JavaScript template associated with this action.
|
# they want JavaScript, then it is an Ajax request and we render the JavaScript
|
||||||
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
|
# template associated with this action. Lastly, if the client wants XML, we
|
||||||
# include the person's company in the rendered XML, so you get something like this:
|
# render the created person as XML, but with a twist: we also include the
|
||||||
|
# person's company in the rendered XML, so you get something like this:
|
||||||
#
|
#
|
||||||
# <person>
|
# <person>
|
||||||
# <id>...</id>
|
|
||||||
# ...
|
|
||||||
# <company>
|
|
||||||
# <id>...</id>
|
# <id>...</id>
|
||||||
# <name>...</name>
|
|
||||||
# ...
|
# ...
|
||||||
# </company>
|
# <company>
|
||||||
# </person>
|
# <id>...</id>
|
||||||
|
# <name>...</name>
|
||||||
|
# ...
|
||||||
|
# </company>
|
||||||
|
# </person>
|
||||||
#
|
#
|
||||||
# Note, however, the extra bit at the top of that action:
|
# Note, however, the extra bit at the top of that action:
|
||||||
#
|
#
|
||||||
# company = params[:person].delete(:company)
|
# company = params[:person].delete(:company)
|
||||||
# @company = Company.find_or_create_by(name: company[:name])
|
# @company = Company.find_or_create_by(name: company[:name])
|
||||||
#
|
#
|
||||||
# This is because the incoming XML document (if a web-service request is in process) can only contain a
|
# This is because the incoming XML document (if a web-service request is in
|
||||||
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
|
# process) can only contain a single root-node. So, we have to rearrange things
|
||||||
|
# so that the request looks like this (url-encoded):
|
||||||
#
|
#
|
||||||
# person[name]=...&person[company][name]=...&...
|
# person[name]=...&person[company][name]=...&...
|
||||||
#
|
#
|
||||||
# And, like this (xml-encoded):
|
# And, like this (xml-encoded):
|
||||||
#
|
#
|
||||||
# <person>
|
# <person>
|
||||||
# <name>...</name>
|
|
||||||
# <company>
|
|
||||||
# <name>...</name>
|
# <name>...</name>
|
||||||
# </company>
|
# <company>
|
||||||
# </person>
|
# <name>...</name>
|
||||||
|
# </company>
|
||||||
|
# </person>
|
||||||
#
|
#
|
||||||
# In other words, we make the request so that it operates on a single entity's person. Then, in the action,
|
# In other words, we make the request so that it operates on a single entity's
|
||||||
# we extract the company data from the request, find or create the company, and then create the new person
|
# person. Then, in the action, we extract the company data from the request,
|
||||||
# with the remaining data.
|
# find or create the company, and then create the new person with the remaining
|
||||||
|
# data.
|
||||||
#
|
#
|
||||||
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
|
# Note that you can define your own XML parameter parser which would allow you
|
||||||
# in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
|
# to describe multiple entities in a single request (i.e., by wrapping them all
|
||||||
# and accept \Rails' defaults, life will be much easier.
|
# in a single root node), but if you just go with the flow and accept Rails'
|
||||||
|
# defaults, life will be much easier.
|
||||||
#
|
#
|
||||||
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
|
# If you need to use a MIME type which isn't supported by default, you can
|
||||||
# +config/initializers/mime_types.rb+ as follows.
|
# register your own handlers in `config/initializers/mime_types.rb` as follows.
|
||||||
#
|
#
|
||||||
# Mime::Type.register "image/jpeg", :jpg
|
# Mime::Type.register "image/jpeg", :jpg
|
||||||
#
|
#
|
||||||
# +respond_to+ also allows you to specify a common block for different formats by using +any+:
|
# `respond_to` also allows you to specify a common block for different formats
|
||||||
|
# by using `any`:
|
||||||
#
|
#
|
||||||
# def index
|
# def index
|
||||||
# @people = Person.all
|
# @people = Person.all
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html
|
# format.html
|
||||||
# format.any(:xml, :json) { render request.format.to_sym => @people }
|
# format.any(:xml, :json) { render request.format.to_sym => @people }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# In the example above, if the format is xml, it will render:
|
# In the example above, if the format is xml, it will render:
|
||||||
#
|
#
|
||||||
# render xml: @people
|
# render xml: @people
|
||||||
#
|
#
|
||||||
# Or if the format is json:
|
# Or if the format is json:
|
||||||
#
|
#
|
||||||
# render json: @people
|
# render json: @people
|
||||||
#
|
#
|
||||||
# +any+ can also be used with no arguments, in which case it will be used for any format requested by
|
# `any` can also be used with no arguments, in which case it will be used for
|
||||||
# the user:
|
# any format requested by the user:
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html
|
# format.html
|
||||||
# format.any { redirect_to support_path }
|
# format.any { redirect_to support_path }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Formats can have different variants.
|
# Formats can have different variants.
|
||||||
#
|
#
|
||||||
# The request variant is a specialization of the request format, like <tt>:tablet</tt>,
|
# The request variant is a specialization of the request format, like `:tablet`,
|
||||||
# <tt>:phone</tt>, or <tt>:desktop</tt>.
|
# `:phone`, or `:desktop`.
|
||||||
#
|
#
|
||||||
# We often want to render different html/json/xml templates for phones,
|
# We often want to render different html/json/xml templates for phones, tablets,
|
||||||
# tablets, and desktop browsers. Variants make it easy.
|
# and desktop browsers. Variants make it easy.
|
||||||
#
|
#
|
||||||
# You can set the variant in a +before_action+:
|
# You can set the variant in a `before_action`:
|
||||||
#
|
#
|
||||||
# request.variant = :tablet if /iPad/.match?(request.user_agent)
|
# request.variant = :tablet if /iPad/.match?(request.user_agent)
|
||||||
#
|
#
|
||||||
# Respond to variants in the action just like you respond to formats:
|
# Respond to variants in the action just like you respond to formats:
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html do |variant|
|
# format.html do |variant|
|
||||||
# variant.tablet # renders app/views/projects/show.html+tablet.erb
|
# variant.tablet # renders app/views/projects/show.html+tablet.erb
|
||||||
# variant.phone { extra_setup; render ... }
|
# variant.phone { extra_setup; render ... }
|
||||||
# variant.none { special_setup } # executed only if there is no variant set
|
# variant.none { special_setup } # executed only if there is no variant set
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Provide separate templates for each format and variant:
|
# Provide separate templates for each format and variant:
|
||||||
#
|
#
|
||||||
# app/views/projects/show.html.erb
|
# app/views/projects/show.html.erb
|
||||||
# app/views/projects/show.html+tablet.erb
|
# app/views/projects/show.html+tablet.erb
|
||||||
# app/views/projects/show.html+phone.erb
|
# app/views/projects/show.html+phone.erb
|
||||||
#
|
#
|
||||||
# When you're not sharing any code within the format, you can simplify defining variants
|
# When you're not sharing any code within the format, you can simplify defining
|
||||||
# using the inline syntax:
|
# variants using the inline syntax:
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.js { render "trash" }
|
# format.js { render "trash" }
|
||||||
# format.html.phone { redirect_to progress_path }
|
# format.html.phone { redirect_to progress_path }
|
||||||
# format.html.none { render "trash" }
|
# format.html.none { render "trash" }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Variants also support common +any+/+all+ block that formats have.
|
# Variants also support common `any`/`all` block that formats have.
|
||||||
#
|
#
|
||||||
# It works for both inline:
|
# It works for both inline:
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html.any { render html: "any" }
|
# format.html.any { render html: "any" }
|
||||||
# format.html.phone { render html: "phone" }
|
# format.html.phone { render html: "phone" }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# and block syntax:
|
# and block syntax:
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html do |variant|
|
# format.html do |variant|
|
||||||
# variant.any(:tablet, :phablet){ render html: "any" }
|
# variant.any(:tablet, :phablet){ render html: "any" }
|
||||||
# variant.phone { render html: "phone" }
|
# variant.phone { render html: "phone" }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# You can also set an array of variants:
|
# You can also set an array of variants:
|
||||||
#
|
#
|
||||||
# request.variant = [:tablet, :phone]
|
# request.variant = [:tablet, :phone]
|
||||||
#
|
#
|
||||||
# This will work similarly to formats and MIME types negotiation. If there
|
# This will work similarly to formats and MIME types negotiation. If there is no
|
||||||
# is no +:tablet+ variant declared, the +:phone+ variant will be used:
|
# `:tablet` variant declared, the `:phone` variant will be used:
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html.none
|
# format.html.none
|
||||||
# format.html.phone # this gets rendered
|
# format.html.phone # this gets rendered
|
||||||
# end
|
# end
|
||||||
def respond_to(*mimes)
|
def respond_to(*mimes)
|
||||||
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
|
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
|
||||||
|
|
||||||
@ -217,27 +227,26 @@ def respond_to(*mimes)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A container for responses available from the current controller for
|
# A container for responses available from the current controller for requests
|
||||||
# requests for different mime-types sent to a particular action.
|
# for different mime-types sent to a particular action.
|
||||||
#
|
#
|
||||||
# The public controller methods +respond_to+ may be called with a block
|
# The public controller methods `respond_to` may be called with a block that is
|
||||||
# that is used to define responses to different mime-types, e.g.
|
# used to define responses to different mime-types, e.g. for `respond_to` :
|
||||||
# for +respond_to+ :
|
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html
|
# format.html
|
||||||
# format.xml { render xml: @people }
|
# format.xml { render xml: @people }
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# In this usage, the argument passed to the block (+format+ above) is an
|
# In this usage, the argument passed to the block (`format` above) is an
|
||||||
# instance of the ActionController::MimeResponds::Collector class. This
|
# instance of the ActionController::MimeResponds::Collector class. This object
|
||||||
# object serves as a container in which available responses can be stored by
|
# serves as a container in which available responses can be stored by calling
|
||||||
# calling any of the dynamically generated, mime-type-specific methods such
|
# any of the dynamically generated, mime-type-specific methods such as `html`,
|
||||||
# as +html+, +xml+ etc on the Collector. Each response is represented by a
|
# `xml` etc on the Collector. Each response is represented by a corresponding
|
||||||
# corresponding block if present.
|
# block if present.
|
||||||
#
|
#
|
||||||
# A subsequent call to #negotiate_format(request) will enable the Collector
|
# A subsequent call to #negotiate_format(request) will enable the Collector to
|
||||||
# to determine which specific mime-type it should respond with for the current
|
# determine which specific mime-type it should respond with for the current
|
||||||
# request, with this response then being accessible by calling #response.
|
# request, with this response then being accessible by calling #response.
|
||||||
class Collector
|
class Collector
|
||||||
include AbstractController::Collector
|
include AbstractController::Collector
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# Specify binary encoding for parameters for a given action.
|
# Specify binary encoding for parameters for a given action.
|
||||||
module ParameterEncoding
|
module ParameterEncoding
|
||||||
@ -21,59 +23,59 @@ def action_encoding_template(action) # :nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify that a given action's parameters should all be encoded as
|
# Specify that a given action's parameters should all be encoded as ASCII-8BIT
|
||||||
# ASCII-8BIT (it "skips" the encoding default of UTF-8).
|
# (it "skips" the encoding default of UTF-8).
|
||||||
#
|
#
|
||||||
# For example, a controller would use it like this:
|
# For example, a controller would use it like this:
|
||||||
#
|
#
|
||||||
# class RepositoryController < ActionController::Base
|
# class RepositoryController < ActionController::Base
|
||||||
# skip_parameter_encoding :show
|
# skip_parameter_encoding :show
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @repo = Repository.find_by_filesystem_path params[:file_path]
|
# @repo = Repository.find_by_filesystem_path params[:file_path]
|
||||||
#
|
#
|
||||||
# # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so
|
# # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so
|
||||||
# # tag it as such
|
# # tag it as such
|
||||||
# @repo_name = params[:repo_name].force_encoding 'UTF-8'
|
# @repo_name = params[:repo_name].force_encoding 'UTF-8'
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def index
|
||||||
|
# @repositories = Repository.all
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def index
|
|
||||||
# @repositories = Repository.all
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# The show action in the above controller would have all parameter values
|
# The show action in the above controller would have all parameter values
|
||||||
# encoded as ASCII-8BIT. This is useful in the case where an application
|
# encoded as ASCII-8BIT. This is useful in the case where an application must
|
||||||
# must handle data but encoding of the data is unknown, like file system data.
|
# handle data but encoding of the data is unknown, like file system data.
|
||||||
def skip_parameter_encoding(action)
|
def skip_parameter_encoding(action)
|
||||||
@_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT }
|
@_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify the encoding for a parameter on an action.
|
# Specify the encoding for a parameter on an action. If not specified the
|
||||||
# If not specified the default is UTF-8.
|
# default is UTF-8.
|
||||||
#
|
#
|
||||||
# You can specify a binary (ASCII_8BIT) parameter with:
|
# You can specify a binary (ASCII_8BIT) parameter with:
|
||||||
#
|
#
|
||||||
# class RepositoryController < ActionController::Base
|
# class RepositoryController < ActionController::Base
|
||||||
# # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
|
# # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
|
||||||
# param_encoding :show, :file_path, Encoding::ASCII_8BIT
|
# param_encoding :show, :file_path, Encoding::ASCII_8BIT
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @repo = Repository.find_by_filesystem_path params[:file_path]
|
# @repo = Repository.find_by_filesystem_path params[:file_path]
|
||||||
#
|
#
|
||||||
# # params[:repo_name] remains UTF-8 encoded
|
# # params[:repo_name] remains UTF-8 encoded
|
||||||
# @repo_name = params[:repo_name]
|
# @repo_name = params[:repo_name]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def index
|
||||||
|
# @repositories = Repository.all
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def index
|
# The file_path parameter on the show action would be encoded as ASCII-8BIT, but
|
||||||
# @repositories = Repository.all
|
# all other arguments will remain UTF-8 encoded. This is useful in the case
|
||||||
# end
|
# where an application must handle data but encoding of the data is unknown,
|
||||||
# end
|
# like file system data.
|
||||||
#
|
|
||||||
# The file_path parameter on the show action would be encoded as ASCII-8BIT,
|
|
||||||
# but all other arguments will remain UTF-8 encoded.
|
|
||||||
# This is useful in the case where an application must handle data
|
|
||||||
# but encoding of the data is unknown, like file system data.
|
|
||||||
def param_encoding(action, param, encoding)
|
def param_encoding(action, param, encoding)
|
||||||
@_parameter_encodings[action.to_s][param.to_s] = encoding
|
@_parameter_encodings[action.to_s][param.to_s] = encoding
|
||||||
end
|
end
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/hash/slice"
|
require "active_support/core_ext/hash/slice"
|
||||||
require "active_support/core_ext/hash/except"
|
require "active_support/core_ext/hash/except"
|
||||||
require "active_support/core_ext/module/anonymous"
|
require "active_support/core_ext/module/anonymous"
|
||||||
require "action_dispatch/http/mime_type"
|
require "action_dispatch/http/mime_type"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller Params Wrapper
|
# # Action Controller Params Wrapper
|
||||||
#
|
#
|
||||||
# Wraps the parameters hash into a nested hash. This will allow clients to
|
# Wraps the parameters hash into a nested hash. This will allow clients to
|
||||||
# submit requests without having to specify any root elements.
|
# submit requests without having to specify any root elements.
|
||||||
@ -24,8 +26,8 @@ module ActionController
|
|||||||
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
# wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
|
# If you enable `ParamsWrapper` for `:json` format, instead of having to send
|
||||||
# send JSON parameters like this:
|
# JSON parameters like this:
|
||||||
#
|
#
|
||||||
# {"user": {"name": "Konata"}}
|
# {"user": {"name": "Konata"}}
|
||||||
#
|
#
|
||||||
@ -34,45 +36,44 @@ module ActionController
|
|||||||
# {"name": "Konata"}
|
# {"name": "Konata"}
|
||||||
#
|
#
|
||||||
# And it will be wrapped into a nested hash with the key name matching the
|
# And it will be wrapped into a nested hash with the key name matching the
|
||||||
# controller's name. For example, if you're posting to +UsersController+,
|
# controller's name. For example, if you're posting to `UsersController`, your
|
||||||
# your new +params+ hash will look like this:
|
# new `params` hash will look like this:
|
||||||
#
|
#
|
||||||
# {"name" => "Konata", "user" => {"name" => "Konata"}}
|
# {"name" => "Konata", "user" => {"name" => "Konata"}}
|
||||||
#
|
#
|
||||||
# You can also specify the key in which the parameters should be wrapped to,
|
# You can also specify the key in which the parameters should be wrapped to, and
|
||||||
# and also the list of attributes it should wrap by using either +:include+ or
|
# also the list of attributes it should wrap by using either `:include` or
|
||||||
# +:exclude+ options like this:
|
# `:exclude` options like this:
|
||||||
#
|
#
|
||||||
# class UsersController < ApplicationController
|
# class UsersController < ApplicationController
|
||||||
# wrap_parameters :person, include: [:username, :password]
|
# wrap_parameters :person, include: [:username, :password]
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# On Active Record models with no +:include+ or +:exclude+ option set,
|
# On Active Record models with no `:include` or `:exclude` option set, it will
|
||||||
# it will only wrap the parameters returned by the class method
|
# only wrap the parameters returned by the class method `attribute_names`.
|
||||||
# <tt>attribute_names</tt>.
|
|
||||||
#
|
#
|
||||||
# If you're going to pass the parameters to an +ActiveModel+ object (such as
|
# If you're going to pass the parameters to an `ActiveModel` object (such as
|
||||||
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to
|
# `User.new(params[:user])`), you might consider passing the model class to the
|
||||||
# the method instead. The +ParamsWrapper+ will actually try to determine the
|
# method instead. The `ParamsWrapper` will actually try to determine the list of
|
||||||
# list of attribute names from the model and only wrap those attributes:
|
# attribute names from the model and only wrap those attributes:
|
||||||
#
|
#
|
||||||
# class UsersController < ApplicationController
|
# class UsersController < ApplicationController
|
||||||
# wrap_parameters Person
|
# wrap_parameters Person
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# You still could pass +:include+ and +:exclude+ to set the list of attributes
|
# You still could pass `:include` and `:exclude` to set the list of attributes
|
||||||
# you want to wrap.
|
# you want to wrap.
|
||||||
#
|
#
|
||||||
# By default, if you don't specify the key in which the parameters would be
|
# By default, if you don't specify the key in which the parameters would be
|
||||||
# wrapped to, +ParamsWrapper+ will actually try to determine if there's
|
# wrapped to, `ParamsWrapper` will actually try to determine if there's a model
|
||||||
# a model related to it or not. This controller, for example:
|
# related to it or not. This controller, for example:
|
||||||
#
|
#
|
||||||
# class Admin::UsersController < ApplicationController
|
# class Admin::UsersController < ApplicationController
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# will try to check if +Admin::User+ or +User+ model exists, and use it to
|
# will try to check if `Admin::User` or `User` model exists, and use it to
|
||||||
# determine the wrapper key respectively. If both models don't exist,
|
# determine the wrapper key respectively. If both models don't exist, it will
|
||||||
# it will then fall back to use +user+ as the key.
|
# then fall back to use `user` as the key.
|
||||||
#
|
#
|
||||||
# To disable this functionality for a controller:
|
# To disable this functionality for a controller:
|
||||||
#
|
#
|
||||||
@ -154,13 +155,13 @@ def name
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Determine the wrapper model from the controller's name. By convention,
|
# Determine the wrapper model from the controller's name. By convention, this
|
||||||
# this could be done by trying to find the defined model that has the
|
# could be done by trying to find the defined model that has the same singular
|
||||||
# same singular name as the controller. For example, +UsersController+
|
# name as the controller. For example, `UsersController` will try to find if the
|
||||||
# will try to find if the +User+ model exists.
|
# `User` model exists.
|
||||||
#
|
#
|
||||||
# This method also does namespace lookup. Foo::Bar::UsersController will
|
# This method also does namespace lookup. Foo::Bar::UsersController will try to
|
||||||
# try to find Foo::Bar::User, Foo::User and finally User.
|
# find Foo::Bar::User, Foo::User and finally User.
|
||||||
def _default_wrap_model
|
def _default_wrap_model
|
||||||
return nil if klass.anonymous?
|
return nil if klass.anonymous?
|
||||||
model_name = klass.name.delete_suffix("Controller").classify
|
model_name = klass.name.delete_suffix("Controller").classify
|
||||||
@ -189,33 +190,34 @@ def _set_wrapper_options(options)
|
|||||||
self._wrapper_options = Options.from_hash(options)
|
self._wrapper_options = Options.from_hash(options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
|
# Sets the name of the wrapper key, or the model which `ParamsWrapper` would use
|
||||||
# would use to determine the attribute names from.
|
# to determine the attribute names from.
|
||||||
#
|
#
|
||||||
# ==== Examples
|
# #### Examples
|
||||||
# wrap_parameters format: :xml
|
# wrap_parameters format: :xml
|
||||||
# # enables the parameter wrapper for XML format
|
# # enables the parameter wrapper for XML format
|
||||||
#
|
#
|
||||||
# wrap_parameters :person
|
# wrap_parameters :person
|
||||||
# # wraps parameters into +params[:person]+ hash
|
# # wraps parameters into +params[:person]+ hash
|
||||||
#
|
#
|
||||||
# wrap_parameters Person
|
# wrap_parameters Person
|
||||||
# # wraps parameters by determining the wrapper key from Person class
|
# # wraps parameters by determining the wrapper key from Person class
|
||||||
# # (+person+, in this case) and the list of attribute names
|
# # (+person+, in this case) and the list of attribute names
|
||||||
#
|
#
|
||||||
# wrap_parameters include: [:username, :title]
|
# wrap_parameters include: [:username, :title]
|
||||||
# # wraps only +:username+ and +:title+ attributes from parameters.
|
# # wraps only +:username+ and +:title+ attributes from parameters.
|
||||||
#
|
#
|
||||||
# wrap_parameters false
|
# wrap_parameters false
|
||||||
# # disables parameters wrapping for this controller altogether.
|
# # disables parameters wrapping for this controller altogether.
|
||||||
|
#
|
||||||
|
# #### Options
|
||||||
|
# * `:format` - The list of formats in which the parameters wrapper will be
|
||||||
|
# enabled.
|
||||||
|
# * `:include` - The list of attribute names which parameters wrapper will
|
||||||
|
# wrap into a nested hash.
|
||||||
|
# * `:exclude` - The list of attribute names which parameters wrapper will
|
||||||
|
# exclude from a nested hash.
|
||||||
#
|
#
|
||||||
# ==== Options
|
|
||||||
# * <tt>:format</tt> - The list of formats in which the parameters wrapper
|
|
||||||
# will be enabled.
|
|
||||||
# * <tt>:include</tt> - The list of attribute names which parameters wrapper
|
|
||||||
# will wrap into a nested hash.
|
|
||||||
# * <tt>:exclude</tt> - The list of attribute names which parameters wrapper
|
|
||||||
# will exclude from a nested hash.
|
|
||||||
def wrap_parameters(name_or_model_or_options, options = {})
|
def wrap_parameters(name_or_model_or_options, options = {})
|
||||||
model = nil
|
model = nil
|
||||||
|
|
||||||
@ -237,9 +239,8 @@ def wrap_parameters(name_or_model_or_options, options = {})
|
|||||||
self._wrapper_options = opts
|
self._wrapper_options = opts
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the default wrapper key or model which will be used to determine
|
# Sets the default wrapper key or model which will be used to determine wrapper
|
||||||
# wrapper key and attribute names. Called automatically when the
|
# key and attribute names. Called automatically when the module is inherited.
|
||||||
# module is inherited.
|
|
||||||
def inherited(klass)
|
def inherited(klass)
|
||||||
if klass._wrapper_options.format.any?
|
if klass._wrapper_options.format.any?
|
||||||
params = klass._wrapper_options.dup
|
params = klass._wrapper_options.dup
|
||||||
@ -251,8 +252,8 @@ def inherited(klass)
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Performs parameters wrapping upon the request. Called automatically
|
# Performs parameters wrapping upon the request. Called automatically by the
|
||||||
# by the metal call stack.
|
# metal call stack.
|
||||||
def process_action(*)
|
def process_action(*)
|
||||||
_perform_parameter_wrapping if _wrapper_enabled?
|
_perform_parameter_wrapping if _wrapper_enabled?
|
||||||
super
|
super
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module PermissionsPolicy
|
module PermissionsPolicy
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Overrides parts of the globally configured +Feature-Policy+
|
# Overrides parts of the globally configured `Feature-Policy` header:
|
||||||
# header:
|
|
||||||
#
|
#
|
||||||
# class PagesController < ApplicationController
|
# class PagesController < ApplicationController
|
||||||
# permissions_policy do |policy|
|
# permissions_policy do |policy|
|
||||||
# policy.geolocation "https://example.com"
|
# policy.geolocation "https://example.com"
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# Options can be passed similar to +before_action+. For example, pass
|
# Options can be passed similar to `before_action`. For example, pass `only:
|
||||||
# <tt>only: :index</tt> to override the header on the index action only:
|
# :index` to override the header on the index action only:
|
||||||
#
|
#
|
||||||
# class PagesController < ApplicationController
|
# class PagesController < ApplicationController
|
||||||
# permissions_policy(only: :index) do |policy|
|
# permissions_policy(only: :index) do |policy|
|
||||||
# policy.camera :self
|
# policy.camera :self
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
def permissions_policy(**options, &block)
|
def permissions_policy(**options, &block)
|
||||||
before_action(options) do
|
before_action(options) do
|
||||||
|
@ -1,39 +1,49 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
module RateLimiting
|
module RateLimiting
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Applies a rate limit to all actions or those specified by the normal <tt>before_action</tt> filters with <tt>only:</tt> and <tt>except:</tt>.
|
# Applies a rate limit to all actions or those specified by the normal
|
||||||
|
# `before_action` filters with `only:` and `except:`.
|
||||||
#
|
#
|
||||||
# The maximum number of requests allowed is specified <tt>to:</tt> and constrained to the window of time given by <tt>within:</tt>.
|
# The maximum number of requests allowed is specified `to:` and constrained to
|
||||||
|
# the window of time given by `within:`.
|
||||||
#
|
#
|
||||||
# Rate limits are by default unique to the ip address making the request, but you can provide your own identity function by passing a callable
|
# Rate limits are by default unique to the ip address making the request, but
|
||||||
# in the <tt>by:</tt> parameter. It's evaluated within the context of the controller processing the request.
|
# you can provide your own identity function by passing a callable in the `by:`
|
||||||
|
# parameter. It's evaluated within the context of the controller processing the
|
||||||
|
# request.
|
||||||
#
|
#
|
||||||
# Requests that exceed the rate limit are refused with a <tt>429 Too Many Requests</tt> response. You can specialize this by passing a callable
|
# Requests that exceed the rate limit are refused with a `429 Too Many Requests`
|
||||||
# in the <tt>with:</tt> parameter. It's evaluated within the context of the controller processing the request.
|
# response. You can specialize this by passing a callable in the `with:`
|
||||||
|
# parameter. It's evaluated within the context of the controller processing the
|
||||||
|
# request.
|
||||||
#
|
#
|
||||||
# Rate limiting relies on a backing <tt>ActiveSupport::Cache</tt> store and defaults to <tt>config.action_controller.cache_store</tt>, which
|
# Rate limiting relies on a backing `ActiveSupport::Cache` store and defaults to
|
||||||
# itself defaults to the global <tt>config.cache_store</tt>. If you don't want to store rate limits in the same datastore as your general caches,
|
# `config.action_controller.cache_store`, which itself defaults to the global
|
||||||
# you can pass a custom store in the <tt>store</tt> parameter.
|
# `config.cache_store`. If you don't want to store rate limits in the same
|
||||||
|
# datastore as your general caches, you can pass a custom store in the `store`
|
||||||
|
# parameter.
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
#
|
#
|
||||||
# class SessionsController < ApplicationController
|
# class SessionsController < ApplicationController
|
||||||
# rate_limit to: 10, within: 3.minutes, only: :create
|
# rate_limit to: 10, within: 3.minutes, only: :create
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class SignupsController < ApplicationController
|
# class SignupsController < ApplicationController
|
||||||
# rate_limit to: 1000, within: 10.seconds,
|
# rate_limit to: 1000, within: 10.seconds,
|
||||||
# by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new
|
# by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups on domain!" }, only: :new
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class APIController < ApplicationController
|
# class APIController < ApplicationController
|
||||||
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
|
# RATE_LIMIT_STORE = ActiveSupport::Cache::RedisCacheStore.new(url: ENV["REDIS_URL"])
|
||||||
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
|
# rate_limit to: 10, within: 3.minutes, store: RATE_LIMIT_STORE
|
||||||
# end
|
# end
|
||||||
def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options)
|
def rate_limit(to:, within:, by: -> { request.remote_ip }, with: -> { head :too_many_requests }, store: cache_store, **options)
|
||||||
before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options
|
before_action -> { rate_limiting(to: to, within: within, by: by, with: with, store: store) }, **options
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Redirecting
|
module Redirecting
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -15,72 +17,89 @@ class UnsafeRedirectError < StandardError; end
|
|||||||
mattr_accessor :raise_on_open_redirects, default: false
|
mattr_accessor :raise_on_open_redirects, default: false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Redirects the browser to the target specified in +options+. This parameter can be any one of:
|
# Redirects the browser to the target specified in `options`. This parameter can
|
||||||
|
# be any one of:
|
||||||
#
|
#
|
||||||
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
|
# * `Hash` - The URL will be generated by calling url_for with the `options`.
|
||||||
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
|
# * `Record` - The URL will be generated by calling url_for with the
|
||||||
# * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
|
# `options`, which will reference a named URL for that record.
|
||||||
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
|
# * `String` starting with `protocol://` (like `http://`) or a protocol
|
||||||
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
|
# relative reference (like `//`) - Is passed straight through as the target
|
||||||
|
# for redirection.
|
||||||
|
# * `String` not containing a protocol - The current protocol and host is
|
||||||
|
# prepended to the string.
|
||||||
|
# * `Proc` - A block that will be executed in the controller's context. Should
|
||||||
|
# return any option accepted by `redirect_to`.
|
||||||
#
|
#
|
||||||
# === Examples
|
|
||||||
#
|
#
|
||||||
# redirect_to action: "show", id: 5
|
# ### Examples
|
||||||
# redirect_to @post
|
|
||||||
# redirect_to "http://www.rubyonrails.org"
|
|
||||||
# redirect_to "/images/screenshot.jpg"
|
|
||||||
# redirect_to posts_url
|
|
||||||
# redirect_to proc { edit_post_url(@post) }
|
|
||||||
#
|
#
|
||||||
# The redirection happens as a <tt>302 Found</tt> header unless otherwise specified using the <tt>:status</tt> option:
|
# redirect_to action: "show", id: 5
|
||||||
|
# redirect_to @post
|
||||||
|
# redirect_to "http://www.rubyonrails.org"
|
||||||
|
# redirect_to "/images/screenshot.jpg"
|
||||||
|
# redirect_to posts_url
|
||||||
|
# redirect_to proc { edit_post_url(@post) }
|
||||||
#
|
#
|
||||||
# redirect_to post_url(@post), status: :found
|
# The redirection happens as a `302 Found` header unless otherwise specified
|
||||||
# redirect_to action: 'atom', status: :moved_permanently
|
# using the `:status` option:
|
||||||
# redirect_to post_url(@post), status: 301
|
|
||||||
# redirect_to action: 'atom', status: 302
|
|
||||||
#
|
#
|
||||||
# The status code can either be a standard {HTTP Status code}[https://www.iana.org/assignments/http-status-codes] as an
|
# redirect_to post_url(@post), status: :found
|
||||||
# integer, or a symbol representing the downcased, underscored and symbolized description.
|
# redirect_to action: 'atom', status: :moved_permanently
|
||||||
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
|
# redirect_to post_url(@post), status: 301
|
||||||
|
# redirect_to action: 'atom', status: 302
|
||||||
|
#
|
||||||
|
# The status code can either be a standard [HTTP Status
|
||||||
|
# code](https://www.iana.org/assignments/http-status-codes) as an integer, or a
|
||||||
|
# symbol representing the downcased, underscored and symbolized description.
|
||||||
|
# Note that the status code must be a 3xx HTTP code, or redirection will not
|
||||||
|
# occur.
|
||||||
#
|
#
|
||||||
# If you are using XHR requests other than GET or POST and redirecting after the
|
# If you are using XHR requests other than GET or POST and redirecting after the
|
||||||
# request then some browsers will follow the redirect using the original request
|
# request then some browsers will follow the redirect using the original request
|
||||||
# method. This may lead to undesirable behavior such as a double DELETE. To work
|
# method. This may lead to undesirable behavior such as a double DELETE. To work
|
||||||
# around this you can return a <tt>303 See Other</tt> status code which will be
|
# around this you can return a `303 See Other` status code which will be
|
||||||
# followed using a GET request.
|
# followed using a GET request.
|
||||||
#
|
#
|
||||||
# redirect_to posts_url, status: :see_other
|
# redirect_to posts_url, status: :see_other
|
||||||
# redirect_to action: 'index', status: 303
|
# redirect_to action: 'index', status: 303
|
||||||
#
|
#
|
||||||
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
|
# It is also possible to assign a flash message as part of the redirection.
|
||||||
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
|
# There are two special accessors for the commonly used flash names `alert` and
|
||||||
|
# `notice` as well as a general purpose `flash` bucket.
|
||||||
#
|
#
|
||||||
# redirect_to post_url(@post), alert: "Watch it, mister!"
|
# redirect_to post_url(@post), alert: "Watch it, mister!"
|
||||||
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
|
# redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
|
||||||
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
|
||||||
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
|
||||||
#
|
#
|
||||||
# Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function.
|
# Statements after `redirect_to` in our controller get executed, so
|
||||||
# To terminate the execution of the function immediately after the +redirect_to+, use return.
|
# `redirect_to` doesn't stop the execution of the function. To terminate the
|
||||||
|
# execution of the function immediately after the `redirect_to`, use return.
|
||||||
#
|
#
|
||||||
# redirect_to post_url(@post) and return
|
# redirect_to post_url(@post) and return
|
||||||
#
|
#
|
||||||
# === Open Redirect protection
|
# ### Open Redirect protection
|
||||||
#
|
#
|
||||||
# By default, \Rails protects against redirecting to external hosts for your app's safety, so called open redirects.
|
# By default, Rails protects against redirecting to external hosts for your
|
||||||
# Note: this was a new default in \Rails 7.0, after upgrading opt-in by uncommenting the line with +raise_on_open_redirects+ in <tt>config/initializers/new_framework_defaults_7_0.rb</tt>
|
# app's safety, so called open redirects. Note: this was a new default in Rails
|
||||||
|
# 7.0, after upgrading opt-in by uncommenting the line with
|
||||||
|
# `raise_on_open_redirects` in
|
||||||
|
# `config/initializers/new_framework_defaults_7_0.rb`
|
||||||
#
|
#
|
||||||
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
# Here #redirect_to automatically validates the potentially-unsafe URL:
|
||||||
#
|
#
|
||||||
# redirect_to params[:redirect_url]
|
# redirect_to params[:redirect_url]
|
||||||
#
|
#
|
||||||
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
# Raises UnsafeRedirectError in the case of an unsafe redirect.
|
||||||
#
|
#
|
||||||
# To allow any external redirects pass <tt>allow_other_host: true</tt>, though using a user-provided param in that case is unsafe.
|
# To allow any external redirects pass `allow_other_host: true`, though using a
|
||||||
|
# user-provided param in that case is unsafe.
|
||||||
#
|
#
|
||||||
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
# redirect_to "https://rubyonrails.org", allow_other_host: true
|
||||||
#
|
#
|
||||||
# See #url_from for more information on what an internal and safe URL is, or how to fall back to an alternate redirect URL in the unsafe case.
|
# See #url_from for more information on what an internal and safe URL is, or how
|
||||||
|
# to fall back to an alternate redirect URL in the unsafe case.
|
||||||
def redirect_to(options = {}, response_options = {})
|
def redirect_to(options = {}, response_options = {})
|
||||||
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
||||||
raise AbstractController::DoubleRenderError if response_body
|
raise AbstractController::DoubleRenderError if response_body
|
||||||
@ -96,50 +115,53 @@ def redirect_to(options = {}, response_options = {})
|
|||||||
self.response_body = ""
|
self.response_body = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
# Soft deprecated alias for #redirect_back_or_to where the +fallback_location+ location is supplied as a keyword argument instead
|
# Soft deprecated alias for #redirect_back_or_to where the `fallback_location`
|
||||||
# of the first positional argument.
|
# location is supplied as a keyword argument instead of the first positional
|
||||||
|
# argument.
|
||||||
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
def redirect_back(fallback_location:, allow_other_host: _allow_other_host, **args)
|
||||||
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
redirect_back_or_to fallback_location, allow_other_host: allow_other_host, **args
|
||||||
end
|
end
|
||||||
|
|
||||||
# Redirects the browser to the page that issued the request (the referrer)
|
# Redirects the browser to the page that issued the request (the referrer) if
|
||||||
# if possible, otherwise redirects to the provided default fallback
|
# possible, otherwise redirects to the provided default fallback location.
|
||||||
# location.
|
|
||||||
#
|
#
|
||||||
# The referrer information is pulled from the HTTP +Referer+ (sic) header on
|
# The referrer information is pulled from the HTTP `Referer` (sic) header on the
|
||||||
# the request. This is an optional header and its presence on the request is
|
# request. This is an optional header and its presence on the request is subject
|
||||||
# subject to browser security settings and user preferences. If the request
|
# to browser security settings and user preferences. If the request is missing
|
||||||
# is missing this header, the <tt>fallback_location</tt> will be used.
|
# this header, the `fallback_location` will be used.
|
||||||
#
|
#
|
||||||
# redirect_back_or_to({ action: "show", id: 5 })
|
# redirect_back_or_to({ action: "show", id: 5 })
|
||||||
# redirect_back_or_to @post
|
# redirect_back_or_to @post
|
||||||
# redirect_back_or_to "http://www.rubyonrails.org"
|
# redirect_back_or_to "http://www.rubyonrails.org"
|
||||||
# redirect_back_or_to "/images/screenshot.jpg"
|
# redirect_back_or_to "/images/screenshot.jpg"
|
||||||
# redirect_back_or_to posts_url
|
# redirect_back_or_to posts_url
|
||||||
# redirect_back_or_to proc { edit_post_url(@post) }
|
# redirect_back_or_to proc { edit_post_url(@post) }
|
||||||
# redirect_back_or_to '/', allow_other_host: false
|
# redirect_back_or_to '/', allow_other_host: false
|
||||||
#
|
#
|
||||||
# ==== Options
|
# #### Options
|
||||||
# * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
|
# * `:allow_other_host` - Allow or disallow redirection to the host that is
|
||||||
|
# different to the current host, defaults to true.
|
||||||
#
|
#
|
||||||
# All other options that can be passed to #redirect_to are accepted as
|
#
|
||||||
# options, and the behavior is identical.
|
# All other options that can be passed to #redirect_to are accepted as options,
|
||||||
|
# and the behavior is identical.
|
||||||
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
def redirect_back_or_to(fallback_location, allow_other_host: _allow_other_host, **options)
|
||||||
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
if request.referer && (allow_other_host || _url_host_allowed?(request.referer))
|
||||||
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
redirect_to request.referer, allow_other_host: allow_other_host, **options
|
||||||
else
|
else
|
||||||
# The method level `allow_other_host` doesn't apply in the fallback case, omit and let the `redirect_to` handling take over.
|
# The method level `allow_other_host` doesn't apply in the fallback case, omit
|
||||||
|
# and let the `redirect_to` handling take over.
|
||||||
redirect_to fallback_location, **options
|
redirect_to fallback_location, **options
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def _compute_redirect_to_location(request, options) # :nodoc:
|
def _compute_redirect_to_location(request, options) # :nodoc:
|
||||||
case options
|
case options
|
||||||
# The scheme name consist of a letter followed by any combination of
|
# The scheme name consist of a letter followed by any combination of letters,
|
||||||
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
|
# digits, and the plus ("+"), period ("."), or hyphen ("-") characters; and is
|
||||||
# characters; and is terminated by a colon (":").
|
# terminated by a colon (":"). See
|
||||||
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
# https://tools.ietf.org/html/rfc3986#section-3.1 The protocol relative scheme
|
||||||
# The protocol relative scheme starts with a double slash "//".
|
# starts with a double slash "//".
|
||||||
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
when /\A([a-z][a-z\d\-+.]*:|\/\/).*/i
|
||||||
options.to_str
|
options.to_str
|
||||||
when String
|
when String
|
||||||
@ -153,25 +175,30 @@ def _compute_redirect_to_location(request, options) # :nodoc:
|
|||||||
module_function :_compute_redirect_to_location
|
module_function :_compute_redirect_to_location
|
||||||
public :_compute_redirect_to_location
|
public :_compute_redirect_to_location
|
||||||
|
|
||||||
# Verifies the passed +location+ is an internal URL that's safe to redirect to and returns it, or nil if not.
|
# Verifies the passed `location` is an internal URL that's safe to redirect to
|
||||||
# Useful to wrap a params provided redirect URL and fall back to an alternate URL to redirect to:
|
# and returns it, or nil if not. Useful to wrap a params provided redirect URL
|
||||||
|
# and fall back to an alternate URL to redirect to:
|
||||||
#
|
#
|
||||||
# redirect_to url_from(params[:redirect_url]) || root_url
|
# redirect_to url_from(params[:redirect_url]) || root_url
|
||||||
#
|
#
|
||||||
# The +location+ is considered internal, and safe, if it's on the same host as <tt>request.host</tt>:
|
# The `location` is considered internal, and safe, if it's on the same host as
|
||||||
|
# `request.host`:
|
||||||
#
|
#
|
||||||
# # If request.host is example.com:
|
# # If request.host is example.com:
|
||||||
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
# url_from("https://example.com/profile") # => "https://example.com/profile"
|
||||||
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
# url_from("http://example.com/profile") # => "http://example.com/profile"
|
||||||
# url_from("http://evil.com/profile") # => nil
|
# url_from("http://evil.com/profile") # => nil
|
||||||
#
|
#
|
||||||
# Subdomains are considered part of the host:
|
# Subdomains are considered part of the host:
|
||||||
#
|
#
|
||||||
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
# # If request.host is on https://example.com or https://app.example.com, you'd get:
|
||||||
# url_from("https://dev.example.com/profile") # => nil
|
# url_from("https://dev.example.com/profile") # => nil
|
||||||
#
|
#
|
||||||
# NOTE: there's a similarity with {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor#url_for], which generates an internal URL from various options from within the app, e.g. <tt>url_for(@post)</tt>.
|
# NOTE: there's a similarity with
|
||||||
# However, #url_from is meant to take an external parameter to verify as in <tt>url_from(params[:redirect_url])</tt>.
|
# [url_for](rdoc-ref:ActionDispatch::Routing::UrlFor#url_for), which generates
|
||||||
|
# an internal URL from various options from within the app, e.g.
|
||||||
|
# `url_for(@post)`. However, #url_from is meant to take an external parameter to
|
||||||
|
# verify as in `url_from(params[:redirect_url])`.
|
||||||
def url_from(location)
|
def url_from(location)
|
||||||
location = location.presence
|
location = location.presence
|
||||||
location if location && _url_host_allowed?(location)
|
location if location && _url_host_allowed?(location)
|
||||||
@ -212,9 +239,8 @@ def _url_host_allowed?(url)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def _ensure_url_is_http_header_safe(url)
|
def _ensure_url_is_http_header_safe(url)
|
||||||
# Attempt to comply with the set of valid token characters
|
# Attempt to comply with the set of valid token characters defined for an HTTP
|
||||||
# defined for an HTTP header value in
|
# header value in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
||||||
# https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
|
||||||
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
|
if url.match?(ILLEGAL_HEADER_VALUE_REGEX)
|
||||||
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
msg = "The redirect URL #{url} contains one or more illegal HTTP header field character. " \
|
||||||
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|
"Set of legal characters defined in https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "set"
|
require "set"
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
@ -13,7 +15,7 @@ def self.remove_renderer(key)
|
|||||||
Renderers.remove(key)
|
Renderers.remove(key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# See <tt>Responder#api_behavior</tt>
|
# See `Responder#api_behavior`
|
||||||
class MissingRenderer < LoadError
|
class MissingRenderer < LoadError
|
||||||
def initialize(format)
|
def initialize(format)
|
||||||
super "No renderer defined for format: #{format}"
|
super "No renderer defined for format: #{format}"
|
||||||
@ -24,7 +26,7 @@ module Renderers
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
# A Set containing renderer names that correspond to available renderer procs.
|
# A Set containing renderer names that correspond to available renderer procs.
|
||||||
# Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
|
# Default values are `:json`, `:js`, `:xml`.
|
||||||
RENDERERS = Set.new
|
RENDERERS = Set.new
|
||||||
|
|
||||||
included do
|
included do
|
||||||
@ -42,35 +44,34 @@ module All
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a new renderer to call within controller actions.
|
# Adds a new renderer to call within controller actions. A renderer is invoked
|
||||||
# A renderer is invoked by passing its name as an option to
|
# by passing its name as an option to AbstractController::Rendering#render. To
|
||||||
# AbstractController::Rendering#render. To create a renderer
|
# create a renderer pass it a name and a block. The block takes two arguments,
|
||||||
# pass it a name and a block. The block takes two arguments, the first
|
# the first is the value paired with its key and the second is the remaining
|
||||||
# is the value paired with its key and the second is the remaining
|
# hash of options passed to `render`.
|
||||||
# hash of options passed to +render+.
|
|
||||||
#
|
#
|
||||||
# Create a csv renderer:
|
# Create a csv renderer:
|
||||||
#
|
#
|
||||||
# ActionController::Renderers.add :csv do |obj, options|
|
# ActionController::Renderers.add :csv do |obj, options|
|
||||||
# filename = options[:filename] || 'data'
|
# filename = options[:filename] || 'data'
|
||||||
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
|
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
|
||||||
# send_data str, type: Mime[:csv],
|
# send_data str, type: Mime[:csv],
|
||||||
# disposition: "attachment; filename=#{filename}.csv"
|
# disposition: "attachment; filename=#{filename}.csv"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Note that we used Mime[:csv] for the csv mime type as it comes with \Rails.
|
# Note that we used [Mime](:csv) for the csv mime type as it comes with Rails.
|
||||||
# For a custom renderer, you'll need to register a mime type with
|
# For a custom renderer, you'll need to register a mime type with
|
||||||
# <tt>Mime::Type.register</tt>.
|
# `Mime::Type.register`.
|
||||||
#
|
#
|
||||||
# To use the csv renderer in a controller action:
|
# To use the csv renderer in a controller action:
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# @csvable = Csvable.find(params[:id])
|
# @csvable = Csvable.find(params[:id])
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html
|
# format.html
|
||||||
# format.csv { render csv: @csvable, filename: @csvable.name }
|
# format.csv { render csv: @csvable, filename: @csvable.name }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
def self.add(key, &block)
|
def self.add(key, &block)
|
||||||
define_method(_render_with_renderer_method_name(key), &block)
|
define_method(_render_with_renderer_method_name(key), &block)
|
||||||
RENDERERS << key.to_sym
|
RENDERERS << key.to_sym
|
||||||
@ -80,7 +81,7 @@ def self.add(key, &block)
|
|||||||
#
|
#
|
||||||
# To remove a csv renderer:
|
# To remove a csv renderer:
|
||||||
#
|
#
|
||||||
# ActionController::Renderers.remove(:csv)
|
# ActionController::Renderers.remove(:csv)
|
||||||
def self.remove(key)
|
def self.remove(key)
|
||||||
RENDERERS.delete(key.to_sym)
|
RENDERERS.delete(key.to_sym)
|
||||||
method_name = _render_with_renderer_method_name(key)
|
method_name = _render_with_renderer_method_name(key)
|
||||||
@ -92,39 +93,39 @@ def self._render_with_renderer_method_name(key)
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Adds, by name, a renderer or renderers to the +_renderers+ available
|
# Adds, by name, a renderer or renderers to the `_renderers` available to call
|
||||||
# to call within controller actions.
|
# within controller actions.
|
||||||
#
|
#
|
||||||
# It is useful when rendering from an ActionController::Metal controller or
|
# It is useful when rendering from an ActionController::Metal controller or
|
||||||
# otherwise to add an available renderer proc to a specific controller.
|
# otherwise to add an available renderer proc to a specific controller.
|
||||||
#
|
#
|
||||||
# Both ActionController::Base and ActionController::API
|
# Both ActionController::Base and ActionController::API include
|
||||||
# include ActionController::Renderers::All, making all renderers
|
# ActionController::Renderers::All, making all renderers available in the
|
||||||
# available in the controller. See Renderers::RENDERERS and Renderers.add.
|
# controller. See Renderers::RENDERERS and Renderers.add.
|
||||||
#
|
#
|
||||||
# Since ActionController::Metal controllers cannot render, the controller
|
# Since ActionController::Metal controllers cannot render, the controller must
|
||||||
# must include AbstractController::Rendering, ActionController::Rendering,
|
# include AbstractController::Rendering, ActionController::Rendering, and
|
||||||
# and ActionController::Renderers, and have at least one renderer.
|
# ActionController::Renderers, and have at least one renderer.
|
||||||
#
|
#
|
||||||
# Rather than including ActionController::Renderers::All and including all renderers,
|
# Rather than including ActionController::Renderers::All and including all
|
||||||
# you may specify which renderers to include by passing the renderer name or names to
|
# renderers, you may specify which renderers to include by passing the renderer
|
||||||
# +use_renderers+. For example, a controller that includes only the <tt>:json</tt> renderer
|
# name or names to `use_renderers`. For example, a controller that includes only
|
||||||
# (+_render_with_renderer_json+) might look like:
|
# the `:json` renderer (`_render_with_renderer_json`) might look like:
|
||||||
#
|
#
|
||||||
# class MetalRenderingController < ActionController::Metal
|
# class MetalRenderingController < ActionController::Metal
|
||||||
# include AbstractController::Rendering
|
# include AbstractController::Rendering
|
||||||
# include ActionController::Rendering
|
# include ActionController::Rendering
|
||||||
# include ActionController::Renderers
|
# include ActionController::Renderers
|
||||||
#
|
#
|
||||||
# use_renderers :json
|
# use_renderers :json
|
||||||
#
|
#
|
||||||
# def show
|
# def show
|
||||||
# render json: record
|
# render json: record
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# You must specify a +use_renderer+, else the +controller.renderer+ and
|
# You must specify a `use_renderer`, else the `controller.renderer` and
|
||||||
# +controller._renderers+ will be <tt>nil</tt>, and the action will fail.
|
# `controller._renderers` will be `nil`, and the action will fail.
|
||||||
def use_renderers(*args)
|
def use_renderers(*args)
|
||||||
renderers = _renderers + args
|
renderers = _renderers + args
|
||||||
self._renderers = renderers.freeze
|
self._renderers = renderers.freeze
|
||||||
@ -132,11 +133,11 @@ def use_renderers(*args)
|
|||||||
alias use_renderer use_renderers
|
alias use_renderer use_renderers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Called by +render+ in AbstractController::Rendering
|
# Called by `render` in AbstractController::Rendering which sets the return
|
||||||
# which sets the return value as the +response_body+.
|
# value as the `response_body`.
|
||||||
#
|
#
|
||||||
# If no renderer is found, +super+ returns control to
|
# If no renderer is found, `super` returns control to
|
||||||
# <tt>ActionView::Rendering.render_to_body</tt>, if present.
|
# `ActionView::Rendering.render_to_body`, if present.
|
||||||
def render_to_body(options)
|
def render_to_body(options)
|
||||||
_render_to_body_with_renderer(options) || super
|
_render_to_body_with_renderer(options) || super
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Rendering
|
module Rendering
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
@ -10,8 +12,8 @@ module ClassMethods
|
|||||||
# Documentation at ActionController::Renderer#render
|
# Documentation at ActionController::Renderer#render
|
||||||
delegate :render, to: :renderer
|
delegate :render, to: :renderer
|
||||||
|
|
||||||
# Returns a renderer instance (inherited from ActionController::Renderer)
|
# Returns a renderer instance (inherited from ActionController::Renderer) for
|
||||||
# for the controller.
|
# the controller.
|
||||||
attr_reader :renderer
|
attr_reader :renderer
|
||||||
|
|
||||||
def setup_renderer! # :nodoc:
|
def setup_renderer! # :nodoc:
|
||||||
@ -24,137 +26,139 @@ def inherited(klass)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Renders a template and assigns the result to +self.response_body+.
|
# Renders a template and assigns the result to `self.response_body`.
|
||||||
#
|
#
|
||||||
# If no rendering mode option is specified, the template will be derived
|
# If no rendering mode option is specified, the template will be derived from
|
||||||
# from the first argument.
|
# the first argument.
|
||||||
#
|
#
|
||||||
# render "posts/show"
|
# render "posts/show"
|
||||||
# # => renders app/views/posts/show.html.erb
|
# # => renders app/views/posts/show.html.erb
|
||||||
#
|
#
|
||||||
# # In a PostsController action...
|
# # In a PostsController action...
|
||||||
# render :show
|
# render :show
|
||||||
# # => renders app/views/posts/show.html.erb
|
# # => renders app/views/posts/show.html.erb
|
||||||
#
|
#
|
||||||
# If the first argument responds to +render_in+, the template will be
|
# If the first argument responds to `render_in`, the template will be rendered
|
||||||
# rendered by calling +render_in+ with the current view context.
|
# by calling `render_in` with the current view context.
|
||||||
#
|
#
|
||||||
# class Greeting
|
# class Greeting
|
||||||
# def render_in(view_context)
|
# def render_in(view_context)
|
||||||
# view_context.render html: "<h1>Hello, World</h1>"
|
# view_context.render html: "<h1>Hello, World</h1>"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def format
|
||||||
|
# :html
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def format
|
# render(Greeting.new)
|
||||||
# :html
|
# # => "<h1>Hello, World</h1>"
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# render(Greeting.new)
|
# render(renderable: Greeting.new)
|
||||||
# # => "<h1>Hello, World</h1>"
|
# # => "<h1>Hello, World</h1>"
|
||||||
#
|
#
|
||||||
# render(renderable: Greeting.new)
|
# #### Rendering Mode
|
||||||
# # => "<h1>Hello, World</h1>"
|
|
||||||
#
|
#
|
||||||
# ==== \Rendering Mode
|
# `:partial`
|
||||||
|
# : See ActionView::PartialRenderer for details.
|
||||||
#
|
#
|
||||||
# [+:partial+]
|
# render partial: "posts/form", locals: { post: Post.new }
|
||||||
# See ActionView::PartialRenderer for details.
|
# # => renders app/views/posts/_form.html.erb
|
||||||
#
|
#
|
||||||
# render partial: "posts/form", locals: { post: Post.new }
|
# `:file`
|
||||||
# # => renders app/views/posts/_form.html.erb
|
# : Renders the contents of a file. This option should **not** be used with
|
||||||
|
# unsanitized user input.
|
||||||
#
|
#
|
||||||
# [+:file+]
|
# render file: "/path/to/some/file"
|
||||||
# Renders the contents of a file. This option should <b>not</b> be used
|
# # => renders /path/to/some/file
|
||||||
# with unsanitized user input.
|
|
||||||
#
|
#
|
||||||
# render file: "/path/to/some/file"
|
# `:inline`
|
||||||
# # => renders /path/to/some/file
|
# : Renders an ERB template string.
|
||||||
#
|
#
|
||||||
# [+:inline+]
|
# @name = "World"
|
||||||
# Renders an ERB template string.
|
# render inline: "<h1>Hello, <%= @name %>!</h1>"
|
||||||
|
# # => renders "<h1>Hello, World!</h1>"
|
||||||
#
|
#
|
||||||
# @name = "World"
|
# `:body`
|
||||||
# render inline: "<h1>Hello, <%= @name %>!</h1>"
|
# : Renders the provided text, and sets the content type as `text/plain`.
|
||||||
# # => renders "<h1>Hello, World!</h1>"
|
|
||||||
#
|
#
|
||||||
# [+:body+]
|
# render body: "Hello, World!"
|
||||||
# Renders the provided text, and sets the content type as +text/plain+.
|
# # => renders "Hello, World!"
|
||||||
#
|
#
|
||||||
# render body: "Hello, World!"
|
# `:plain`
|
||||||
# # => renders "Hello, World!"
|
# : Renders the provided text, and sets the content type as `text/plain`.
|
||||||
#
|
#
|
||||||
# [+:plain+]
|
# render plain: "Hello, World!"
|
||||||
# Renders the provided text, and sets the content type as +text/plain+.
|
# # => renders "Hello, World!"
|
||||||
#
|
#
|
||||||
# render plain: "Hello, World!"
|
# `:html`
|
||||||
# # => renders "Hello, World!"
|
# : Renders the provided HTML string, and sets the content type as
|
||||||
|
# `text/html`. If the string is not `html_safe?`, performs HTML escaping on
|
||||||
|
# the string before rendering.
|
||||||
#
|
#
|
||||||
# [+:html+]
|
# render html: "<h1>Hello, World!</h1>".html_safe
|
||||||
# Renders the provided HTML string, and sets the content type as +text/html+.
|
# # => renders "<h1>Hello, World!</h1>"
|
||||||
# If the string is not +html_safe?+, performs HTML escaping on the string
|
|
||||||
# before rendering.
|
|
||||||
#
|
#
|
||||||
# render html: "<h1>Hello, World!</h1>".html_safe
|
# render html: "<h1>Hello, World!</h1>"
|
||||||
# # => renders "<h1>Hello, World!</h1>"
|
# # => renders "<h1>Hello, World!</h1>"
|
||||||
#
|
#
|
||||||
# render html: "<h1>Hello, World!</h1>"
|
# `:json`
|
||||||
# # => renders "<h1>Hello, World!</h1>"
|
# : Renders the provided object as JSON, and sets the content type as
|
||||||
|
# `application/json`. If the object is not a string, it will be converted to
|
||||||
|
# JSON by calling `to_json`.
|
||||||
#
|
#
|
||||||
# [+:json+]
|
# render json: { hello: "world" }
|
||||||
# Renders the provided object as JSON, and sets the content type as
|
# # => renders "{\"hello\":\"world\"}"
|
||||||
# +application/json+. If the object is not a string, it will be converted
|
|
||||||
# to JSON by calling +to_json+.
|
|
||||||
#
|
#
|
||||||
# render json: { hello: "world" }
|
# `:renderable`
|
||||||
# # => renders "{\"hello\":\"world\"}"
|
# : Renders the provided object by calling `render_in` with the current view
|
||||||
|
# context. The response format is determined by calling `format` on the
|
||||||
|
# renderable if it responds to `format`, falling back to `text/html` by
|
||||||
|
# default.
|
||||||
#
|
#
|
||||||
# [+:renderable+]
|
# render renderable: Greeting.new
|
||||||
# Renders the provided object by calling +render_in+ with the current view
|
# # => renders "<h1>Hello, World</h1>"
|
||||||
# context. The response format is determined by calling +format+ on the
|
|
||||||
# renderable if it responds to +format+, falling back to +text/html+ by default.
|
|
||||||
#
|
#
|
||||||
# render renderable: Greeting.new
|
|
||||||
# # => renders "<h1>Hello, World</h1>"
|
|
||||||
#
|
#
|
||||||
# By default, when a rendering mode is specified, no layout template is
|
# By default, when a rendering mode is specified, no layout template is
|
||||||
# rendered.
|
# rendered.
|
||||||
#
|
#
|
||||||
# ==== Options
|
# #### Options
|
||||||
#
|
#
|
||||||
# [+:assigns+]
|
# `:assigns`
|
||||||
# Hash of instance variable assignments for the template.
|
# : Hash of instance variable assignments for the template.
|
||||||
#
|
#
|
||||||
# render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
|
# render inline: "<h1>Hello, <%= @name %>!</h1>", assigns: { name: "World" }
|
||||||
# # => renders "<h1>Hello, World!</h1>"
|
# # => renders "<h1>Hello, World!</h1>"
|
||||||
#
|
#
|
||||||
# [+:locals+]
|
# `:locals`
|
||||||
# Hash of local variable assignments for the template.
|
# : Hash of local variable assignments for the template.
|
||||||
#
|
#
|
||||||
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
|
# render inline: "<h1>Hello, <%= name %>!</h1>", locals: { name: "World" }
|
||||||
# # => renders "<h1>Hello, World!</h1>"
|
# # => renders "<h1>Hello, World!</h1>"
|
||||||
#
|
#
|
||||||
# [+:layout+]
|
# `:layout`
|
||||||
# The layout template to render. Can also be +false+ or +true+ to disable
|
# : The layout template to render. Can also be `false` or `true` to disable or
|
||||||
# or (re)enable the default layout template.
|
# (re)enable the default layout template.
|
||||||
#
|
#
|
||||||
# render "posts/show", layout: "holiday"
|
# render "posts/show", layout: "holiday"
|
||||||
# # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
|
# # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
|
||||||
#
|
#
|
||||||
# render "posts/show", layout: false
|
# render "posts/show", layout: false
|
||||||
# # => renders app/views/posts/show.html.erb with no layout
|
# # => renders app/views/posts/show.html.erb with no layout
|
||||||
#
|
#
|
||||||
# render inline: "<h1>Hello, World!</h1>", layout: true
|
# render inline: "<h1>Hello, World!</h1>", layout: true
|
||||||
# # => renders "<h1>Hello, World!</h1>" with the default layout
|
# # => renders "<h1>Hello, World!</h1>" with the default layout
|
||||||
#
|
#
|
||||||
# [+:status+]
|
# `:status`
|
||||||
# The HTTP status code to send with the response. Can be specified as a
|
# : The HTTP status code to send with the response. Can be specified as a
|
||||||
# number or as the status name in Symbol form. Defaults to 200.
|
# number or as the status name in Symbol form. Defaults to 200.
|
||||||
#
|
#
|
||||||
# render "posts/new", status: 422
|
# render "posts/new", status: 422
|
||||||
# # => renders app/views/posts/new.html.erb with HTTP status code 422
|
# # => renders app/views/posts/new.html.erb with HTTP status code 422
|
||||||
#
|
#
|
||||||
# render "posts/new", status: :unprocessable_entity
|
# render "posts/new", status: :unprocessable_entity
|
||||||
# # => renders app/views/posts/new.html.erb with HTTP status code 422
|
# # => renders app/views/posts/new.html.erb with HTTP status code 422
|
||||||
#
|
#
|
||||||
#--
|
#--
|
||||||
# Check for double render errors and set the content_type after rendering.
|
# Check for double render errors and set the content_type after rendering.
|
||||||
@ -164,7 +168,7 @@ def render(*args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Similar to #render, but only returns the rendered template as a string,
|
# Similar to #render, but only returns the rendered template as a string,
|
||||||
# instead of setting +self.response_body+.
|
# instead of setting `self.response_body`.
|
||||||
#--
|
#--
|
||||||
# Override render_to_string because body can now be set to a Rack body.
|
# Override render_to_string because body can now be set to a Rack body.
|
||||||
def render_to_string(*)
|
def render_to_string(*)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "rack/session/abstract/id"
|
require "rack/session/abstract/id"
|
||||||
require "action_controller/metal/exceptions"
|
require "action_controller/metal/exceptions"
|
||||||
require "active_support/security_utils"
|
require "active_support/security_utils"
|
||||||
@ -11,51 +13,53 @@ class InvalidAuthenticityToken < ActionControllerError # :nodoc:
|
|||||||
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
|
class InvalidCrossOriginRequest < ActionControllerError # :nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Action Controller Request Forgery Protection
|
# # Action Controller Request Forgery Protection
|
||||||
#
|
#
|
||||||
# Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks
|
# Controller actions are protected from Cross-Site Request Forgery (CSRF)
|
||||||
# by including a token in the rendered HTML for your application. This token is
|
# attacks by including a token in the rendered HTML for your application. This
|
||||||
# stored as a random string in the session, to which an attacker does not have
|
# token is stored as a random string in the session, to which an attacker does
|
||||||
# access. When a request reaches your application, \Rails verifies the received
|
# not have access. When a request reaches your application, Rails verifies the
|
||||||
# token with the token in the session. All requests are checked except GET requests
|
# received token with the token in the session. All requests are checked except
|
||||||
# as these should be idempotent. Keep in mind that all session-oriented requests
|
# GET requests as these should be idempotent. Keep in mind that all
|
||||||
# are CSRF protected by default, including JavaScript and HTML requests.
|
# session-oriented requests are CSRF protected by default, including JavaScript
|
||||||
|
# and HTML requests.
|
||||||
#
|
#
|
||||||
# Since HTML and JavaScript requests are typically made from the browser, we
|
# Since HTML and JavaScript requests are typically made from the browser, we
|
||||||
# need to ensure to verify request authenticity for the web browser. We can
|
# need to ensure to verify request authenticity for the web browser. We can use
|
||||||
# use session-oriented authentication for these types of requests, by using
|
# session-oriented authentication for these types of requests, by using the
|
||||||
# the <tt>protect_from_forgery</tt> method in our controllers.
|
# `protect_from_forgery` method in our controllers.
|
||||||
#
|
#
|
||||||
# GET requests are not protected since they don't have side effects like writing
|
# GET requests are not protected since they don't have side effects like writing
|
||||||
# to the database and don't leak sensitive information. JavaScript requests are
|
# to the database and don't leak sensitive information. JavaScript requests are
|
||||||
# an exception: a third-party site can use a <script> tag to reference a JavaScript
|
# an exception: a third-party site can use a <script> tag to reference a
|
||||||
# URL on your site. When your JavaScript response loads on their site, it executes.
|
# JavaScript URL on your site. When your JavaScript response loads on their
|
||||||
# With carefully crafted JavaScript on their end, sensitive data in your JavaScript
|
# site, it executes. With carefully crafted JavaScript on their end, sensitive
|
||||||
# response may be extracted. To prevent this, only XmlHttpRequest (known as XHR or
|
# data in your JavaScript response may be extracted. To prevent this, only
|
||||||
# Ajax) requests are allowed to make requests for JavaScript responses.
|
# XmlHttpRequest (known as XHR or Ajax) requests are allowed to make requests
|
||||||
|
# for JavaScript responses.
|
||||||
#
|
#
|
||||||
# Subclasses of ActionController::Base are protected by default with the
|
# Subclasses of ActionController::Base are protected by default with the
|
||||||
# <tt>:exception</tt> strategy, which raises an
|
# `:exception` strategy, which raises an
|
||||||
# ActionController::InvalidAuthenticityToken error on unverified requests.
|
# ActionController::InvalidAuthenticityToken error on unverified requests.
|
||||||
#
|
#
|
||||||
# APIs may want to disable this behavior since they are typically designed to be
|
# APIs may want to disable this behavior since they are typically designed to be
|
||||||
# state-less: that is, the request API client handles the session instead of \Rails.
|
# state-less: that is, the request API client handles the session instead of
|
||||||
# One way to achieve this is to use the <tt>:null_session</tt> strategy instead,
|
# Rails. One way to achieve this is to use the `:null_session` strategy instead,
|
||||||
# which allows unverified requests to be handled, but with an empty session:
|
# which allows unverified requests to be handled, but with an empty session:
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# protect_from_forgery with: :null_session
|
# protect_from_forgery with: :null_session
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Note that API only applications don't include this module or a session middleware
|
# Note that API only applications don't include this module or a session
|
||||||
# by default, and so don't require CSRF protection to be configured.
|
# middleware by default, and so don't require CSRF protection to be configured.
|
||||||
#
|
#
|
||||||
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
|
# The token parameter is named `authenticity_token` by default. The name and
|
||||||
# value of this token must be added to every layout that renders forms by including
|
# value of this token must be added to every layout that renders forms by
|
||||||
# <tt>csrf_meta_tags</tt> in the HTML +head+.
|
# including `csrf_meta_tags` in the HTML `head`.
|
||||||
#
|
#
|
||||||
# Learn more about CSRF attacks and securing your application in the
|
# Learn more about CSRF attacks and securing your application in the [Ruby on
|
||||||
# {Ruby on Rails Security Guide}[https://guides.rubyonrails.org/security.html].
|
# Rails Security Guide](https://guides.rubyonrails.org/security.html).
|
||||||
module RequestForgeryProtection
|
module RequestForgeryProtection
|
||||||
CSRF_TOKEN = "action_controller.csrf_token"
|
CSRF_TOKEN = "action_controller.csrf_token"
|
||||||
|
|
||||||
@ -65,8 +69,8 @@ module RequestForgeryProtection
|
|||||||
include AbstractController::Callbacks
|
include AbstractController::Callbacks
|
||||||
|
|
||||||
included do
|
included do
|
||||||
# Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+
|
# Sets the token parameter name for RequestForgery. Calling
|
||||||
# sets it to <tt>:authenticity_token</tt> by default.
|
# `protect_from_forgery` sets it to `:authenticity_token` by default.
|
||||||
config_accessor :request_forgery_protection_token
|
config_accessor :request_forgery_protection_token
|
||||||
self.request_forgery_protection_token ||= :authenticity_token
|
self.request_forgery_protection_token ||= :authenticity_token
|
||||||
|
|
||||||
@ -74,7 +78,8 @@ module RequestForgeryProtection
|
|||||||
config_accessor :forgery_protection_strategy
|
config_accessor :forgery_protection_strategy
|
||||||
self.forgery_protection_strategy = nil
|
self.forgery_protection_strategy = nil
|
||||||
|
|
||||||
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
|
# Controls whether request forgery protection is turned on or not. Turned off by
|
||||||
|
# default only in test mode.
|
||||||
config_accessor :allow_forgery_protection
|
config_accessor :allow_forgery_protection
|
||||||
self.allow_forgery_protection = true if allow_forgery_protection.nil?
|
self.allow_forgery_protection = true if allow_forgery_protection.nil?
|
||||||
|
|
||||||
@ -99,79 +104,96 @@ module RequestForgeryProtection
|
|||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
|
# Turn on request forgery protection. Bear in mind that GET and HEAD requests
|
||||||
|
# are not checked.
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# protect_from_forgery
|
# protect_from_forgery
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# class FooController < ApplicationController
|
# class FooController < ApplicationController
|
||||||
# protect_from_forgery except: :index
|
# protect_from_forgery except: :index
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# You can disable forgery protection on a controller using skip_forgery_protection:
|
# You can disable forgery protection on a controller using
|
||||||
|
# skip_forgery_protection:
|
||||||
#
|
#
|
||||||
# class BarController < ApplicationController
|
# class BarController < ApplicationController
|
||||||
# skip_forgery_protection
|
# skip_forgery_protection
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Valid Options:
|
# Valid Options:
|
||||||
#
|
#
|
||||||
# * <tt>:only</tt> / <tt>:except</tt> - Only apply forgery protection to a subset of actions. For example <tt>only: [ :create, :create_all ]</tt>.
|
# * `:only` / `:except` - Only apply forgery protection to a subset of
|
||||||
# * <tt>:if</tt> / <tt>:unless</tt> - Turn off the forgery protection entirely depending on the passed Proc or method reference.
|
# actions. For example `only: [ :create, :create_all ]`.
|
||||||
# * <tt>:prepend</tt> - By default, the verification of the authentication token will be added at the position of the
|
# * `:if` / `:unless` - Turn off the forgery protection entirely depending on
|
||||||
# protect_from_forgery call in your application. This means any callbacks added before are run first. This is useful
|
# the passed Proc or method reference.
|
||||||
# when you want your forgery protection to depend on other callbacks, like authentication methods (Oauth vs Cookie auth).
|
# * `:prepend` - By default, the verification of the authentication token will
|
||||||
|
# be added at the position of the protect_from_forgery call in your
|
||||||
|
# application. This means any callbacks added before are run first. This is
|
||||||
|
# useful when you want your forgery protection to depend on other callbacks,
|
||||||
|
# like authentication methods (Oauth vs Cookie auth).
|
||||||
|
#
|
||||||
|
# If you need to add verification to the beginning of the callback chain,
|
||||||
|
# use `prepend: true`.
|
||||||
|
# * `:with` - Set the method to handle unverified request. Note if
|
||||||
|
# `default_protect_from_forgery` is true, Rails call protect_from_forgery
|
||||||
|
# with `with :exception`.
|
||||||
#
|
#
|
||||||
# If you need to add verification to the beginning of the callback chain, use <tt>prepend: true</tt>.
|
|
||||||
# * <tt>:with</tt> - Set the method to handle unverified request.
|
|
||||||
# Note if <tt>default_protect_from_forgery</tt> is true, Rails call protect_from_forgery with <tt>with :exception</tt>.
|
|
||||||
#
|
#
|
||||||
# Built-in unverified request handling methods are:
|
# Built-in unverified request handling methods are:
|
||||||
# * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
|
# * `:exception` - Raises ActionController::InvalidAuthenticityToken
|
||||||
# * <tt>:reset_session</tt> - Resets the session.
|
# exception.
|
||||||
# * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified.
|
# * `:reset_session` - Resets the session.
|
||||||
|
# * `:null_session` - Provides an empty session during request but doesn't
|
||||||
|
# reset it completely. Used as default if `:with` option is not specified.
|
||||||
#
|
#
|
||||||
# You can also implement custom strategy classes for unverified request handling:
|
|
||||||
#
|
#
|
||||||
# class CustomStrategy
|
# You can also implement custom strategy classes for unverified request
|
||||||
# def initialize(controller)
|
# handling:
|
||||||
# @controller = controller
|
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# def handle_unverified_request
|
# class CustomStrategy
|
||||||
# # Custom behavior for unverfied request
|
# def initialize(controller)
|
||||||
# end
|
# @controller = controller
|
||||||
# end
|
# end
|
||||||
|
#
|
||||||
|
# def handle_unverified_request
|
||||||
|
# # Custom behavior for unverfied request
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class ApplicationController < ActionController::Base
|
||||||
|
# protect_from_forgery with: CustomStrategy
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# * `:store` - Set the strategy to store and retrieve CSRF tokens.
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# protect_from_forgery with: CustomStrategy
|
|
||||||
# end
|
|
||||||
# * <tt>:store</tt> - Set the strategy to store and retrieve CSRF tokens.
|
|
||||||
#
|
#
|
||||||
# Built-in session token strategies are:
|
# Built-in session token strategies are:
|
||||||
# * <tt>:session</tt> - Store the CSRF token in the session. Used as default if <tt>:store</tt> option is not specified.
|
# * `:session` - Store the CSRF token in the session. Used as default if
|
||||||
# * <tt>:cookie</tt> - Store the CSRF token in an encrypted cookie.
|
# `:store` option is not specified.
|
||||||
|
# * `:cookie` - Store the CSRF token in an encrypted cookie.
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# You can also implement custom strategy classes for CSRF token storage:
|
# You can also implement custom strategy classes for CSRF token storage:
|
||||||
#
|
#
|
||||||
# class CustomStore
|
# class CustomStore
|
||||||
# def fetch(request)
|
# def fetch(request)
|
||||||
# # Return the token from a custom location
|
# # Return the token from a custom location
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def store(request, csrf_token)
|
||||||
|
# # Store the token in a custom location
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def reset(request)
|
||||||
|
# # Delete the stored session token
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# def store(request, csrf_token)
|
# class ApplicationController < ActionController::Base
|
||||||
# # Store the token in a custom location
|
# protect_from_forgery store: CustomStore.new
|
||||||
# end
|
# end
|
||||||
#
|
|
||||||
# def reset(request)
|
|
||||||
# # Delete the stored session token
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class ApplicationController < ActionController::Base
|
|
||||||
# protect_from_forgery store: CustomStore.new
|
|
||||||
# end
|
|
||||||
def protect_from_forgery(options = {})
|
def protect_from_forgery(options = {})
|
||||||
options = options.reverse_merge(prepend: false)
|
options = options.reverse_merge(prepend: false)
|
||||||
|
|
||||||
@ -186,9 +208,9 @@ def protect_from_forgery(options = {})
|
|||||||
|
|
||||||
# Turn off request forgery protection. This is a wrapper for:
|
# Turn off request forgery protection. This is a wrapper for:
|
||||||
#
|
#
|
||||||
# skip_before_action :verify_authenticity_token
|
# skip_before_action :verify_authenticity_token
|
||||||
#
|
#
|
||||||
# See +skip_before_action+ for allowed options.
|
# See `skip_before_action` for allowed options.
|
||||||
def skip_forgery_protection(options = {})
|
def skip_forgery_protection(options = {})
|
||||||
skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
|
skip_before_action :verify_authenticity_token, options.reverse_merge(raise: false)
|
||||||
end
|
end
|
||||||
@ -232,7 +254,8 @@ def initialize(controller)
|
|||||||
@controller = controller
|
@controller = controller
|
||||||
end
|
end
|
||||||
|
|
||||||
# This is the method that defines the application behavior when a request is found to be unverified.
|
# This is the method that defines the application behavior when a request is
|
||||||
|
# found to be unverified.
|
||||||
def handle_unverified_request
|
def handle_unverified_request
|
||||||
request = @controller.request
|
request = @controller.request
|
||||||
request.session = NullSessionHash.new(request)
|
request.session = NullSessionHash.new(request)
|
||||||
@ -354,16 +377,15 @@ def commit_csrf_token(request) # :doc:
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# The actual before_action that is used to verify the CSRF token.
|
# The actual before_action that is used to verify the CSRF token. Don't override
|
||||||
# Don't override this directly. Provide your own forgery protection
|
# this directly. Provide your own forgery protection strategy instead. If you
|
||||||
# strategy instead. If you override, you'll disable same-origin
|
# override, you'll disable same-origin `<script>` verification.
|
||||||
# <tt><script></tt> verification.
|
|
||||||
#
|
#
|
||||||
# Lean on the protect_from_forgery declaration to mark which actions are
|
# Lean on the protect_from_forgery declaration to mark which actions are due for
|
||||||
# due for same-origin request verification. If protect_from_forgery is
|
# same-origin request verification. If protect_from_forgery is enabled on an
|
||||||
# enabled on an action, this before_action flags its after_action to
|
# action, this before_action flags its after_action to verify that JavaScript
|
||||||
# verify that JavaScript responses are for XHR requests, ensuring they
|
# responses are for XHR requests, ensuring they follow the browser's same-origin
|
||||||
# follow the browser's same-origin policy.
|
# policy.
|
||||||
def verify_authenticity_token # :doc:
|
def verify_authenticity_token # :doc:
|
||||||
mark_for_same_origin_verification!
|
mark_for_same_origin_verification!
|
||||||
|
|
||||||
@ -400,9 +422,9 @@ def unverified_request_warning_message # :nodoc:
|
|||||||
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
|
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
|
||||||
# :startdoc:
|
# :startdoc:
|
||||||
|
|
||||||
# If +verify_authenticity_token+ was run (indicating that we have
|
# If `verify_authenticity_token` was run (indicating that we have
|
||||||
# forgery protection enabled for this request) then also verify that
|
# forgery protection enabled for this request) then also verify that we aren't
|
||||||
# we aren't serving an unauthorized cross-origin response.
|
# serving an unauthorized cross-origin response.
|
||||||
def verify_same_origin_request # :doc:
|
def verify_same_origin_request # :doc:
|
||||||
if marked_for_same_origin_verification? && non_xhr_javascript_response?
|
if marked_for_same_origin_verification? && non_xhr_javascript_response?
|
||||||
if logger && log_warning_on_csrf_failure
|
if logger && log_warning_on_csrf_failure
|
||||||
@ -417,8 +439,8 @@ def mark_for_same_origin_verification! # :doc:
|
|||||||
@_marked_for_same_origin_verification = request.get?
|
@_marked_for_same_origin_verification = request.get?
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the +verify_authenticity_token+ before_action ran, verify that
|
# If the `verify_authenticity_token` before_action ran, verify that JavaScript
|
||||||
# JavaScript responses are only served to same-origin GET requests.
|
# responses are only served to same-origin GET requests.
|
||||||
def marked_for_same_origin_verification? # :doc:
|
def marked_for_same_origin_verification? # :doc:
|
||||||
@_marked_for_same_origin_verification ||= false
|
@_marked_for_same_origin_verification ||= false
|
||||||
end
|
end
|
||||||
@ -432,9 +454,11 @@ def non_xhr_javascript_response? # :doc:
|
|||||||
|
|
||||||
# Returns true or false if a request is verified. Checks:
|
# Returns true or false if a request is verified. Checks:
|
||||||
#
|
#
|
||||||
# * Is it a GET or HEAD request? GETs should be safe and idempotent
|
# * Is it a GET or HEAD request? GETs should be safe and idempotent
|
||||||
# * Does the form_authenticity_token match the given token value from the params?
|
# * Does the form_authenticity_token match the given token value from the
|
||||||
# * Does the +X-CSRF-Token+ header match the form_authenticity_token?
|
# params?
|
||||||
|
# * Does the `X-CSRF-Token` header match the form_authenticity_token?
|
||||||
|
#
|
||||||
def verified_request? # :doc:
|
def verified_request? # :doc:
|
||||||
!protect_against_forgery? || request.get? || request.head? ||
|
!protect_against_forgery? || request.get? || request.head? ||
|
||||||
(valid_request_origin? && any_authenticity_token_valid?)
|
(valid_request_origin? && any_authenticity_token_valid?)
|
||||||
@ -457,9 +481,8 @@ def form_authenticity_token(form_options: {}) # :doc:
|
|||||||
masked_authenticity_token(form_options: form_options)
|
masked_authenticity_token(form_options: form_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates a masked version of the authenticity token that varies
|
# Creates a masked version of the authenticity token that varies on each
|
||||||
# on each request. The masking is used to mitigate SSL attacks
|
# request. The masking is used to mitigate SSL attacks like BREACH.
|
||||||
# like BREACH.
|
|
||||||
def masked_authenticity_token(form_options: {})
|
def masked_authenticity_token(form_options: {})
|
||||||
action, method = form_options.values_at(:action, :method)
|
action, method = form_options.values_at(:action, :method)
|
||||||
|
|
||||||
@ -473,9 +496,8 @@ def masked_authenticity_token(form_options: {})
|
|||||||
mask_token(raw_token)
|
mask_token(raw_token)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks the client's masked token to see if it matches the
|
# Checks the client's masked token to see if it matches the session token.
|
||||||
# session token. Essentially the inverse of
|
# Essentially the inverse of `masked_authenticity_token`.
|
||||||
# +masked_authenticity_token+.
|
|
||||||
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
||||||
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
|
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
|
||||||
return false
|
return false
|
||||||
@ -487,14 +509,12 @@ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
# See if it's actually a masked token or not. In order to
|
# See if it's actually a masked token or not. In order to deploy this code, we
|
||||||
# deploy this code, we should be able to handle any unmasked
|
# should be able to handle any unmasked tokens that we've issued without error.
|
||||||
# tokens that we've issued without error.
|
|
||||||
|
|
||||||
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
|
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
|
||||||
# This is actually an unmasked token. This is expected if
|
# This is actually an unmasked token. This is expected if you have just upgraded
|
||||||
# you have just upgraded to masked tokens, but should stop
|
# to masked tokens, but should stop happening shortly after installing this gem.
|
||||||
# happening shortly after installing this gem.
|
|
||||||
compare_with_real_token masked_token
|
compare_with_real_token masked_token
|
||||||
|
|
||||||
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
|
||||||
@ -509,8 +529,7 @@ def valid_authenticity_token?(session, encoded_masked_token) # :doc:
|
|||||||
end
|
end
|
||||||
|
|
||||||
def unmask_token(masked_token) # :doc:
|
def unmask_token(masked_token) # :doc:
|
||||||
# Split the token into the one-time pad and the encrypted
|
# Split the token into the one-time pad and the encrypted value and decrypt it.
|
||||||
# value and decrypt it.
|
|
||||||
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
|
||||||
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
|
||||||
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
xor_byte_strings(one_time_pad, encrypted_csrf_token)
|
||||||
@ -602,8 +621,8 @@ def protect_against_forgery? # :doc:
|
|||||||
Rails.application.config.action_controller.forgery_protection_origin_check setting.
|
Rails.application.config.action_controller.forgery_protection_origin_check setting.
|
||||||
MSG
|
MSG
|
||||||
|
|
||||||
# Checks if the request originated from the same origin by looking at the
|
# Checks if the request originated from the same origin by looking at the Origin
|
||||||
# Origin header.
|
# header.
|
||||||
def valid_request_origin? # :doc:
|
def valid_request_origin? # :doc:
|
||||||
if forgery_protection_origin_check
|
if forgery_protection_origin_check
|
||||||
# We accept blank origin headers because some user agents don't send it.
|
# We accept blank origin headers because some user agents don't send it.
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
# = Action Controller \Rescue
|
# # Action Controller Rescue
|
||||||
#
|
#
|
||||||
# This module is responsible for providing
|
# This module is responsible for providing
|
||||||
# {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]
|
# [rescue_from](rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from) to
|
||||||
# to controllers, wrapping actions to handle configured errors, and
|
# controllers, wrapping actions to handle configured errors, and configuring
|
||||||
# configuring when detailed exceptions must be shown.
|
# when detailed exceptions must be shown.
|
||||||
module Rescue
|
module Rescue
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include ActiveSupport::Rescuable
|
include ActiveSupport::Rescuable
|
||||||
|
|
||||||
# Override this method if you want to customize when detailed
|
# Override this method if you want to customize when detailed exceptions must be
|
||||||
# exceptions must be shown. This method is only called when
|
# shown. This method is only called when `consider_all_requests_local` is
|
||||||
# +consider_all_requests_local+ is +false+. By default, it returns
|
# `false`. By default, it returns `false`, but someone may set it to
|
||||||
# +false+, but someone may set it to <tt>request.local?</tt> so local
|
# `request.local?` so local requests in production still show the detailed
|
||||||
# requests in production still show the detailed exception pages.
|
# exception pages.
|
||||||
def show_detailed_exceptions?
|
def show_detailed_exceptions?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -1,208 +1,210 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController # :nodoc:
|
module ActionController # :nodoc:
|
||||||
# = Action Controller \Streaming
|
# # Action Controller Streaming
|
||||||
#
|
#
|
||||||
# Allows views to be streamed back to the client as they are rendered.
|
# Allows views to be streamed back to the client as they are rendered.
|
||||||
#
|
#
|
||||||
# By default, \Rails renders views by first rendering the template
|
# By default, Rails renders views by first rendering the template and then the
|
||||||
# and then the layout. The response is sent to the client after the whole
|
# layout. The response is sent to the client after the whole template is
|
||||||
# template is rendered, all queries are made, and the layout is processed.
|
# rendered, all queries are made, and the layout is processed.
|
||||||
#
|
#
|
||||||
# \Streaming inverts the rendering flow by rendering the layout first and
|
# Streaming inverts the rendering flow by rendering the layout first and
|
||||||
# subsequently each part of the layout as they are processed. This allows the
|
# subsequently each part of the layout as they are processed. This allows the
|
||||||
# header of the HTML (which is usually in the layout) to be streamed back
|
# header of the HTML (which is usually in the layout) to be streamed back to
|
||||||
# to client very quickly, enabling JavaScripts and stylesheets to be loaded
|
# client very quickly, enabling JavaScripts and stylesheets to be loaded earlier
|
||||||
# earlier than usual.
|
# than usual.
|
||||||
#
|
#
|
||||||
# Several Rack middlewares may not work and you need to be careful when streaming.
|
# Several Rack middlewares may not work and you need to be careful when
|
||||||
# This is covered in more detail below, see the Streaming@Middlewares section.
|
# streaming. This is covered in more detail below, see the Streaming@Middlewares
|
||||||
|
# section.
|
||||||
#
|
#
|
||||||
# \Streaming can be added to a given template easily, all you need to do is
|
# Streaming can be added to a given template easily, all you need to do is to
|
||||||
# to pass the +:stream+ option to +render+.
|
# pass the `:stream` option to `render`.
|
||||||
#
|
#
|
||||||
# class PostsController
|
# class PostsController
|
||||||
# def index
|
# def index
|
||||||
# @posts = Post.all
|
# @posts = Post.all
|
||||||
# render stream: true
|
# render stream: true
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# == When to use streaming
|
# ## When to use streaming
|
||||||
#
|
#
|
||||||
# \Streaming may be considered to be overkill for lightweight actions like
|
# Streaming may be considered to be overkill for lightweight actions like `new`
|
||||||
# +new+ or +edit+. The real benefit of streaming is on expensive actions
|
# or `edit`. The real benefit of streaming is on expensive actions that, for
|
||||||
# that, for example, do a lot of queries on the database.
|
# example, do a lot of queries on the database.
|
||||||
#
|
#
|
||||||
# In such actions, you want to delay queries execution as much as you can.
|
# In such actions, you want to delay queries execution as much as you can. For
|
||||||
# For example, imagine the following +dashboard+ action:
|
# example, imagine the following `dashboard` action:
|
||||||
#
|
#
|
||||||
# def dashboard
|
# def dashboard
|
||||||
# @posts = Post.all
|
# @posts = Post.all
|
||||||
# @pages = Page.all
|
# @pages = Page.all
|
||||||
# @articles = Article.all
|
# @articles = Article.all
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Most of the queries here are happening in the controller. In order to benefit
|
# Most of the queries here are happening in the controller. In order to benefit
|
||||||
# from streaming you would want to rewrite it as:
|
# from streaming you would want to rewrite it as:
|
||||||
#
|
#
|
||||||
# def dashboard
|
# def dashboard
|
||||||
# # Allow lazy execution of the queries
|
# # Allow lazy execution of the queries
|
||||||
# @posts = Post.all
|
# @posts = Post.all
|
||||||
# @pages = Page.all
|
# @pages = Page.all
|
||||||
# @articles = Article.all
|
# @articles = Article.all
|
||||||
# render stream: true
|
# render stream: true
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Notice that +:stream+ only works with templates. \Rendering +:json+
|
# Notice that `:stream` only works with templates. Rendering `:json` or `:xml`
|
||||||
# or +:xml+ with +:stream+ won't work.
|
# with `:stream` won't work.
|
||||||
#
|
#
|
||||||
# == Communication between layout and template
|
# ## Communication between layout and template
|
||||||
#
|
#
|
||||||
# When streaming, rendering happens top-down instead of inside-out.
|
# When streaming, rendering happens top-down instead of inside-out. Rails starts
|
||||||
# \Rails starts with the layout, and the template is rendered later,
|
# with the layout, and the template is rendered later, when its `yield` is
|
||||||
# when its +yield+ is reached.
|
# reached.
|
||||||
#
|
#
|
||||||
# This means that, if your application currently relies on instance
|
# This means that, if your application currently relies on instance variables
|
||||||
# variables set in the template to be used in the layout, they won't
|
# set in the template to be used in the layout, they won't work once you move to
|
||||||
# work once you move to streaming. The proper way to communicate
|
# streaming. The proper way to communicate between layout and template,
|
||||||
# between layout and template, regardless of whether you use streaming
|
# regardless of whether you use streaming or not, is by using `content_for`,
|
||||||
# or not, is by using +content_for+, +provide+, and +yield+.
|
# `provide`, and `yield`.
|
||||||
#
|
#
|
||||||
# Take a simple example where the layout expects the template to tell
|
# Take a simple example where the layout expects the template to tell which
|
||||||
# which title to use:
|
# title to use:
|
||||||
#
|
#
|
||||||
# <html>
|
# <html>
|
||||||
# <head><title><%= yield :title %></title></head>
|
# <head><title><%= yield :title %></title></head>
|
||||||
# <body><%= yield %></body>
|
# <body><%= yield %></body>
|
||||||
# </html>
|
# </html>
|
||||||
#
|
#
|
||||||
# You would use +content_for+ in your template to specify the title:
|
# You would use `content_for` in your template to specify the title:
|
||||||
#
|
#
|
||||||
# <%= content_for :title, "Main" %>
|
# <%= content_for :title, "Main" %>
|
||||||
# Hello
|
# Hello
|
||||||
#
|
#
|
||||||
# And the final result would be:
|
# And the final result would be:
|
||||||
#
|
#
|
||||||
# <html>
|
# <html>
|
||||||
# <head><title>Main</title></head>
|
# <head><title>Main</title></head>
|
||||||
# <body>Hello</body>
|
# <body>Hello</body>
|
||||||
# </html>
|
# </html>
|
||||||
#
|
#
|
||||||
# However, if +content_for+ is called several times, the final result
|
# However, if `content_for` is called several times, the final result would have
|
||||||
# would have all calls concatenated. For instance, if we have the following
|
# all calls concatenated. For instance, if we have the following template:
|
||||||
# template:
|
|
||||||
#
|
#
|
||||||
# <%= content_for :title, "Main" %>
|
# <%= content_for :title, "Main" %>
|
||||||
# Hello
|
# Hello
|
||||||
# <%= content_for :title, " page" %>
|
# <%= content_for :title, " page" %>
|
||||||
#
|
#
|
||||||
# The final result would be:
|
# The final result would be:
|
||||||
#
|
#
|
||||||
# <html>
|
# <html>
|
||||||
# <head><title>Main page</title></head>
|
# <head><title>Main page</title></head>
|
||||||
# <body>Hello</body>
|
# <body>Hello</body>
|
||||||
# </html>
|
# </html>
|
||||||
#
|
#
|
||||||
# This means that, if you have <code>yield :title</code> in your layout
|
# This means that, if you have `yield :title` in your layout and you want to use
|
||||||
# and you want to use streaming, you would have to render the whole template
|
# streaming, you would have to render the whole template (and eventually trigger
|
||||||
# (and eventually trigger all queries) before streaming the title and all
|
# all queries) before streaming the title and all assets, which defeats the
|
||||||
# assets, which defeats the purpose of streaming. Alternatively, you can use
|
# purpose of streaming. Alternatively, you can use a helper called `provide`
|
||||||
# a helper called +provide+ that does the same as +content_for+ but tells the
|
# that does the same as `content_for` but tells the layout to stop searching for
|
||||||
# layout to stop searching for other entries and continue rendering.
|
# other entries and continue rendering.
|
||||||
#
|
#
|
||||||
# For instance, the template above using +provide+ would be:
|
# For instance, the template above using `provide` would be:
|
||||||
#
|
#
|
||||||
# <%= provide :title, "Main" %>
|
# <%= provide :title, "Main" %>
|
||||||
# Hello
|
# Hello
|
||||||
# <%= content_for :title, " page" %>
|
# <%= content_for :title, " page" %>
|
||||||
#
|
#
|
||||||
# Resulting in:
|
# Resulting in:
|
||||||
#
|
#
|
||||||
# <html>
|
# <html>
|
||||||
# <head><title>Main</title></head>
|
# <head><title>Main</title></head>
|
||||||
# <body>Hello</body>
|
# <body>Hello</body>
|
||||||
# </html>
|
# </html>
|
||||||
#
|
#
|
||||||
# That said, when streaming, you need to properly check your templates
|
# That said, when streaming, you need to properly check your templates and
|
||||||
# and choose when to use +provide+ and +content_for+.
|
# choose when to use `provide` and `content_for`.
|
||||||
#
|
#
|
||||||
# See also ActionView::Helpers::CaptureHelper for more information.
|
# See also ActionView::Helpers::CaptureHelper for more information.
|
||||||
#
|
#
|
||||||
# == Headers, cookies, session, and flash
|
# ## Headers, cookies, session, and flash
|
||||||
#
|
#
|
||||||
# When streaming, the HTTP headers are sent to the client right before
|
# When streaming, the HTTP headers are sent to the client right before it
|
||||||
# it renders the first line. This means that, modifying headers, cookies,
|
# renders the first line. This means that, modifying headers, cookies, session
|
||||||
# session or flash after the template starts rendering will not propagate
|
# or flash after the template starts rendering will not propagate to the client.
|
||||||
# to the client.
|
|
||||||
#
|
#
|
||||||
# == Middlewares
|
# ## Middlewares
|
||||||
#
|
#
|
||||||
# Middlewares that need to manipulate the body won't work with streaming.
|
# Middlewares that need to manipulate the body won't work with streaming. You
|
||||||
# You should disable those middlewares whenever streaming in development
|
# should disable those middlewares whenever streaming in development or
|
||||||
# or production. For instance, +Rack::Bug+ won't work when streaming as it
|
# production. For instance, `Rack::Bug` won't work when streaming as it needs to
|
||||||
# needs to inject contents in the HTML body.
|
# inject contents in the HTML body.
|
||||||
#
|
#
|
||||||
# Also +Rack::Cache+ won't work with streaming as it does not support
|
# Also `Rack::Cache` won't work with streaming as it does not support streaming
|
||||||
# streaming bodies yet. Whenever streaming +Cache-Control+ is automatically
|
# bodies yet. Whenever streaming `Cache-Control` is automatically set to
|
||||||
# set to "no-cache".
|
# "no-cache".
|
||||||
#
|
#
|
||||||
# == Errors
|
# ## Errors
|
||||||
#
|
#
|
||||||
# When it comes to streaming, exceptions get a bit more complicated. This
|
# When it comes to streaming, exceptions get a bit more complicated. This
|
||||||
# happens because part of the template was already rendered and streamed to
|
# happens because part of the template was already rendered and streamed to the
|
||||||
# the client, making it impossible to render a whole exception page.
|
# client, making it impossible to render a whole exception page.
|
||||||
#
|
#
|
||||||
# Currently, when an exception happens in development or production, \Rails
|
# Currently, when an exception happens in development or production, Rails will
|
||||||
# will automatically stream to the client:
|
# automatically stream to the client:
|
||||||
#
|
#
|
||||||
# "><script>window.location = "/500.html"</script></html>
|
# "><script>window.location = "/500.html"</script></html>
|
||||||
#
|
#
|
||||||
# The first two characters (<tt>"></tt>) are required in case the exception
|
# The first two characters (`">`) are required in case the exception happens
|
||||||
# happens while rendering attributes for a given tag. You can check the real
|
# while rendering attributes for a given tag. You can check the real cause for
|
||||||
# cause for the exception in your logger.
|
# the exception in your logger.
|
||||||
#
|
#
|
||||||
# == Web server support
|
# ## Web server support
|
||||||
#
|
#
|
||||||
# Not all web servers support streaming out-of-the-box. You need to check
|
# Not all web servers support streaming out-of-the-box. You need to check the
|
||||||
# the instructions for each of them.
|
# instructions for each of them.
|
||||||
#
|
#
|
||||||
# ==== Unicorn
|
# #### Unicorn
|
||||||
#
|
#
|
||||||
# Unicorn supports streaming but it needs to be configured. For this, you
|
# Unicorn supports streaming but it needs to be configured. For this, you need
|
||||||
# need to create a config file as follow:
|
# to create a config file as follow:
|
||||||
#
|
#
|
||||||
# # unicorn.config.rb
|
# # unicorn.config.rb
|
||||||
# listen 3000, tcp_nopush: false
|
# listen 3000, tcp_nopush: false
|
||||||
#
|
#
|
||||||
# And use it on initialization:
|
# And use it on initialization:
|
||||||
#
|
#
|
||||||
# unicorn_rails --config-file unicorn.config.rb
|
# unicorn_rails --config-file unicorn.config.rb
|
||||||
#
|
#
|
||||||
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
|
# You may also want to configure other parameters like `:tcp_nodelay`.
|
||||||
#
|
#
|
||||||
# For more information, please check the
|
# For more information, please check the
|
||||||
# {documentation}[https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen].
|
# [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method-
|
||||||
|
# i-listen).
|
||||||
#
|
#
|
||||||
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
|
# If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming
|
||||||
# \Streaming should work out of the box on Rainbows.
|
# should work out of the box on Rainbows.
|
||||||
#
|
#
|
||||||
# ==== Passenger
|
# #### Passenger
|
||||||
#
|
#
|
||||||
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
|
# Phusion Passenger with NGINX, offers two streaming mechanisms out of the box.
|
||||||
#
|
#
|
||||||
# 1. NGINX response buffering mechanism which is dependent on the value of
|
# 1. NGINX response buffering mechanism which is dependent on the value of
|
||||||
# +passenger_buffer_response+ option (default is "off").
|
# `passenger_buffer_response` option (default is "off").
|
||||||
# 2. Passenger buffering system which is always 'on' irrespective of the value
|
# 2. Passenger buffering system which is always 'on' irrespective of the value
|
||||||
# of +passenger_buffer_response+.
|
# of `passenger_buffer_response`.
|
||||||
#
|
#
|
||||||
# When +passenger_buffer_response+ is turned "on", then streaming would be
|
|
||||||
# done at the NGINX level which waits until the application is done sending
|
|
||||||
# the response back to the client.
|
|
||||||
#
|
#
|
||||||
# For more information, please check the
|
# When `passenger_buffer_response` is turned "on", then streaming would be done
|
||||||
# {documentation}[https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response].
|
# at the NGINX level which waits until the application is done sending the
|
||||||
|
# response back to the client.
|
||||||
#
|
#
|
||||||
|
# For more information, please check the [documentation]
|
||||||
|
# (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response).
|
||||||
module Streaming
|
module Streaming
|
||||||
class Body # :nodoc:
|
class Body # :nodoc:
|
||||||
TERM = "\r\n"
|
TERM = "\r\n"
|
||||||
@ -213,8 +215,8 @@ def initialize(body)
|
|||||||
@body = body
|
@body = body
|
||||||
end
|
end
|
||||||
|
|
||||||
# For each element yielded by the response body, yield
|
# For each element yielded by the response body, yield the element in chunked
|
||||||
# the element in chunked encoding.
|
# encoding.
|
||||||
def each(&block)
|
def each(&block)
|
||||||
term = TERM
|
term = TERM
|
||||||
@body.each do |chunk|
|
@body.each do |chunk|
|
||||||
@ -248,7 +250,7 @@ def _process_options(options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Call render_body if we are streaming instead of usual +render+.
|
# Call render_body if we are streaming instead of usual `render`.
|
||||||
def _render_template(options)
|
def _render_template(options)
|
||||||
if options.delete(:stream)
|
if options.delete(:stream)
|
||||||
Body.new view_renderer.render_body(view_context, options)
|
Body.new view_renderer.render_body(view_context, options)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Testing
|
module Testing
|
||||||
# Behavior specific to functional tests
|
# Behavior specific to functional tests
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \UrlFor
|
# # Action Controller UrlFor
|
||||||
#
|
#
|
||||||
# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
|
# Includes `url_for` into the host class. The class has to provide a `RouteSet`
|
||||||
# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
|
# by implementing the `_routes` method. Otherwise, an exception will be raised.
|
||||||
#
|
#
|
||||||
# In addition to AbstractController::UrlFor, this module accesses the HTTP layer to define
|
# In addition to AbstractController::UrlFor, this module accesses the HTTP layer
|
||||||
# URL options like the +host+. In order to do so, this module requires the host class
|
# to define URL options like the `host`. In order to do so, this module requires
|
||||||
# to implement +env+ which needs to be Rack-compatible, and +request+ which
|
# the host class to implement `env` which needs to be Rack-compatible, and
|
||||||
# returns an ActionDispatch::Request instance.
|
# `request` which returns an ActionDispatch::Request instance.
|
||||||
#
|
#
|
||||||
# class RootUrl
|
# class RootUrl
|
||||||
# include ActionController::UrlFor
|
# include ActionController::UrlFor
|
||||||
# include Rails.application.routes.url_helpers
|
# include Rails.application.routes.url_helpers
|
||||||
#
|
#
|
||||||
# delegate :env, :request, to: :controller
|
# delegate :env, :request, to: :controller
|
||||||
#
|
#
|
||||||
# def initialize(controller)
|
# def initialize(controller)
|
||||||
# @controller = controller
|
# @controller = controller
|
||||||
# @url = root_path # named route from the application.
|
# @url = root_path # named route from the application.
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
module UrlFor
|
module UrlFor
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "rails"
|
require "rails"
|
||||||
require "action_controller"
|
require "action_controller"
|
||||||
require "action_dispatch/railtie"
|
require "action_dispatch/railtie"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module Railties
|
module Railties
|
||||||
module Helpers
|
module Helpers
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
# = Action Controller \Renderer
|
# # Action Controller Renderer
|
||||||
#
|
#
|
||||||
# ActionController::Renderer allows you to render arbitrary templates without
|
# ActionController::Renderer allows you to render arbitrary templates without
|
||||||
# being inside a controller action.
|
# being inside a controller action.
|
||||||
#
|
#
|
||||||
# You can get a renderer instance by calling +renderer+ on a controller class:
|
# You can get a renderer instance by calling `renderer` on a controller class:
|
||||||
#
|
#
|
||||||
# ApplicationController.renderer
|
# ApplicationController.renderer
|
||||||
# PostsController.renderer
|
# PostsController.renderer
|
||||||
#
|
#
|
||||||
# and render a template by calling the #render method:
|
# and render a template by calling the #render method:
|
||||||
#
|
#
|
||||||
# ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first }
|
# ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first }
|
||||||
# PostsController.renderer.render :show, assigns: { post: Post.first }
|
# PostsController.renderer.render :show, assigns: { post: Post.first }
|
||||||
#
|
#
|
||||||
# As a shortcut, you can also call +render+ directly on the controller class itself:
|
# As a shortcut, you can also call `render` directly on the controller class
|
||||||
|
# itself:
|
||||||
#
|
#
|
||||||
# ApplicationController.render template: "posts/show", assigns: { post: Post.first }
|
# ApplicationController.render template: "posts/show", assigns: { post: Post.first }
|
||||||
# PostsController.render :show, assigns: { post: Post.first }
|
# PostsController.render :show, assigns: { post: Post.first }
|
||||||
#
|
#
|
||||||
class Renderer
|
class Renderer
|
||||||
attr_reader :controller
|
attr_reader :controller
|
||||||
@ -64,45 +67,47 @@ def self.for(controller, env = nil, defaults = DEFAULTS)
|
|||||||
|
|
||||||
# Creates a new renderer using the same controller, but with a new Rack env.
|
# Creates a new renderer using the same controller, but with a new Rack env.
|
||||||
#
|
#
|
||||||
# ApplicationController.renderer.new(method: "post")
|
# ApplicationController.renderer.new(method: "post")
|
||||||
#
|
#
|
||||||
def new(env = nil)
|
def new(env = nil)
|
||||||
self.class.new controller, env, @defaults
|
self.class.new controller, env, @defaults
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates a new renderer using the same controller, but with the given
|
# Creates a new renderer using the same controller, but with the given defaults
|
||||||
# defaults merged on top of the previous defaults.
|
# merged on top of the previous defaults.
|
||||||
def with_defaults(defaults)
|
def with_defaults(defaults)
|
||||||
self.class.new controller, @env, @defaults.merge(defaults)
|
self.class.new controller, @env, @defaults.merge(defaults)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Initializes a new Renderer.
|
# Initializes a new Renderer.
|
||||||
#
|
#
|
||||||
# ==== Parameters
|
# #### Parameters
|
||||||
#
|
#
|
||||||
# * +controller+ - The controller class to instantiate for rendering.
|
# * `controller` - The controller class to instantiate for rendering.
|
||||||
# * +env+ - The Rack env to use for mocking a request when rendering.
|
# * `env` - The Rack env to use for mocking a request when rendering. Entries
|
||||||
# Entries can be typical Rack env keys and values, or they can be any of
|
# can be typical Rack env keys and values, or they can be any of the
|
||||||
# the following, which will be converted appropriately:
|
# following, which will be converted appropriately:
|
||||||
# * +:http_host+ - The HTTP host for the incoming request. Converts to
|
# * `:http_host` - The HTTP host for the incoming request. Converts to
|
||||||
# Rack's +HTTP_HOST+.
|
# Rack's `HTTP_HOST`.
|
||||||
# * +:https+ - Boolean indicating whether the incoming request uses HTTPS.
|
# * `:https` - Boolean indicating whether the incoming request uses HTTPS.
|
||||||
# Converts to Rack's +HTTPS+.
|
# Converts to Rack's `HTTPS`.
|
||||||
# * +:method+ - The HTTP method for the incoming request, case-insensitive.
|
# * `:method` - The HTTP method for the incoming request,
|
||||||
# Converts to Rack's +REQUEST_METHOD+.
|
# case-insensitive. Converts to Rack's `REQUEST_METHOD`.
|
||||||
# * +:script_name+ - The portion of the incoming request's URL path that
|
# * `:script_name` - The portion of the incoming request's URL path that
|
||||||
# corresponds to the application. Converts to Rack's +SCRIPT_NAME+.
|
# corresponds to the application. Converts to Rack's `SCRIPT_NAME`.
|
||||||
# * +:input+ - The input stream. Converts to Rack's +rack.input+.
|
# * `:input` - The input stream. Converts to Rack's `rack.input`.
|
||||||
# * +defaults+ - Default values for the Rack env. Entries are specified in
|
|
||||||
# the same format as +env+. +env+ will be merged on top of these values.
|
|
||||||
# +defaults+ will be retained when calling #new on a renderer instance.
|
|
||||||
#
|
#
|
||||||
# If no +http_host+ is specified, the env HTTP host will be derived from the
|
# * `defaults` - Default values for the Rack env. Entries are specified in the
|
||||||
# routes' +default_url_options+. In this case, the +https+ boolean and the
|
# same format as `env`. `env` will be merged on top of these values.
|
||||||
# +script_name+ will also be derived from +default_url_options+ if they were
|
# `defaults` will be retained when calling #new on a renderer instance.
|
||||||
# not specified. Additionally, the +https+ boolean will fall back to
|
#
|
||||||
# +Rails.application.config.force_ssl+ if +default_url_options+ does not
|
#
|
||||||
# specify a +protocol+.
|
# If no `http_host` is specified, the env HTTP host will be derived from the
|
||||||
|
# routes' `default_url_options`. In this case, the `https` boolean and the
|
||||||
|
# `script_name` will also be derived from `default_url_options` if they were not
|
||||||
|
# specified. Additionally, the `https` boolean will fall back to
|
||||||
|
# `Rails.application.config.force_ssl` if `default_url_options` does not specify
|
||||||
|
# a `protocol`.
|
||||||
def initialize(controller, env, defaults)
|
def initialize(controller, env, defaults)
|
||||||
@controller = controller
|
@controller = controller
|
||||||
@defaults = defaults
|
@defaults = defaults
|
||||||
@ -119,7 +124,8 @@ def defaults
|
|||||||
@defaults
|
@defaults
|
||||||
end
|
end
|
||||||
|
|
||||||
# Renders a template to a string, just like ActionController::Rendering#render_to_string.
|
# Renders a template to a string, just like
|
||||||
|
# ActionController::Rendering#render_to_string.
|
||||||
def render(*args)
|
def render(*args)
|
||||||
request = ActionDispatch::Request.new(env_for_request)
|
request = ActionDispatch::Request.new(env_for_request)
|
||||||
request.routes = controller._routes
|
request.routes = controller._routes
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionController
|
module ActionController
|
||||||
module TemplateAssertions # :nodoc:
|
module TemplateAssertions # :nodoc:
|
||||||
def assert_template(options = {}, message = nil)
|
def assert_template(options = {}, message = nil)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "rack/session/abstract/id"
|
require "rack/session/abstract/id"
|
||||||
require "active_support/core_ext/hash/conversions"
|
require "active_support/core_ext/hash/conversions"
|
||||||
require "active_support/core_ext/object/to_query"
|
require "active_support/core_ext/object/to_query"
|
||||||
@ -16,10 +18,10 @@ class Metal
|
|||||||
end
|
end
|
||||||
|
|
||||||
module Live
|
module Live
|
||||||
# Disable controller / rendering threads in tests. User tests can access
|
# Disable controller / rendering threads in tests. User tests can access the
|
||||||
# the database on the main thread, so they could open a txn, then the
|
# database on the main thread, so they could open a txn, then the controller
|
||||||
# controller thread will open a new connection and try to access data
|
# thread will open a new connection and try to access data that's only visible
|
||||||
# that's only visible to the main thread's txn. This is the problem in #23483.
|
# to the main thread's txn. This is the problem in #23483.
|
||||||
silence_redefinition_of_method :new_controller_thread
|
silence_redefinition_of_method :new_controller_thread
|
||||||
def new_controller_thread # :nodoc:
|
def new_controller_thread # :nodoc:
|
||||||
yield
|
yield
|
||||||
@ -29,8 +31,8 @@ def new_controller_thread # :nodoc:
|
|||||||
Buffer.queue_size = nil
|
Buffer.queue_size = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# ActionController::TestCase will be deprecated and moved to a gem in the future.
|
# ActionController::TestCase will be deprecated and moved to a gem in the
|
||||||
# Please use ActionDispatch::IntegrationTest going forward.
|
# future. Please use ActionDispatch::IntegrationTest going forward.
|
||||||
class TestRequest < ActionDispatch::TestRequest # :nodoc:
|
class TestRequest < ActionDispatch::TestRequest # :nodoc:
|
||||||
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
|
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
|
||||||
DEFAULT_ENV.delete "PATH_INFO"
|
DEFAULT_ENV.delete "PATH_INFO"
|
||||||
@ -232,116 +234,125 @@ def load!
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Action Controller Test Case
|
# # Action Controller Test Case
|
||||||
#
|
#
|
||||||
# Superclass for ActionController functional tests. Functional tests allow you to
|
# Superclass for ActionController functional tests. Functional tests allow you
|
||||||
# test a single controller action per test method.
|
# to test a single controller action per test method.
|
||||||
#
|
#
|
||||||
# == Use integration style controller tests over functional style controller tests.
|
# ## Use integration style controller tests over functional style controller tests.
|
||||||
#
|
#
|
||||||
# \Rails discourages the use of functional tests in favor of integration tests
|
# Rails discourages the use of functional tests in favor of integration tests
|
||||||
# (use ActionDispatch::IntegrationTest).
|
# (use ActionDispatch::IntegrationTest).
|
||||||
#
|
#
|
||||||
# New \Rails applications no longer generate functional style controller tests and they should
|
# New Rails applications no longer generate functional style controller tests
|
||||||
# only be used for backward compatibility. Integration style controller tests perform actual
|
# and they should only be used for backward compatibility. Integration style
|
||||||
# requests, whereas functional style controller tests merely simulate a request. Besides,
|
# controller tests perform actual requests, whereas functional style controller
|
||||||
# integration tests are as fast as functional tests and provide lot of helpers such as +as+,
|
# tests merely simulate a request. Besides, integration tests are as fast as
|
||||||
# +parsed_body+ for effective testing of controller actions including even API endpoints.
|
# functional tests and provide lot of helpers such as `as`, `parsed_body` for
|
||||||
|
# effective testing of controller actions including even API endpoints.
|
||||||
#
|
#
|
||||||
# == Basic example
|
# ## Basic example
|
||||||
#
|
#
|
||||||
# Functional tests are written as follows:
|
# Functional tests are written as follows:
|
||||||
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+, or +head+ method to simulate
|
# 1. First, one uses the `get`, `post`, `patch`, `put`, `delete`, or `head`
|
||||||
# an HTTP request.
|
# method to simulate an HTTP request.
|
||||||
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
|
# 2. Then, one asserts whether the current state is as expected. "State" can be
|
||||||
# the controller's HTTP response, the database contents, etc.
|
# anything: the controller's HTTP response, the database contents, etc.
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# For example:
|
# For example:
|
||||||
#
|
#
|
||||||
# class BooksControllerTest < ActionController::TestCase
|
# class BooksControllerTest < ActionController::TestCase
|
||||||
# def test_create
|
# def test_create
|
||||||
# # Simulate a POST response with the given HTTP parameters.
|
# # Simulate a POST response with the given HTTP parameters.
|
||||||
# post(:create, params: { book: { title: "Love Hina" }})
|
# post(:create, params: { book: { title: "Love Hina" }})
|
||||||
#
|
#
|
||||||
# # Asserts that the controller tried to redirect us to
|
# # Asserts that the controller tried to redirect us to
|
||||||
# # the created book's URI.
|
# # the created book's URI.
|
||||||
# assert_response :found
|
# assert_response :found
|
||||||
#
|
#
|
||||||
# # Asserts that the controller really put the book in the database.
|
# # Asserts that the controller really put the book in the database.
|
||||||
# assert_not_nil Book.find_by(title: "Love Hina")
|
# assert_not_nil Book.find_by(title: "Love Hina")
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
#
|
#
|
||||||
# You can also send a real document in the simulated HTTP request.
|
# You can also send a real document in the simulated HTTP request.
|
||||||
#
|
#
|
||||||
# def test_create
|
# def test_create
|
||||||
# json = {book: { title: "Love Hina" }}.to_json
|
# json = {book: { title: "Love Hina" }}.to_json
|
||||||
# post :create, body: json
|
# post :create, body: json
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# == Special instance variables
|
# ## Special instance variables
|
||||||
#
|
#
|
||||||
# ActionController::TestCase will also automatically provide the following instance
|
# ActionController::TestCase will also automatically provide the following
|
||||||
# variables for use in the tests:
|
# instance variables for use in the tests:
|
||||||
#
|
#
|
||||||
# <b>@controller</b>::
|
# **@controller**
|
||||||
# The controller instance that will be tested.
|
# : The controller instance that will be tested.
|
||||||
# <b>@request</b>::
|
# **@request**
|
||||||
# An ActionController::TestRequest, representing the current HTTP
|
# : An ActionController::TestRequest, representing the current HTTP request.
|
||||||
# request. You can modify this object before sending the HTTP request. For example,
|
# You can modify this object before sending the HTTP request. For example,
|
||||||
# you might want to set some session properties before sending a GET request.
|
# you might want to set some session properties before sending a GET
|
||||||
# <b>@response</b>::
|
# request.
|
||||||
# An ActionDispatch::TestResponse object, representing the response
|
# **@response**
|
||||||
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
|
# : An ActionDispatch::TestResponse object, representing the response of the
|
||||||
# after calling +post+. If the various assert methods are not sufficient, then you
|
# last HTTP response. In the above example, `@response` becomes valid after
|
||||||
# may use this object to inspect the HTTP response in detail.
|
# calling `post`. If the various assert methods are not sufficient, then you
|
||||||
|
# may use this object to inspect the HTTP response in detail.
|
||||||
#
|
#
|
||||||
# == Controller is automatically inferred
|
#
|
||||||
|
# ## Controller is automatically inferred
|
||||||
#
|
#
|
||||||
# ActionController::TestCase will automatically infer the controller under test
|
# ActionController::TestCase will automatically infer the controller under test
|
||||||
# from the test class name. If the controller cannot be inferred from the test
|
# from the test class name. If the controller cannot be inferred from the test
|
||||||
# class name, you can explicitly set it with +tests+.
|
# class name, you can explicitly set it with `tests`.
|
||||||
#
|
#
|
||||||
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
|
||||||
# tests WidgetController
|
# tests WidgetController
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# == \Testing controller internals
|
# ## Testing controller internals
|
||||||
#
|
#
|
||||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
# In addition to these specific assertions, you also have easy access to various
|
||||||
# can be used against. These collections are:
|
# collections that the regular test/unit assertions can be used against. These
|
||||||
|
# collections are:
|
||||||
|
#
|
||||||
|
# * session: Objects being saved in the session.
|
||||||
|
# * flash: The flash objects currently in the session.
|
||||||
|
# * cookies: Cookies being sent to the user on this request.
|
||||||
#
|
#
|
||||||
# * session: Objects being saved in the session.
|
|
||||||
# * flash: The flash objects currently in the session.
|
|
||||||
# * cookies: \Cookies being sent to the user on this request.
|
|
||||||
#
|
#
|
||||||
# These collections can be used just like any other hash:
|
# These collections can be used just like any other hash:
|
||||||
#
|
#
|
||||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
||||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
# assert flash.empty? # makes sure that there's nothing in the flash
|
||||||
#
|
#
|
||||||
# On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
|
# On top of the collections, you have the complete URL that a given action
|
||||||
|
# redirected to available in `redirect_to_url`.
|
||||||
#
|
#
|
||||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
# For redirects within the same controller, you can even call follow_redirect
|
||||||
# action call which can then be asserted against.
|
# and the redirect will be followed, triggering another action call which can
|
||||||
|
# then be asserted against.
|
||||||
#
|
#
|
||||||
# == Manipulating session and cookie variables
|
# ## Manipulating session and cookie variables
|
||||||
#
|
#
|
||||||
# Sometimes you need to set up the session and cookie variables for a test.
|
# Sometimes you need to set up the session and cookie variables for a test. To
|
||||||
# To do this just assign a value to the session or cookie collection:
|
# do this just assign a value to the session or cookie collection:
|
||||||
#
|
#
|
||||||
# session[:key] = "value"
|
# session[:key] = "value"
|
||||||
# cookies[:key] = "value"
|
# cookies[:key] = "value"
|
||||||
#
|
#
|
||||||
# To clear the cookies for a test just clear the cookie collection:
|
# To clear the cookies for a test just clear the cookie collection:
|
||||||
#
|
#
|
||||||
# cookies.clear
|
# cookies.clear
|
||||||
#
|
#
|
||||||
# == \Testing named routes
|
# ## Testing named routes
|
||||||
#
|
#
|
||||||
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
|
# If you're using named routes, they can be easily tested using the original
|
||||||
|
# named routes' methods straight in the test case.
|
||||||
#
|
#
|
||||||
# assert_redirected_to page_url(title: 'foo')
|
# assert_redirected_to page_url(title: 'foo')
|
||||||
class TestCase < ActiveSupport::TestCase
|
class TestCase < ActiveSupport::TestCase
|
||||||
singleton_class.attr_accessor :executor_around_each_request
|
singleton_class.attr_accessor :executor_around_each_request
|
||||||
|
|
||||||
@ -354,12 +365,12 @@ module Behavior
|
|||||||
attr_reader :response, :request
|
attr_reader :response, :request
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Sets the controller class name. Useful if the name can't be inferred from test class.
|
# Sets the controller class name. Useful if the name can't be inferred from test
|
||||||
# Normalizes +controller_class+ before using.
|
# class. Normalizes `controller_class` before using.
|
||||||
#
|
#
|
||||||
# tests WidgetController
|
# tests WidgetController
|
||||||
# tests :widget
|
# tests :widget
|
||||||
# tests 'widget'
|
# tests 'widget'
|
||||||
def tests(controller_class)
|
def tests(controller_class)
|
||||||
case controller_class
|
case controller_class
|
||||||
when String, Symbol
|
when String, Symbol
|
||||||
@ -392,21 +403,24 @@ def determine_default_controller_class(name)
|
|||||||
|
|
||||||
# Simulate a GET request with the given parameters.
|
# Simulate a GET request with the given parameters.
|
||||||
#
|
#
|
||||||
# - +action+: The controller action to call.
|
# * `action`: The controller action to call.
|
||||||
# - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
|
# * `params`: The hash with HTTP parameters that you want to pass. This may be
|
||||||
# - +body+: The request body with a string that is appropriately encoded
|
# `nil`.
|
||||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
# * `body`: The request body with a string that is appropriately encoded
|
||||||
# - +session+: A hash of parameters to store in the session. This may be +nil+.
|
# (`application/x-www-form-urlencoded` or `multipart/form-data`).
|
||||||
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
|
# * `session`: A hash of parameters to store in the session. This may be
|
||||||
|
# `nil`.
|
||||||
|
# * `flash`: A hash of parameters to store in the flash. This may be `nil`.
|
||||||
#
|
#
|
||||||
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
|
|
||||||
# +post+, +patch+, +put+, +delete+, and +head+.
|
|
||||||
# Example sending parameters, session, and setting a flash message:
|
|
||||||
#
|
#
|
||||||
# get :show,
|
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with `post`,
|
||||||
# params: { id: 7 },
|
# `patch`, `put`, `delete`, and `head`. Example sending parameters, session, and
|
||||||
# session: { user_id: 1 },
|
# setting a flash message:
|
||||||
# flash: { notice: 'This is flash message' }
|
#
|
||||||
|
# get :show,
|
||||||
|
# params: { id: 7 },
|
||||||
|
# session: { user_id: 1 },
|
||||||
|
# flash: { notice: 'This is flash message' }
|
||||||
#
|
#
|
||||||
# Note that the request method is not verified. The different methods are
|
# Note that the request method is not verified. The different methods are
|
||||||
# available to make the tests more expressive.
|
# available to make the tests more expressive.
|
||||||
@ -417,67 +431,71 @@ def get(action, **args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Simulate a POST request with the given parameters and set/volley the response.
|
# Simulate a POST request with the given parameters and set/volley the response.
|
||||||
# See +get+ for more details.
|
# See `get` for more details.
|
||||||
def post(action, **args)
|
def post(action, **args)
|
||||||
process(action, method: "POST", **args)
|
process(action, method: "POST", **args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Simulate a PATCH request with the given parameters and set/volley the response.
|
# Simulate a PATCH request with the given parameters and set/volley the
|
||||||
# See +get+ for more details.
|
# response. See `get` for more details.
|
||||||
def patch(action, **args)
|
def patch(action, **args)
|
||||||
process(action, method: "PATCH", **args)
|
process(action, method: "PATCH", **args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Simulate a PUT request with the given parameters and set/volley the response.
|
# Simulate a PUT request with the given parameters and set/volley the response.
|
||||||
# See +get+ for more details.
|
# See `get` for more details.
|
||||||
def put(action, **args)
|
def put(action, **args)
|
||||||
process(action, method: "PUT", **args)
|
process(action, method: "PUT", **args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Simulate a DELETE request with the given parameters and set/volley the response.
|
# Simulate a DELETE request with the given parameters and set/volley the
|
||||||
# See +get+ for more details.
|
# response. See `get` for more details.
|
||||||
def delete(action, **args)
|
def delete(action, **args)
|
||||||
process(action, method: "DELETE", **args)
|
process(action, method: "DELETE", **args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Simulate a HEAD request with the given parameters and set/volley the response.
|
# Simulate a HEAD request with the given parameters and set/volley the response.
|
||||||
# See +get+ for more details.
|
# See `get` for more details.
|
||||||
def head(action, **args)
|
def head(action, **args)
|
||||||
process(action, method: "HEAD", **args)
|
process(action, method: "HEAD", **args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Simulate an HTTP request to +action+ by specifying request method,
|
# Simulate an HTTP request to `action` by specifying request method, parameters
|
||||||
# parameters and set/volley the response.
|
# and set/volley the response.
|
||||||
#
|
#
|
||||||
# - +action+: The controller action to call.
|
# * `action`: The controller action to call.
|
||||||
# - +method+: Request method used to send the HTTP request. Possible values
|
# * `method`: Request method used to send the HTTP request. Possible values
|
||||||
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
|
# are `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `HEAD`. Defaults to `GET`.
|
||||||
# - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
|
# Can be a symbol.
|
||||||
# - +body+: The request body with a string that is appropriately encoded
|
# * `params`: The hash with HTTP parameters that you want to pass. This may be
|
||||||
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
|
# `nil`.
|
||||||
# - +session+: A hash of parameters to store in the session. This may be +nil+.
|
# * `body`: The request body with a string that is appropriately encoded
|
||||||
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
|
# (`application/x-www-form-urlencoded` or `multipart/form-data`).
|
||||||
# - +format+: Request format. Defaults to +nil+. Can be string or symbol.
|
# * `session`: A hash of parameters to store in the session. This may be
|
||||||
# - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
|
# `nil`.
|
||||||
# to a mime type.
|
# * `flash`: A hash of parameters to store in the flash. This may be `nil`.
|
||||||
|
# * `format`: Request format. Defaults to `nil`. Can be string or symbol.
|
||||||
|
# * `as`: Content type. Defaults to `nil`. Must be a symbol that corresponds
|
||||||
|
# to a mime type.
|
||||||
#
|
#
|
||||||
# Example calling +create+ action and sending two params:
|
|
||||||
#
|
#
|
||||||
# process :create,
|
# Example calling `create` action and sending two params:
|
||||||
# method: 'POST',
|
|
||||||
# params: {
|
|
||||||
# user: { name: 'Gaurish Sharma', email: 'user@example.com' }
|
|
||||||
# },
|
|
||||||
# session: { user_id: 1 },
|
|
||||||
# flash: { notice: 'This is flash message' }
|
|
||||||
#
|
#
|
||||||
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, and +HEAD+ requests
|
# process :create,
|
||||||
# prefer using #get, #post, #patch, #put, #delete and #head methods
|
# method: 'POST',
|
||||||
# respectively which will make tests more expressive.
|
# params: {
|
||||||
|
# user: { name: 'Gaurish Sharma', email: 'user@example.com' }
|
||||||
|
# },
|
||||||
|
# session: { user_id: 1 },
|
||||||
|
# flash: { notice: 'This is flash message' }
|
||||||
|
#
|
||||||
|
# To simulate `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `HEAD` requests
|
||||||
|
# prefer using #get, #post, #patch, #put, #delete and #head methods respectively
|
||||||
|
# which will make tests more expressive.
|
||||||
#
|
#
|
||||||
# It's not recommended to make more than one request in the same test. Instance
|
# It's not recommended to make more than one request in the same test. Instance
|
||||||
# variables that are set in one request will not persist to the next request,
|
# variables that are set in one request will not persist to the next request,
|
||||||
# but it's not guaranteed that all \Rails internal state will be reset. Prefer
|
# but it's not guaranteed that all Rails internal state will be reset. Prefer
|
||||||
# ActionDispatch::IntegrationTest for making multiple requests in the same test.
|
# ActionDispatch::IntegrationTest for making multiple requests in the same test.
|
||||||
#
|
#
|
||||||
# Note that the request method is not verified.
|
# Note that the request method is not verified.
|
||||||
@ -654,8 +672,8 @@ def document_root_element
|
|||||||
end
|
end
|
||||||
|
|
||||||
def check_required_ivars
|
def check_required_ivars
|
||||||
# Check for required instance variables so we can give an
|
# Check for required instance variables so we can give an understandable error
|
||||||
# understandable error message.
|
# message.
|
||||||
[:@routes, :@controller, :@request, :@response].each do |iv_name|
|
[:@routes, :@controller, :@request, :@response].each do |iv_name|
|
||||||
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
|
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
|
||||||
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
|
||||||
|
@ -3,26 +3,27 @@
|
|||||||
#--
|
#--
|
||||||
# Copyright (c) David Heinemeier Hansson
|
# Copyright (c) David Heinemeier Hansson
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# a copy of this software and associated documentation files (the
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# "Software"), to deal in the Software without restriction, including
|
# in the Software without restriction, including without limitation the rights
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
# furnished to do so, subject to the following conditions:
|
||||||
# the following conditions:
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# included in all copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# SOFTWARE.
|
||||||
#++
|
#++
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support"
|
require "active_support"
|
||||||
require "active_support/rails"
|
require "active_support/rails"
|
||||||
require "active_support/core_ext/module/attribute_accessors"
|
require "active_support/core_ext/module/attribute_accessors"
|
||||||
@ -35,14 +36,14 @@ module Rack # :nodoc:
|
|||||||
autoload :Test, "rack/test"
|
autoload :Test, "rack/test"
|
||||||
end
|
end
|
||||||
|
|
||||||
# = Action Dispatch
|
# # Action Dispatch
|
||||||
#
|
#
|
||||||
# Action Dispatch is a module of Action Pack.
|
# Action Dispatch is a module of Action Pack.
|
||||||
#
|
#
|
||||||
# Action Dispatch parses information about the web request, handles
|
# Action Dispatch parses information about the web request, handles routing as
|
||||||
# routing as defined by the user, and does advanced processing related to HTTP
|
# defined by the user, and does advanced processing related to HTTP such as
|
||||||
# such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT
|
# MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies,
|
||||||
# bodies, handling HTTP caching logic, cookies and sessions.
|
# handling HTTP caching logic, cookies and sessions.
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
extend ActiveSupport::Autoload
|
extend ActiveSupport::Autoload
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "rack/version"
|
require "rack/version"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
def self.deprecator # :nodoc:
|
def self.deprecator # :nodoc:
|
||||||
@deprecator ||= ActiveSupport::Deprecation.new
|
@deprecator ||= ActiveSupport::Deprecation.new
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
module Cache
|
module Cache
|
||||||
@ -32,8 +34,8 @@ def etag_matches?(etag)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check response freshness (+Last-Modified+ and ETag) against request
|
# Check response freshness (`Last-Modified` and ETag) against request
|
||||||
# +If-Modified-Since+ and +If-None-Match+ conditions. If both headers are
|
# `If-Modified-Since` and `If-None-Match` conditions. If both headers are
|
||||||
# supplied, both must match, or the request is not considered fresh.
|
# supplied, both must match, or the request is not considered fresh.
|
||||||
def fresh?(response)
|
def fresh?(response)
|
||||||
last_modified = if_modified_since
|
last_modified = if_modified_since
|
||||||
@ -79,25 +81,24 @@ def date=(utc_time)
|
|||||||
set_header DATE, utc_time.httpdate
|
set_header DATE, utc_time.httpdate
|
||||||
end
|
end
|
||||||
|
|
||||||
# This method sets a weak ETag validator on the response so browsers
|
# This method sets a weak ETag validator on the response so browsers and proxies
|
||||||
# and proxies may cache the response, keyed on the ETag. On subsequent
|
# may cache the response, keyed on the ETag. On subsequent requests, the
|
||||||
# requests, the +If-None-Match+ header is set to the cached ETag. If it
|
# `If-None-Match` header is set to the cached ETag. If it matches the current
|
||||||
# matches the current ETag, we can return a <tt>304 Not Modified</tt> response
|
# ETag, we can return a `304 Not Modified` response with no body, letting the
|
||||||
# with no body, letting the browser or proxy know that their cache is
|
# browser or proxy know that their cache is current. Big savings in request time
|
||||||
# current. Big savings in request time and network bandwidth.
|
# and network bandwidth.
|
||||||
#
|
#
|
||||||
# Weak ETags are considered to be semantically equivalent but not
|
# Weak ETags are considered to be semantically equivalent but not byte-for-byte
|
||||||
# byte-for-byte identical. This is perfect for browser caching of HTML
|
# identical. This is perfect for browser caching of HTML pages where we don't
|
||||||
# pages where we don't care about exact equality, just what the user
|
# care about exact equality, just what the user is viewing.
|
||||||
# is viewing.
|
|
||||||
#
|
#
|
||||||
# Strong ETags are considered byte-for-byte identical. They allow a
|
# Strong ETags are considered byte-for-byte identical. They allow a browser or
|
||||||
# browser or proxy cache to support +Range+ requests, useful for paging
|
# proxy cache to support `Range` requests, useful for paging through a PDF file
|
||||||
# through a PDF file or scrubbing through a video. Some CDNs only
|
# or scrubbing through a video. Some CDNs only support strong ETags and will
|
||||||
# support strong ETags and will ignore weak ETags entirely.
|
# ignore weak ETags entirely.
|
||||||
#
|
#
|
||||||
# Weak ETags are what we almost always need, so they're the default.
|
# Weak ETags are what we almost always need, so they're the default. Check out
|
||||||
# Check out #strong_etag= to provide a strong ETag validator.
|
# #strong_etag= to provide a strong ETag validator.
|
||||||
def etag=(weak_validators)
|
def etag=(weak_validators)
|
||||||
self.weak_etag = weak_validators
|
self.weak_etag = weak_validators
|
||||||
end
|
end
|
||||||
@ -112,12 +113,13 @@ def strong_etag=(strong_validators)
|
|||||||
|
|
||||||
def etag?; etag; end
|
def etag?; etag; end
|
||||||
|
|
||||||
# True if an ETag is set, and it's a weak validator (preceded with <tt>W/</tt>).
|
# True if an ETag is set, and it's a weak validator (preceded with `W/`).
|
||||||
def weak_etag?
|
def weak_etag?
|
||||||
etag? && etag.start_with?('W/"')
|
etag? && etag.start_with?('W/"')
|
||||||
end
|
end
|
||||||
|
|
||||||
# True if an ETag is set, and it isn't a weak validator (not preceded with <tt>W/</tt>).
|
# True if an ETag is set, and it isn't a weak validator (not preceded with
|
||||||
|
# `W/`).
|
||||||
def strong_etag?
|
def strong_etag?
|
||||||
etag? && !weak_etag?
|
etag? && !weak_etag?
|
||||||
end
|
end
|
||||||
@ -171,10 +173,9 @@ def prepare_cache_control!
|
|||||||
MUST_REVALIDATE = "must-revalidate"
|
MUST_REVALIDATE = "must-revalidate"
|
||||||
|
|
||||||
def handle_conditional_get!
|
def handle_conditional_get!
|
||||||
# Normally default cache control setting is handled by ETag
|
# Normally default cache control setting is handled by ETag middleware. But, if
|
||||||
# middleware. But, if an etag is already set, the middleware
|
# an etag is already set, the middleware defaults to `no-cache` unless a default
|
||||||
# defaults to `no-cache` unless a default `Cache-Control` value is
|
# `Cache-Control` value is previously set. So, set a default one here.
|
||||||
# previously set. So, set a default one here.
|
|
||||||
if (etag? || last_modified?) && !self._cache_control
|
if (etag? || last_modified?) && !self._cache_control
|
||||||
self._cache_control = DEFAULT_CACHE_CONTROL
|
self._cache_control = DEFAULT_CACHE_CONTROL
|
||||||
end
|
end
|
||||||
@ -186,8 +187,8 @@ def merge_and_normalize_cache_control!(cache_control)
|
|||||||
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
|
return if control.empty? && cache_control.empty? # Let middleware handle default behavior
|
||||||
|
|
||||||
if cache_control.any?
|
if cache_control.any?
|
||||||
# Any caching directive coming from a controller overrides
|
# Any caching directive coming from a controller overrides no-cache/no-store in
|
||||||
# no-cache/no-store in the default Cache-Control header.
|
# the default Cache-Control header.
|
||||||
control.delete(:no_cache)
|
control.delete(:no_cache)
|
||||||
control.delete(:no_store)
|
control.delete(:no_store)
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
class ContentDisposition # :nodoc:
|
class ContentDisposition # :nodoc:
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/object/deep_dup"
|
require "active_support/core_ext/object/deep_dup"
|
||||||
require "active_support/core_ext/array/wrap"
|
require "active_support/core_ext/array/wrap"
|
||||||
|
|
||||||
module ActionDispatch # :nodoc:
|
module ActionDispatch # :nodoc:
|
||||||
# = Action Dispatch Content Security Policy
|
# # Action Dispatch Content Security Policy
|
||||||
#
|
#
|
||||||
# Configures the HTTP
|
# Configures the HTTP [Content-Security-Policy]
|
||||||
# {Content-Security-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy]
|
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
|
||||||
# response header to help protect against XSS and injection attacks.
|
# response header to help protect against XSS and
|
||||||
|
# injection attacks.
|
||||||
#
|
#
|
||||||
# Example global policy:
|
# Example global policy:
|
||||||
#
|
#
|
||||||
# Rails.application.config.content_security_policy do |policy|
|
# Rails.application.config.content_security_policy do |policy|
|
||||||
# policy.default_src :self, :https
|
# policy.default_src :self, :https
|
||||||
# policy.font_src :self, :https, :data
|
# policy.font_src :self, :https, :data
|
||||||
# policy.img_src :self, :https, :data
|
# policy.img_src :self, :https, :data
|
||||||
# policy.object_src :none
|
# policy.object_src :none
|
||||||
# policy.script_src :self, :https
|
# policy.script_src :self, :https
|
||||||
# policy.style_src :self, :https
|
# policy.style_src :self, :https
|
||||||
#
|
#
|
||||||
# # Specify URI for violation reports
|
# # Specify URI for violation reports
|
||||||
# policy.report_uri "/csp-violation-report-endpoint"
|
# policy.report_uri "/csp-violation-report-endpoint"
|
||||||
# end
|
# end
|
||||||
class ContentSecurityPolicy
|
class ContentSecurityPolicy
|
||||||
class Middleware
|
class Middleware
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
@ -32,8 +35,8 @@ def initialize(app)
|
|||||||
def call(env)
|
def call(env)
|
||||||
status, headers, _ = response = @app.call(env)
|
status, headers, _ = response = @app.call(env)
|
||||||
|
|
||||||
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the new
|
# Returning CSP headers with a 304 Not Modified is harmful, since nonces in the
|
||||||
# CSP headers might not match nonces in the cached HTML.
|
# new CSP headers might not match nonces in the cached HTML.
|
||||||
return response if status == 304
|
return response if status == 304
|
||||||
|
|
||||||
return response if policy_present?(headers)
|
return response if policy_present?(headers)
|
||||||
@ -190,14 +193,14 @@ def initialize_copy(other)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify whether to prevent the user agent from loading any assets over
|
# Specify whether to prevent the user agent from loading any assets over HTTP
|
||||||
# HTTP when the page uses HTTPS:
|
# when the page uses HTTPS:
|
||||||
#
|
#
|
||||||
# policy.block_all_mixed_content
|
# policy.block_all_mixed_content
|
||||||
#
|
#
|
||||||
# Pass +false+ to allow it again:
|
# Pass `false` to allow it again:
|
||||||
#
|
#
|
||||||
# policy.block_all_mixed_content false
|
# policy.block_all_mixed_content false
|
||||||
#
|
#
|
||||||
def block_all_mixed_content(enabled = true)
|
def block_all_mixed_content(enabled = true)
|
||||||
if enabled
|
if enabled
|
||||||
@ -209,11 +212,11 @@ def block_all_mixed_content(enabled = true)
|
|||||||
|
|
||||||
# Restricts the set of plugins that can be embedded:
|
# Restricts the set of plugins that can be embedded:
|
||||||
#
|
#
|
||||||
# policy.plugin_types "application/x-shockwave-flash"
|
# policy.plugin_types "application/x-shockwave-flash"
|
||||||
#
|
#
|
||||||
# Leave empty to allow all plugins:
|
# Leave empty to allow all plugins:
|
||||||
#
|
#
|
||||||
# policy.plugin_types
|
# policy.plugin_types
|
||||||
#
|
#
|
||||||
def plugin_types(*types)
|
def plugin_types(*types)
|
||||||
if types.first
|
if types.first
|
||||||
@ -223,23 +226,25 @@ def plugin_types(*types)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Enable the {report-uri}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri]
|
# Enable the [report-uri]
|
||||||
# directive. Violation reports will be sent to the specified URI:
|
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri)
|
||||||
|
# directive. Violation reports will be sent to the
|
||||||
|
# specified URI:
|
||||||
#
|
#
|
||||||
# policy.report_uri "/csp-violation-report-endpoint"
|
# policy.report_uri "/csp-violation-report-endpoint"
|
||||||
#
|
#
|
||||||
def report_uri(uri)
|
def report_uri(uri)
|
||||||
@directives["report-uri"] = [uri]
|
@directives["report-uri"] = [uri]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify asset types for which {Subresource Integrity}[https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity]
|
# Specify asset types for which [Subresource Integrity]
|
||||||
# is required:
|
# (https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) is required:
|
||||||
#
|
#
|
||||||
# policy.require_sri_for :script, :style
|
# policy.require_sri_for :script, :style
|
||||||
#
|
#
|
||||||
# Leave empty to not require Subresource Integrity:
|
# Leave empty to not require Subresource Integrity:
|
||||||
#
|
#
|
||||||
# policy.require_sri_for
|
# policy.require_sri_for
|
||||||
#
|
#
|
||||||
def require_sri_for(*types)
|
def require_sri_for(*types)
|
||||||
if types.first
|
if types.first
|
||||||
@ -249,18 +254,19 @@ def require_sri_for(*types)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Specify whether a {sandbox}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox]
|
# Specify whether a [sandbox]
|
||||||
|
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox)
|
||||||
# should be enabled for the requested resource:
|
# should be enabled for the requested resource:
|
||||||
#
|
#
|
||||||
# policy.sandbox
|
# policy.sandbox
|
||||||
#
|
#
|
||||||
# Values can be passed as arguments:
|
# Values can be passed as arguments:
|
||||||
#
|
#
|
||||||
# policy.sandbox "allow-scripts", "allow-modals"
|
# policy.sandbox "allow-scripts", "allow-modals"
|
||||||
#
|
#
|
||||||
# Pass +false+ to disable the sandbox:
|
# Pass `false` to disable the sandbox:
|
||||||
#
|
#
|
||||||
# policy.sandbox false
|
# policy.sandbox false
|
||||||
#
|
#
|
||||||
def sandbox(*values)
|
def sandbox(*values)
|
||||||
if values.empty?
|
if values.empty?
|
||||||
@ -274,11 +280,11 @@ def sandbox(*values)
|
|||||||
|
|
||||||
# Specify whether user agents should treat any assets over HTTP as HTTPS:
|
# Specify whether user agents should treat any assets over HTTP as HTTPS:
|
||||||
#
|
#
|
||||||
# policy.upgrade_insecure_requests
|
# policy.upgrade_insecure_requests
|
||||||
#
|
#
|
||||||
# Pass +false+ to disable it:
|
# Pass `false` to disable it:
|
||||||
#
|
#
|
||||||
# policy.upgrade_insecure_requests false
|
# policy.upgrade_insecure_requests false
|
||||||
#
|
#
|
||||||
def upgrade_insecure_requests(enabled = true)
|
def upgrade_insecure_requests(enabled = true)
|
||||||
if enabled
|
if enabled
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/parameter_filter"
|
require "active_support/parameter_filter"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
# = Action Dispatch HTTP Filter Parameters
|
# # Action Dispatch HTTP Filter Parameters
|
||||||
#
|
#
|
||||||
# Allows you to specify sensitive query string and POST parameters to filter
|
# Allows you to specify sensitive query string and POST parameters to filter
|
||||||
# from the request log.
|
# from the request log.
|
||||||
#
|
#
|
||||||
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
|
# # Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
|
||||||
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
||||||
#
|
#
|
||||||
# For more information about filter behavior, see ActiveSupport::ParameterFilter.
|
# For more information about filter behavior, see
|
||||||
|
# ActiveSupport::ParameterFilter.
|
||||||
module FilterParameters
|
module FilterParameters
|
||||||
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
||||||
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
NULL_PARAM_FILTER = ActiveSupport::ParameterFilter.new # :nodoc:
|
||||||
@ -43,7 +46,8 @@ def filtered_path
|
|||||||
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the +ActiveSupport::ParameterFilter+ object used to filter in this request.
|
# Returns the `ActiveSupport::ParameterFilter` object used to filter in this
|
||||||
|
# request.
|
||||||
def parameter_filter
|
def parameter_filter
|
||||||
@parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
|
@parameter_filter ||= if has_header?("action_dispatch.parameter_filter")
|
||||||
parameter_filter_for get_header("action_dispatch.parameter_filter")
|
parameter_filter_for get_header("action_dispatch.parameter_filter")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
module FilterRedirect
|
module FilterRedirect
|
||||||
|
@ -1,28 +1,30 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
# = Action Dispatch HTTP \Headers
|
# # Action Dispatch HTTP Headers
|
||||||
#
|
#
|
||||||
# Provides access to the request's HTTP headers from the environment.
|
# Provides access to the request's HTTP headers from the environment.
|
||||||
#
|
#
|
||||||
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
||||||
# headers = ActionDispatch::Http::Headers.from_hash(env)
|
# headers = ActionDispatch::Http::Headers.from_hash(env)
|
||||||
# headers["Content-Type"] # => "text/plain"
|
# headers["Content-Type"] # => "text/plain"
|
||||||
# headers["User-Agent"] # => "curl/7.43.0"
|
# headers["User-Agent"] # => "curl/7.43.0"
|
||||||
#
|
#
|
||||||
# Also note that when headers are mapped to CGI-like variables by the Rack
|
# Also note that when headers are mapped to CGI-like variables by the Rack
|
||||||
# server, both dashes and underscores are converted to underscores. This
|
# server, both dashes and underscores are converted to underscores. This
|
||||||
# ambiguity cannot be resolved at this stage anymore. Both underscores and
|
# ambiguity cannot be resolved at this stage anymore. Both underscores and
|
||||||
# dashes have to be interpreted as if they were originally sent as dashes.
|
# dashes have to be interpreted as if they were originally sent as dashes.
|
||||||
#
|
#
|
||||||
# # GET / HTTP/1.1
|
# # GET / HTTP/1.1
|
||||||
# # ...
|
# # ...
|
||||||
# # User-Agent: curl/7.43.0
|
# # User-Agent: curl/7.43.0
|
||||||
# # X_Custom_Header: token
|
# # X_Custom_Header: token
|
||||||
#
|
#
|
||||||
# headers["X_Custom_Header"] # => nil
|
# headers["X_Custom_Header"] # => nil
|
||||||
# headers["X-Custom-Header"] # => "token"
|
# headers["X-Custom-Header"] # => "token"
|
||||||
class Headers
|
class Headers
|
||||||
CGI_VARIABLES = Set.new(%W[
|
CGI_VARIABLES = Set.new(%W[
|
||||||
AUTH_TYPE
|
AUTH_TYPE
|
||||||
@ -67,7 +69,7 @@ def []=(key, value)
|
|||||||
@req.set_header env_name(key), value
|
@req.set_header env_name(key), value
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add a value to a multivalued header like +Vary+ or +Accept-Encoding+.
|
# Add a value to a multivalued header like `Vary` or `Accept-Encoding`.
|
||||||
def add(key, value)
|
def add(key, value)
|
||||||
@req.add_header env_name(key), value
|
@req.add_header env_name(key), value
|
||||||
end
|
end
|
||||||
@ -81,11 +83,10 @@ def key?(key)
|
|||||||
|
|
||||||
# Returns the value for the given key mapped to @env.
|
# Returns the value for the given key mapped to @env.
|
||||||
#
|
#
|
||||||
# If the key is not found and an optional code block is not provided,
|
# If the key is not found and an optional code block is not provided, raises a
|
||||||
# raises a <tt>KeyError</tt> exception.
|
# `KeyError` exception.
|
||||||
#
|
#
|
||||||
# If the code block is provided, then it will be run and
|
# If the code block is provided, then it will be run and its result returned.
|
||||||
# its result returned.
|
|
||||||
def fetch(key, default = DEFAULT)
|
def fetch(key, default = DEFAULT)
|
||||||
@req.fetch_header(env_name(key)) do
|
@req.fetch_header(env_name(key)) do
|
||||||
return default unless default == DEFAULT
|
return default unless default == DEFAULT
|
||||||
@ -99,16 +100,15 @@ def each(&block)
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Returns a new Http::Headers instance containing the contents of
|
# Returns a new Http::Headers instance containing the contents of
|
||||||
# <tt>headers_or_env</tt> and the original instance.
|
# `headers_or_env` and the original instance.
|
||||||
def merge(headers_or_env)
|
def merge(headers_or_env)
|
||||||
headers = @req.dup.headers
|
headers = @req.dup.headers
|
||||||
headers.merge!(headers_or_env)
|
headers.merge!(headers_or_env)
|
||||||
headers
|
headers
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds the contents of <tt>headers_or_env</tt> to original instance
|
# Adds the contents of `headers_or_env` to original instance entries; duplicate
|
||||||
# entries; duplicate keys are overwritten with the values from
|
# keys are overwritten with the values from `headers_or_env`.
|
||||||
# <tt>headers_or_env</tt>.
|
|
||||||
def merge!(headers_or_env)
|
def merge!(headers_or_env)
|
||||||
headers_or_env.each do |key, value|
|
headers_or_env.each do |key, value|
|
||||||
@req.set_header env_name(key), value
|
@req.set_header env_name(key), value
|
||||||
@ -118,8 +118,8 @@ def merge!(headers_or_env)
|
|||||||
def env; @req.env.dup; end
|
def env; @req.env.dup; end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Converts an HTTP header name to an environment variable name if it is
|
# Converts an HTTP header name to an environment variable name if it is not
|
||||||
# not contained within the headers hash.
|
# contained within the headers hash.
|
||||||
def env_name(key)
|
def env_name(key)
|
||||||
key = key.to_s
|
key = key.to_s
|
||||||
if HTTP_HEADER.match?(key)
|
if HTTP_HEADER.match?(key)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/module/attribute_accessors"
|
require "active_support/core_ext/module/attribute_accessors"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
@ -18,7 +20,7 @@ class InvalidType < ::Mime::Type::InvalidMimeType; end
|
|||||||
mattr_accessor :ignore_accept_header, default: false
|
mattr_accessor :ignore_accept_header, default: false
|
||||||
end
|
end
|
||||||
|
|
||||||
# The MIME type of the HTTP request, such as Mime[:xml].
|
# The MIME type of the HTTP request, such as [Mime](:xml).
|
||||||
def content_mime_type
|
def content_mime_type
|
||||||
fetch_header("action_dispatch.request.content_type") do |k|
|
fetch_header("action_dispatch.request.content_type") do |k|
|
||||||
v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/
|
v = if get_header("CONTENT_TYPE") =~ /^([^,;]*)/
|
||||||
@ -52,11 +54,11 @@ def accepts
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the MIME type for the \format used in the request.
|
# Returns the MIME type for the format used in the request.
|
||||||
#
|
#
|
||||||
# GET /posts/5.xml | request.format => Mime[:xml]
|
# GET /posts/5.xml | request.format => Mime[:xml]
|
||||||
# GET /posts/5.xhtml | request.format => Mime[:html]
|
# GET /posts/5.xhtml | request.format => Mime[:html]
|
||||||
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
|
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
|
||||||
#
|
#
|
||||||
def format(_view_path = nil)
|
def format(_view_path = nil)
|
||||||
formats.first || Mime::NullType.instance
|
formats.first || Mime::NullType.instance
|
||||||
@ -84,7 +86,7 @@ def formats
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the \variant for template.
|
# Sets the variant for template.
|
||||||
def variant=(variant)
|
def variant=(variant)
|
||||||
variant = Array(variant)
|
variant = Array(variant)
|
||||||
|
|
||||||
@ -99,36 +101,37 @@ def variant
|
|||||||
@variant ||= ActiveSupport::ArrayInquirer.new
|
@variant ||= ActiveSupport::ArrayInquirer.new
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the \format by string extension, which can be used to force custom formats
|
# Sets the format by string extension, which can be used to force custom formats
|
||||||
# that are not controlled by the extension.
|
# that are not controlled by the extension.
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# before_action :adjust_format_for_iphone
|
# before_action :adjust_format_for_iphone
|
||||||
#
|
#
|
||||||
# private
|
# private
|
||||||
# def adjust_format_for_iphone
|
# def adjust_format_for_iphone
|
||||||
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
def format=(extension)
|
def format=(extension)
|
||||||
parameters[:format] = extension.to_s
|
parameters[:format] = extension.to_s
|
||||||
set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
|
set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the \formats by string extensions. This differs from #format= by allowing you
|
# Sets the formats by string extensions. This differs from #format= by allowing
|
||||||
# to set multiple, ordered formats, which is useful when you want to have a fallback.
|
# you to set multiple, ordered formats, which is useful when you want to have a
|
||||||
|
# fallback.
|
||||||
#
|
#
|
||||||
# In this example, the +:iphone+ format will be used if it's available, otherwise it'll fall back
|
# In this example, the `:iphone` format will be used if it's available,
|
||||||
# to the +:html+ format.
|
# otherwise it'll fall back to the `:html` format.
|
||||||
#
|
#
|
||||||
# class ApplicationController < ActionController::Base
|
# class ApplicationController < ActionController::Base
|
||||||
# before_action :adjust_format_for_iphone_with_html_fallback
|
# before_action :adjust_format_for_iphone_with_html_fallback
|
||||||
#
|
#
|
||||||
# private
|
# private
|
||||||
# def adjust_format_for_iphone_with_html_fallback
|
# def adjust_format_for_iphone_with_html_fallback
|
||||||
# request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
|
# request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
def formats=(extensions)
|
def formats=(extensions)
|
||||||
parameters[:format] = extensions.first.to_s
|
parameters[:format] = extensions.first.to_s
|
||||||
set_header "action_dispatch.request.formats", extensions.collect { |extension|
|
set_header "action_dispatch.request.formats", extensions.collect { |extension|
|
||||||
@ -154,8 +157,8 @@ def should_apply_vary_header?
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# We use normal content negotiation unless you include */* in your list,
|
# We use normal content negotiation unless you include **/** in your list, in
|
||||||
# in which case we assume you're a browser and send HTML.
|
# which case we assume you're a browser and send HTML.
|
||||||
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
||||||
|
|
||||||
def params_readable?
|
def params_readable?
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "singleton"
|
require "singleton"
|
||||||
|
|
||||||
module Mime
|
module Mime
|
||||||
@ -65,19 +67,20 @@ def fetch(type, &block)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Encapsulates the notion of a MIME type. Can be used at render time, for example, with:
|
# Encapsulates the notion of a MIME type. Can be used at render time, for
|
||||||
|
# example, with:
|
||||||
#
|
#
|
||||||
# class PostsController < ActionController::Base
|
# class PostsController < ActionController::Base
|
||||||
# def show
|
# def show
|
||||||
# @post = Post.find(params[:id])
|
# @post = Post.find(params[:id])
|
||||||
#
|
#
|
||||||
# respond_to do |format|
|
# respond_to do |format|
|
||||||
# format.html
|
# format.html
|
||||||
# format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
|
# format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
|
||||||
# format.xml { render xml: @post }
|
# format.xml { render xml: @post }
|
||||||
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
# end
|
|
||||||
class Type
|
class Type
|
||||||
attr_reader :symbol
|
attr_reader :symbol
|
||||||
|
|
||||||
@ -170,8 +173,9 @@ def lookup_by_extension(extension)
|
|||||||
EXTENSION_LOOKUP[extension.to_s]
|
EXTENSION_LOOKUP[extension.to_s]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for
|
# Registers an alias that's not used on MIME type lookup, but can be referenced
|
||||||
# rendering different HTML versions depending on the user agent, like an iPhone.
|
# directly. Especially useful for rendering different HTML versions depending on
|
||||||
|
# the user agent, like an iPhone.
|
||||||
def register_alias(string, symbol, extension_synonyms = [])
|
def register_alias(string, symbol, extension_synonyms = [])
|
||||||
register(string, symbol, [], extension_synonyms, true)
|
register(string, symbol, [], extension_synonyms, true)
|
||||||
end
|
end
|
||||||
@ -221,11 +225,11 @@ def parse_trailing_star(accept_header)
|
|||||||
parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
|
parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
|
||||||
end
|
end
|
||||||
|
|
||||||
# For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
|
# For an input of `'text'`, returns `[Mime[:json], Mime[:xml], Mime[:ics],
|
||||||
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]</tt>.
|
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]]`.
|
||||||
#
|
#
|
||||||
# For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
|
# For an input of `'application'`, returns `[Mime[:html], Mime[:js], Mime[:xml],
|
||||||
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]</tt>.
|
# Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]]`.
|
||||||
def parse_data_with_trailing_star(type)
|
def parse_data_with_trailing_star(type)
|
||||||
Mime::SET.select { |m| m.match?(type) }
|
Mime::SET.select { |m| m.match?(type) }
|
||||||
end
|
end
|
||||||
@ -234,7 +238,7 @@ def parse_data_with_trailing_star(type)
|
|||||||
#
|
#
|
||||||
# To unregister a MIME type:
|
# To unregister a MIME type:
|
||||||
#
|
#
|
||||||
# Mime::Type.unregister(:mobile)
|
# Mime::Type.unregister(:mobile)
|
||||||
def unregister(symbol)
|
def unregister(symbol)
|
||||||
symbol = symbol.downcase
|
symbol = symbol.downcase
|
||||||
if mime = Mime[symbol]
|
if mime = Mime[symbol]
|
||||||
@ -350,9 +354,9 @@ def all?; true; end
|
|||||||
def html?; true; end
|
def html?; true; end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ALL isn't a real MIME type, so we don't register it for lookup with the
|
# ALL isn't a real MIME type, so we don't register it for lookup with the other
|
||||||
# other concrete types. It's a wildcard match that we use for +respond_to+
|
# concrete types. It's a wildcard match that we use for `respond_to` negotiation
|
||||||
# negotiation internals.
|
# internals.
|
||||||
ALL = AllType.instance
|
ALL = AllType.instance
|
||||||
|
|
||||||
class NullType
|
class NullType
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
# Build list of Mime types for HTTP responses
|
# Build list of Mime types for HTTP responses
|
||||||
# https://www.iana.org/assignments/media-types/
|
# https://www.iana.org/assignments/media-types/
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
|
Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
|
||||||
Mime::Type.register "text/plain", :text, [], %w(txt)
|
Mime::Type.register "text/plain", :text, [], %w(txt)
|
||||||
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
|
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
module Parameters
|
module Parameters
|
||||||
@ -14,8 +16,8 @@ module Parameters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Raised when raw data from the request cannot be parsed by the parser
|
# Raised when raw data from the request cannot be parsed by the parser defined
|
||||||
# defined for request's content MIME type.
|
# for request's content MIME type.
|
||||||
class ParseError < StandardError
|
class ParseError < StandardError
|
||||||
def initialize(message = $!.message)
|
def initialize(message = $!.message)
|
||||||
super(message)
|
super(message)
|
||||||
@ -34,8 +36,8 @@ class << self
|
|||||||
module ClassMethods
|
module ClassMethods
|
||||||
# Configure the parameter parser for a given MIME type.
|
# Configure the parameter parser for a given MIME type.
|
||||||
#
|
#
|
||||||
# It accepts a hash where the key is the symbol of the MIME type
|
# It accepts a hash where the key is the symbol of the MIME type and the value
|
||||||
# and the value is a proc.
|
# is a proc.
|
||||||
#
|
#
|
||||||
# original_parsers = ActionDispatch::Request.parameter_parsers
|
# original_parsers = ActionDispatch::Request.parameter_parsers
|
||||||
# xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} }
|
# xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} }
|
||||||
@ -46,7 +48,7 @@ def parameter_parsers=(parsers)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns both GET and POST \parameters in a single hash.
|
# Returns both GET and POST parameters in a single hash.
|
||||||
def parameters
|
def parameters
|
||||||
params = get_header("action_dispatch.request.parameters")
|
params = get_header("action_dispatch.request.parameters")
|
||||||
return params if params
|
return params if params
|
||||||
@ -66,8 +68,8 @@ def path_parameters=(parameters) # :nodoc:
|
|||||||
delete_header("action_dispatch.request.parameters")
|
delete_header("action_dispatch.request.parameters")
|
||||||
|
|
||||||
parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
|
parameters = Request::Utils.set_binary_encoding(self, parameters, parameters[:controller], parameters[:action])
|
||||||
# If any of the path parameters has an invalid encoding then
|
# If any of the path parameters has an invalid encoding then raise since it's
|
||||||
# raise since it's likely to trigger errors further on.
|
# likely to trigger errors further on.
|
||||||
Request::Utils.check_param_encoding(parameters)
|
Request::Utils.check_param_encoding(parameters)
|
||||||
|
|
||||||
set_header PARAMETERS_KEY, parameters
|
set_header PARAMETERS_KEY, parameters
|
||||||
@ -75,10 +77,10 @@ def path_parameters=(parameters) # :nodoc:
|
|||||||
raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
|
raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a hash with the \parameters used to form the \path of the request.
|
# Returns a hash with the parameters used to form the path of the request.
|
||||||
# Returned hash keys are symbols:
|
# Returned hash keys are symbols:
|
||||||
#
|
#
|
||||||
# { action: "my_action", controller: "my_controller" }
|
# { action: "my_action", controller: "my_controller" }
|
||||||
def path_parameters
|
def path_parameters
|
||||||
get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
|
get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {})
|
||||||
end
|
end
|
||||||
|
@ -1,31 +1,33 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/object/deep_dup"
|
require "active_support/core_ext/object/deep_dup"
|
||||||
|
|
||||||
module ActionDispatch # :nodoc:
|
module ActionDispatch # :nodoc:
|
||||||
# = Action Dispatch \PermissionsPolicy
|
# # Action Dispatch PermissionsPolicy
|
||||||
#
|
#
|
||||||
# Configures the HTTP
|
# Configures the HTTP
|
||||||
# {Feature-Policy}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy]
|
# [Feature-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy)
|
||||||
# response header to specify which browser features the current document and
|
# response header to specify which browser features the current
|
||||||
# its iframes can use.
|
# document and its iframes can use.
|
||||||
#
|
#
|
||||||
# Example global policy:
|
# Example global policy:
|
||||||
#
|
#
|
||||||
# Rails.application.config.permissions_policy do |policy|
|
# Rails.application.config.permissions_policy do |policy|
|
||||||
# policy.camera :none
|
# policy.camera :none
|
||||||
# policy.gyroscope :none
|
# policy.gyroscope :none
|
||||||
# policy.microphone :none
|
# policy.microphone :none
|
||||||
# policy.usb :none
|
# policy.usb :none
|
||||||
# policy.fullscreen :self
|
# policy.fullscreen :self
|
||||||
# policy.payment :self, "https://secure.example.com"
|
# policy.payment :self, "https://secure.example.com"
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# The Feature-Policy header has been renamed to Permissions-Policy.
|
# The Feature-Policy header has been renamed to Permissions-Policy. The
|
||||||
# The Permissions-Policy requires a different implementation and isn't
|
# Permissions-Policy requires a different implementation and isn't yet supported
|
||||||
# yet supported by all browsers. To avoid having to rename this
|
# by all browsers. To avoid having to rename this middleware in the future we
|
||||||
# middleware in the future we use the new name for the middleware but
|
# use the new name for the middleware but keep the old header name and
|
||||||
# keep the old header name and implementation for now.
|
# implementation for now.
|
||||||
class PermissionsPolicy
|
class PermissionsPolicy
|
||||||
class Middleware
|
class Middleware
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
# :enddoc:
|
# :enddoc:
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "rack/cache"
|
require "rack/cache"
|
||||||
require "rack/cache/context"
|
require "rack/cache/context"
|
||||||
require "active_support/cache"
|
require "active_support/cache"
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "stringio"
|
require "stringio"
|
||||||
|
|
||||||
require "active_support/inflector"
|
require "active_support/inflector"
|
||||||
@ -102,26 +104,26 @@ def controller_class_for(name)
|
|||||||
|
|
||||||
# Returns true if the request has a header matching the given key parameter.
|
# Returns true if the request has a header matching the given key parameter.
|
||||||
#
|
#
|
||||||
# request.key? :ip_spoofing_check # => true
|
# request.key? :ip_spoofing_check # => true
|
||||||
def key?(key)
|
def key?(key)
|
||||||
has_header? key
|
has_header? key
|
||||||
end
|
end
|
||||||
|
|
||||||
# HTTP methods from {RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1}[https://www.ietf.org/rfc/rfc2616.txt]
|
# HTTP methods from [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1](https://www.ietf.org/rfc/rfc2616.txt)
|
||||||
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
|
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
|
||||||
# HTTP methods from {RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV}[https://www.ietf.org/rfc/rfc2518.txt]
|
# HTTP methods from [RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV](https://www.ietf.org/rfc/rfc2518.txt)
|
||||||
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
|
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
|
||||||
# HTTP methods from {RFC 3253: Versioning Extensions to WebDAV}[https://www.ietf.org/rfc/rfc3253.txt]
|
# HTTP methods from [RFC 3253: Versioning Extensions to WebDAV](https://www.ietf.org/rfc/rfc3253.txt)
|
||||||
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
|
RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
|
||||||
# HTTP methods from {RFC 3648: WebDAV Ordered Collections Protocol}[https://www.ietf.org/rfc/rfc3648.txt]
|
# HTTP methods from [RFC 3648: WebDAV Ordered Collections Protocol](https://www.ietf.org/rfc/rfc3648.txt)
|
||||||
RFC3648 = %w(ORDERPATCH)
|
RFC3648 = %w(ORDERPATCH)
|
||||||
# HTTP methods from {RFC 3744: WebDAV Access Control Protocol}[https://www.ietf.org/rfc/rfc3744.txt]
|
# HTTP methods from [RFC 3744: WebDAV Access Control Protocol](https://www.ietf.org/rfc/rfc3744.txt)
|
||||||
RFC3744 = %w(ACL)
|
RFC3744 = %w(ACL)
|
||||||
# HTTP methods from {RFC 5323: WebDAV SEARCH}[https://www.ietf.org/rfc/rfc5323.txt]
|
# HTTP methods from [RFC 5323: WebDAV SEARCH](https://www.ietf.org/rfc/rfc5323.txt)
|
||||||
RFC5323 = %w(SEARCH)
|
RFC5323 = %w(SEARCH)
|
||||||
# HTTP methods from {RFC 4791: Calendaring Extensions to WebDAV}[https://www.ietf.org/rfc/rfc4791.txt]
|
# HTTP methods from [RFC 4791: Calendaring Extensions to WebDAV](https://www.ietf.org/rfc/rfc4791.txt)
|
||||||
RFC4791 = %w(MKCALENDAR)
|
RFC4791 = %w(MKCALENDAR)
|
||||||
# HTTP methods from {RFC 5789: PATCH Method for HTTP}[https://www.ietf.org/rfc/rfc5789.txt]
|
# HTTP methods from [RFC 5789: PATCH Method for HTTP](https://www.ietf.org/rfc/rfc5789.txt)
|
||||||
RFC5789 = %w(PATCH)
|
RFC5789 = %w(PATCH)
|
||||||
|
|
||||||
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
|
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
|
||||||
@ -135,20 +137,19 @@ def key?(key)
|
|||||||
|
|
||||||
alias raw_request_method request_method # :nodoc:
|
alias raw_request_method request_method # :nodoc:
|
||||||
|
|
||||||
# Returns the HTTP \method that the application should see.
|
# Returns the HTTP method that the application should see. In the case where the
|
||||||
# In the case where the \method was overridden by a middleware
|
# method was overridden by a middleware (for instance, if a HEAD request was
|
||||||
# (for instance, if a HEAD request was converted to a GET,
|
# converted to a GET, or if a _method parameter was used to determine the method
|
||||||
# or if a _method parameter was used to determine the \method
|
# the application should use), this method returns the overridden value, not the
|
||||||
# the application should use), this \method returns the overridden
|
# original.
|
||||||
# value, not the original.
|
|
||||||
def request_method
|
def request_method
|
||||||
@request_method ||= check_method(super)
|
@request_method ||= check_method(super)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the URI pattern of the matched route for the request,
|
# Returns the URI pattern of the matched route for the request, using the same
|
||||||
# using the same format as <tt>bin/rails routes</tt>:
|
# format as `bin/rails routes`:
|
||||||
#
|
#
|
||||||
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
|
# request.route_uri_pattern # => "/:controller(/:action(/:id))(.:format)"
|
||||||
def route_uri_pattern
|
def route_uri_pattern
|
||||||
get_header("action_dispatch.route_uri_pattern")
|
get_header("action_dispatch.route_uri_pattern")
|
||||||
end
|
end
|
||||||
@ -196,12 +197,11 @@ def request_method_symbol
|
|||||||
HTTP_METHOD_LOOKUP[request_method]
|
HTTP_METHOD_LOOKUP[request_method]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the original value of the environment's REQUEST_METHOD,
|
# Returns the original value of the environment's REQUEST_METHOD, even if it was
|
||||||
# even if it was overridden by middleware. See #request_method for
|
# overridden by middleware. See #request_method for more information.
|
||||||
# more information.
|
|
||||||
#
|
#
|
||||||
# For debugging purposes, when called with arguments this method will
|
# For debugging purposes, when called with arguments this method will fall back
|
||||||
# fall back to Object#method
|
# to Object#method
|
||||||
def method(*args)
|
def method(*args)
|
||||||
if args.empty?
|
if args.empty?
|
||||||
@method ||= check_method(
|
@method ||= check_method(
|
||||||
@ -221,60 +221,61 @@ def method_symbol
|
|||||||
|
|
||||||
# Provides access to the request's HTTP headers, for example:
|
# Provides access to the request's HTTP headers, for example:
|
||||||
#
|
#
|
||||||
# request.headers["Content-Type"] # => "text/plain"
|
# request.headers["Content-Type"] # => "text/plain"
|
||||||
def headers
|
def headers
|
||||||
@headers ||= Http::Headers.new(self)
|
@headers ||= Http::Headers.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Early Hints is an HTTP/2 status code that indicates hints to help a client start
|
# Early Hints is an HTTP/2 status code that indicates hints to help a client
|
||||||
# making preparations for processing the final response.
|
# start making preparations for processing the final response.
|
||||||
#
|
#
|
||||||
# If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
|
# If the env contains `rack.early_hints` then the server accepts HTTP2 push for
|
||||||
|
# Link headers.
|
||||||
#
|
#
|
||||||
# The +send_early_hints+ method accepts a hash of links as follows:
|
# The `send_early_hints` method accepts a hash of links as follows:
|
||||||
#
|
#
|
||||||
# send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
|
# send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
|
||||||
#
|
#
|
||||||
# If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
|
# If you are using `javascript_include_tag` or `stylesheet_link_tag` the Early
|
||||||
# Early Hints headers are included by default if supported.
|
# Hints headers are included by default if supported.
|
||||||
def send_early_hints(links)
|
def send_early_hints(links)
|
||||||
env["rack.early_hints"]&.call(links)
|
env["rack.early_hints"]&.call(links)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a +String+ with the last requested path including their params.
|
# Returns a `String` with the last requested path including their params.
|
||||||
#
|
#
|
||||||
# # get '/foo'
|
# # get '/foo'
|
||||||
# request.original_fullpath # => '/foo'
|
# request.original_fullpath # => '/foo'
|
||||||
#
|
#
|
||||||
# # get '/foo?bar'
|
# # get '/foo?bar'
|
||||||
# request.original_fullpath # => '/foo?bar'
|
# request.original_fullpath # => '/foo?bar'
|
||||||
def original_fullpath
|
def original_fullpath
|
||||||
@original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
|
@original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the +String+ full path including params of the last URL requested.
|
# Returns the `String` full path including params of the last URL requested.
|
||||||
#
|
#
|
||||||
# # get "/articles"
|
# # get "/articles"
|
||||||
# request.fullpath # => "/articles"
|
# request.fullpath # => "/articles"
|
||||||
#
|
#
|
||||||
# # get "/articles?page=2"
|
# # get "/articles?page=2"
|
||||||
# request.fullpath # => "/articles?page=2"
|
# request.fullpath # => "/articles?page=2"
|
||||||
def fullpath
|
def fullpath
|
||||||
@fullpath ||= super
|
@fullpath ||= super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the original request URL as a +String+.
|
# Returns the original request URL as a `String`.
|
||||||
#
|
#
|
||||||
# # get "/articles?page=2"
|
# # get "/articles?page=2"
|
||||||
# request.original_url # => "http://www.example.com/articles?page=2"
|
# request.original_url # => "http://www.example.com/articles?page=2"
|
||||||
def original_url
|
def original_url
|
||||||
base_url + original_fullpath
|
base_url + original_fullpath
|
||||||
end
|
end
|
||||||
|
|
||||||
# The +String+ MIME type of the request.
|
# The `String` MIME type of the request.
|
||||||
#
|
#
|
||||||
# # get "/articles"
|
# # get "/articles"
|
||||||
# request.media_type # => "application/x-www-form-urlencoded"
|
# request.media_type # => "application/x-www-form-urlencoded"
|
||||||
def media_type
|
def media_type
|
||||||
content_mime_type&.to_s
|
content_mime_type&.to_s
|
||||||
end
|
end
|
||||||
@ -285,7 +286,7 @@ def content_length
|
|||||||
super.to_i
|
super.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the +X-Requested-With+ header contains "XMLHttpRequest"
|
# Returns true if the `X-Requested-With` header contains "XMLHttpRequest"
|
||||||
# (case-insensitive), which may need to be manually added depending on the
|
# (case-insensitive), which may need to be manually added depending on the
|
||||||
# choice of JavaScript libraries and frameworks.
|
# choice of JavaScript libraries and frameworks.
|
||||||
def xml_http_request?
|
def xml_http_request?
|
||||||
@ -293,13 +294,13 @@ def xml_http_request?
|
|||||||
end
|
end
|
||||||
alias :xhr? :xml_http_request?
|
alias :xhr? :xml_http_request?
|
||||||
|
|
||||||
# Returns the IP address of client as a +String+.
|
# Returns the IP address of client as a `String`.
|
||||||
def ip
|
def ip
|
||||||
@ip ||= super
|
@ip ||= super
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the IP address of client as a +String+,
|
# Returns the IP address of client as a `String`, usually set by the RemoteIp
|
||||||
# usually set by the RemoteIp middleware.
|
# middleware.
|
||||||
def remote_ip
|
def remote_ip
|
||||||
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
|
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
|
||||||
end
|
end
|
||||||
@ -311,12 +312,14 @@ def remote_ip=(remote_ip)
|
|||||||
|
|
||||||
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc:
|
ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id" # :nodoc:
|
||||||
|
|
||||||
# Returns the unique request id, which is based on either the +X-Request-Id+ header that can
|
# Returns the unique request id, which is based on either the `X-Request-Id`
|
||||||
# be generated by a firewall, load balancer, or web server, or by the RequestId middleware
|
# header that can be generated by a firewall, load balancer, or web server, or
|
||||||
# (which sets the +action_dispatch.request_id+ environment variable).
|
# by the RequestId middleware (which sets the `action_dispatch.request_id`
|
||||||
|
# environment variable).
|
||||||
#
|
#
|
||||||
# This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
|
# This unique ID is useful for tracing a request from end-to-end as part of
|
||||||
# This relies on the Rack variable set by the ActionDispatch::RequestId middleware.
|
# logging or debugging. This relies on the Rack variable set by the
|
||||||
|
# ActionDispatch::RequestId middleware.
|
||||||
def request_id
|
def request_id
|
||||||
get_header ACTION_DISPATCH_REQUEST_ID
|
get_header ACTION_DISPATCH_REQUEST_ID
|
||||||
end
|
end
|
||||||
@ -332,8 +335,8 @@ def server_software
|
|||||||
(get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil
|
(get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read the request \body. This is useful for web services that need to
|
# Read the request body. This is useful for web services that need to work with
|
||||||
# work with raw requests directly.
|
# raw requests directly.
|
||||||
def raw_post
|
def raw_post
|
||||||
unless has_header? "RAW_POST_DATA"
|
unless has_header? "RAW_POST_DATA"
|
||||||
set_header("RAW_POST_DATA", read_body_stream)
|
set_header("RAW_POST_DATA", read_body_stream)
|
||||||
@ -353,14 +356,13 @@ def body
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determine whether the request body contains form-data by checking
|
# Determine whether the request body contains form-data by checking the request
|
||||||
# the request +Content-Type+ for one of the media-types:
|
# `Content-Type` for one of the media-types: `application/x-www-form-urlencoded`
|
||||||
# +application/x-www-form-urlencoded+ or +multipart/form-data+. The
|
# or `multipart/form-data`. The list of form-data media types can be modified
|
||||||
# list of form-data media types can be modified through the
|
# through the `FORM_DATA_MEDIA_TYPES` array.
|
||||||
# +FORM_DATA_MEDIA_TYPES+ array.
|
|
||||||
#
|
#
|
||||||
# A request body is not assumed to contain form-data when no
|
# A request body is not assumed to contain form-data when no `Content-Type`
|
||||||
# +Content-Type+ header is provided and the request_method is POST.
|
# header is provided and the request_method is POST.
|
||||||
def form_data?
|
def form_data?
|
||||||
FORM_DATA_MEDIA_TYPES.include?(media_type)
|
FORM_DATA_MEDIA_TYPES.include?(media_type)
|
||||||
end
|
end
|
||||||
@ -413,8 +415,8 @@ def POST
|
|||||||
end
|
end
|
||||||
alias :request_parameters :POST
|
alias :request_parameters :POST
|
||||||
|
|
||||||
# Returns the authorization header regardless of whether it was specified directly or through one of the
|
# Returns the authorization header regardless of whether it was specified
|
||||||
# proxy alternatives.
|
# directly or through one of the proxy alternatives.
|
||||||
def authorization
|
def authorization
|
||||||
get_header("HTTP_AUTHORIZATION") ||
|
get_header("HTTP_AUTHORIZATION") ||
|
||||||
get_header("X-HTTP_AUTHORIZATION") ||
|
get_header("X-HTTP_AUTHORIZATION") ||
|
||||||
|
@ -1,38 +1,40 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/module/attribute_accessors"
|
require "active_support/core_ext/module/attribute_accessors"
|
||||||
require "action_dispatch/http/filter_redirect"
|
require "action_dispatch/http/filter_redirect"
|
||||||
require "action_dispatch/http/cache"
|
require "action_dispatch/http/cache"
|
||||||
require "monitor"
|
require "monitor"
|
||||||
|
|
||||||
module ActionDispatch # :nodoc:
|
module ActionDispatch # :nodoc:
|
||||||
# = Action Dispatch \Response
|
# # Action Dispatch Response
|
||||||
#
|
#
|
||||||
# Represents an HTTP response generated by a controller action. Use it to
|
# Represents an HTTP response generated by a controller action. Use it to
|
||||||
# retrieve the current state of the response, or customize the response. It can
|
# retrieve the current state of the response, or customize the response. It can
|
||||||
# either represent a real HTTP response (i.e. one that is meant to be sent
|
# either represent a real HTTP response (i.e. one that is meant to be sent back
|
||||||
# back to the web browser) or a TestResponse (i.e. one that is generated
|
# to the web browser) or a TestResponse (i.e. one that is generated from
|
||||||
# from integration tests).
|
# integration tests).
|
||||||
#
|
#
|
||||||
# The \Response object for the current request is exposed on controllers as
|
# The Response object for the current request is exposed on controllers as
|
||||||
# ActionController::Metal#response. ActionController::Metal also provides a
|
# ActionController::Metal#response. ActionController::Metal also provides a few
|
||||||
# few additional methods that delegate to attributes of the \Response such as
|
# additional methods that delegate to attributes of the Response such as
|
||||||
# ActionController::Metal#headers.
|
# ActionController::Metal#headers.
|
||||||
#
|
#
|
||||||
# Integration tests will likely also want to inspect responses in
|
# Integration tests will likely also want to inspect responses in more detail.
|
||||||
# more detail. Methods such as Integration::RequestHelpers#get
|
# Methods such as Integration::RequestHelpers#get and
|
||||||
# and Integration::RequestHelpers#post return instances of
|
# Integration::RequestHelpers#post return instances of TestResponse (which
|
||||||
# TestResponse (which inherits from \Response) for this purpose.
|
# inherits from Response) for this purpose.
|
||||||
#
|
#
|
||||||
# For example, the following demo integration test prints the body of the
|
# For example, the following demo integration test prints the body of the
|
||||||
# controller response to the console:
|
# controller response to the console:
|
||||||
#
|
#
|
||||||
# class DemoControllerTest < ActionDispatch::IntegrationTest
|
# class DemoControllerTest < ActionDispatch::IntegrationTest
|
||||||
# def test_print_root_path_to_console
|
# def test_print_root_path_to_console
|
||||||
# get('/')
|
# get('/')
|
||||||
# puts response.body
|
# puts response.body
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
class Response
|
class Response
|
||||||
begin
|
begin
|
||||||
# For `Rack::Headers` (Rack 3+):
|
# For `Rack::Headers` (Rack 3+):
|
||||||
@ -55,17 +57,17 @@ class Response
|
|||||||
|
|
||||||
# The headers for the response.
|
# The headers for the response.
|
||||||
#
|
#
|
||||||
# header["Content-Type"] # => "text/plain"
|
# header["Content-Type"] # => "text/plain"
|
||||||
# header["Content-Type"] = "application/json"
|
# header["Content-Type"] = "application/json"
|
||||||
# header["Content-Type"] # => "application/json"
|
# header["Content-Type"] # => "application/json"
|
||||||
#
|
#
|
||||||
# Also aliased as +headers+.
|
# Also aliased as `headers`.
|
||||||
#
|
#
|
||||||
# headers["Content-Type"] # => "text/plain"
|
# headers["Content-Type"] # => "text/plain"
|
||||||
# headers["Content-Type"] = "application/json"
|
# headers["Content-Type"] = "application/json"
|
||||||
# headers["Content-Type"] # => "application/json"
|
# headers["Content-Type"] # => "application/json"
|
||||||
#
|
#
|
||||||
# Also aliased as +header+ for compatibility.
|
# Also aliased as `header` for compatibility.
|
||||||
attr_reader :headers
|
attr_reader :headers
|
||||||
|
|
||||||
alias_method :header, :headers
|
alias_method :header, :headers
|
||||||
@ -234,13 +236,13 @@ def status=(status)
|
|||||||
@status = Rack::Utils.status_code(status)
|
@status = Rack::Utils.status_code(status)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the HTTP response's content MIME type. For example, in the controller
|
# Sets the HTTP response's content MIME type. For example, in the controller you
|
||||||
# you could write this:
|
# could write this:
|
||||||
#
|
#
|
||||||
# response.content_type = "text/plain"
|
# response.content_type = "text/plain"
|
||||||
#
|
#
|
||||||
# If a character set has been defined for this response (see charset=) then
|
# If a character set has been defined for this response (see charset=) then the
|
||||||
# the character set information will also be included in the content type
|
# character set information will also be included in the content type
|
||||||
# information.
|
# information.
|
||||||
def content_type=(content_type)
|
def content_type=(content_type)
|
||||||
return unless content_type
|
return unless content_type
|
||||||
@ -267,11 +269,11 @@ def sending_file=(v)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sets the HTTP character set. In case of +nil+ parameter
|
# Sets the HTTP character set. In case of `nil` parameter it sets the charset to
|
||||||
# it sets the charset to +default_charset+.
|
# `default_charset`.
|
||||||
#
|
#
|
||||||
# response.charset = 'utf-16' # => 'utf-16'
|
# response.charset = 'utf-16' # => 'utf-16'
|
||||||
# response.charset = nil # => 'utf-8'
|
# response.charset = nil # => 'utf-8'
|
||||||
def charset=(charset)
|
def charset=(charset)
|
||||||
content_type = parsed_content_type_header.mime_type
|
content_type = parsed_content_type_header.mime_type
|
||||||
if false == charset
|
if false == charset
|
||||||
@ -281,8 +283,8 @@ def charset=(charset)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The charset of the response. HTML wants to know the encoding of the
|
# The charset of the response. HTML wants to know the encoding of the content
|
||||||
# content you're giving them, so we need to send that along.
|
# you're giving them, so we need to send that along.
|
||||||
def charset
|
def charset
|
||||||
header_info = parsed_content_type_header
|
header_info = parsed_content_type_header
|
||||||
header_info.charset || self.class.default_charset
|
header_info.charset || self.class.default_charset
|
||||||
@ -293,26 +295,26 @@ def response_code
|
|||||||
@status
|
@status
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a string to ensure compatibility with +Net::HTTPResponse+.
|
# Returns a string to ensure compatibility with `Net::HTTPResponse`.
|
||||||
def code
|
def code
|
||||||
@status.to_s
|
@status.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the corresponding message for the current HTTP status code:
|
# Returns the corresponding message for the current HTTP status code:
|
||||||
#
|
#
|
||||||
# response.status = 200
|
# response.status = 200
|
||||||
# response.message # => "OK"
|
# response.message # => "OK"
|
||||||
#
|
#
|
||||||
# response.status = 404
|
# response.status = 404
|
||||||
# response.message # => "Not Found"
|
# response.message # => "Not Found"
|
||||||
#
|
#
|
||||||
def message
|
def message
|
||||||
Rack::Utils::HTTP_STATUS_CODES[@status]
|
Rack::Utils::HTTP_STATUS_CODES[@status]
|
||||||
end
|
end
|
||||||
alias_method :status_message, :message
|
alias_method :status_message, :message
|
||||||
|
|
||||||
# Returns the content of the response as a string. This contains the contents
|
# Returns the content of the response as a string. This contains the contents of
|
||||||
# of any calls to <tt>render</tt>.
|
# any calls to `render`.
|
||||||
def body
|
def body
|
||||||
@stream.body
|
@stream.body
|
||||||
end
|
end
|
||||||
@ -332,9 +334,9 @@ def body=(body)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Avoid having to pass an open file handle as the response body.
|
# Avoid having to pass an open file handle as the response body. Rack::Sendfile
|
||||||
# Rack::Sendfile will usually intercept the response and uses
|
# will usually intercept the response and uses the path directly, so there is no
|
||||||
# the path directly, so there is no reason to open the file.
|
# reason to open the file.
|
||||||
class FileBody # :nodoc:
|
class FileBody # :nodoc:
|
||||||
attr_reader :to_path
|
attr_reader :to_path
|
||||||
|
|
||||||
@ -356,7 +358,7 @@ def each
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Send the file stored at +path+ as the response body.
|
# Send the file stored at `path` as the response body.
|
||||||
def send_file(path)
|
def send_file(path)
|
||||||
commit!
|
commit!
|
||||||
@stream = FileBody.new(path)
|
@stream = FileBody.new(path)
|
||||||
@ -383,17 +385,16 @@ def abort
|
|||||||
if stream.respond_to?(:abort)
|
if stream.respond_to?(:abort)
|
||||||
stream.abort
|
stream.abort
|
||||||
elsif stream.respond_to?(:close)
|
elsif stream.respond_to?(:close)
|
||||||
# `stream.close` should really be reserved for a close from the
|
# `stream.close` should really be reserved for a close from the other direction,
|
||||||
# other direction, but we must fall back to it for
|
# but we must fall back to it for compatibility.
|
||||||
# compatibility.
|
|
||||||
stream.close
|
stream.close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Turns the Response into a Rack-compatible array of the status, headers,
|
# Turns the Response into a Rack-compatible array of the status, headers, and
|
||||||
# and body. Allows explicit splatting:
|
# body. Allows explicit splatting:
|
||||||
#
|
#
|
||||||
# status, headers, body = *response
|
# status, headers, body = *response
|
||||||
def to_a
|
def to_a
|
||||||
commit!
|
commit!
|
||||||
rack_response @status, @headers.to_hash
|
rack_response @status, @headers.to_hash
|
||||||
@ -402,7 +403,7 @@ def to_a
|
|||||||
|
|
||||||
# Returns the response cookies, converted to a Hash of (name => value) pairs
|
# Returns the response cookies, converted to a Hash of (name => value) pairs
|
||||||
#
|
#
|
||||||
# assert_equal 'AuthorOfNewPage', r.cookies['author']
|
# assert_equal 'AuthorOfNewPage', r.cookies['author']
|
||||||
def cookies
|
def cookies
|
||||||
cookies = {}
|
cookies = {}
|
||||||
if header = get_header(SET_COOKIE)
|
if header = get_header(SET_COOKIE)
|
||||||
@ -456,11 +457,10 @@ def before_committed
|
|||||||
end
|
end
|
||||||
|
|
||||||
def before_sending
|
def before_sending
|
||||||
# Normally we've already committed by now, but it's possible
|
# Normally we've already committed by now, but it's possible (e.g., if the
|
||||||
# (e.g., if the controller action tries to read back its own
|
# controller action tries to read back its own response) to get here before
|
||||||
# response) to get here before that. In that case, we must force
|
# that. In that case, we must force an "early" commit: we're about to freeze the
|
||||||
# an "early" commit: we're about to freeze the headers, so this is
|
# headers, so this is our last chance.
|
||||||
# our last chance.
|
|
||||||
commit! unless committed?
|
commit! unless committed?
|
||||||
|
|
||||||
@request.commit_cookie_jar! unless committed?
|
@request.commit_cookie_jar! unless committed?
|
||||||
@ -488,8 +488,8 @@ def initialize(response)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
# Rack "close" maps to Response#abort, and *not* Response#close
|
# Rack "close" maps to Response#abort, and **not** Response#close (which is used
|
||||||
# (which is used when the controller's finished writing)
|
# when the controller's finished writing)
|
||||||
@response.abort
|
@response.abort
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Http
|
module Http
|
||||||
# = Action Dispatch HTTP \UploadedFile
|
# # Action Dispatch HTTP UploadedFile
|
||||||
#
|
#
|
||||||
# Models uploaded files.
|
# Models uploaded files.
|
||||||
#
|
#
|
||||||
# The actual file is accessible via the +tempfile+ accessor, though some
|
# The actual file is accessible via the `tempfile` accessor, though some of its
|
||||||
# of its interface is available directly for convenience.
|
# interface is available directly for convenience.
|
||||||
#
|
#
|
||||||
# Uploaded files are temporary files whose lifespan is one request. When
|
# Uploaded files are temporary files whose lifespan is one request. When the
|
||||||
# the object is finalized Ruby unlinks the file, so there is no need to
|
# object is finalized Ruby unlinks the file, so there is no need to clean them
|
||||||
# clean them with a separate maintenance task.
|
# with a separate maintenance task.
|
||||||
class UploadedFile
|
class UploadedFile
|
||||||
# The basename of the file in the client.
|
# The basename of the file in the client.
|
||||||
attr_accessor :original_filename
|
attr_accessor :original_filename
|
||||||
@ -19,8 +21,8 @@ class UploadedFile
|
|||||||
# A string with the MIME type of the file.
|
# A string with the MIME type of the file.
|
||||||
attr_accessor :content_type
|
attr_accessor :content_type
|
||||||
|
|
||||||
# A +Tempfile+ object with the actual uploaded file. Note that some of
|
# A `Tempfile` object with the actual uploaded file. Note that some of its
|
||||||
# its interface is available directly.
|
# interface is available directly.
|
||||||
attr_accessor :tempfile
|
attr_accessor :tempfile
|
||||||
|
|
||||||
# A string with the headers of the multipart request.
|
# A string with the headers of the multipart request.
|
||||||
@ -57,42 +59,42 @@ def initialize(hash) # :nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.read+.
|
# Shortcut for `tempfile.read`.
|
||||||
def read(length = nil, buffer = nil)
|
def read(length = nil, buffer = nil)
|
||||||
@tempfile.read(length, buffer)
|
@tempfile.read(length, buffer)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.open+.
|
# Shortcut for `tempfile.open`.
|
||||||
def open
|
def open
|
||||||
@tempfile.open
|
@tempfile.open
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.close+.
|
# Shortcut for `tempfile.close`.
|
||||||
def close(unlink_now = false)
|
def close(unlink_now = false)
|
||||||
@tempfile.close(unlink_now)
|
@tempfile.close(unlink_now)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.path+.
|
# Shortcut for `tempfile.path`.
|
||||||
def path
|
def path
|
||||||
@tempfile.path
|
@tempfile.path
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.to_path+.
|
# Shortcut for `tempfile.to_path`.
|
||||||
def to_path
|
def to_path
|
||||||
@tempfile.to_path
|
@tempfile.to_path
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.rewind+.
|
# Shortcut for `tempfile.rewind`.
|
||||||
def rewind
|
def rewind
|
||||||
@tempfile.rewind
|
@tempfile.rewind
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.size+.
|
# Shortcut for `tempfile.size`.
|
||||||
def size
|
def size
|
||||||
@tempfile.size
|
@tempfile.size
|
||||||
end
|
end
|
||||||
|
|
||||||
# Shortcut for +tempfile.eof?+.
|
# Shortcut for `tempfile.eof?`.
|
||||||
def eof?
|
def eof?
|
||||||
@tempfile.eof?
|
@tempfile.eof?
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "active_support/core_ext/module/attribute_accessors"
|
require "active_support/core_ext/module/attribute_accessors"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
@ -15,20 +17,20 @@ module URL
|
|||||||
class << self
|
class << self
|
||||||
# Returns the domain part of a host given the domain level.
|
# Returns the domain part of a host given the domain level.
|
||||||
#
|
#
|
||||||
# # Top-level domain example
|
# # Top-level domain example
|
||||||
# extract_domain('www.example.com', 1) # => "example.com"
|
# extract_domain('www.example.com', 1) # => "example.com"
|
||||||
# # Second-level domain example
|
# # Second-level domain example
|
||||||
# extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
|
# extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
|
||||||
def extract_domain(host, tld_length)
|
def extract_domain(host, tld_length)
|
||||||
extract_domain_from(host, tld_length) if named_host?(host)
|
extract_domain_from(host, tld_length) if named_host?(host)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the subdomains of a host as an Array given the domain level.
|
# Returns the subdomains of a host as an Array given the domain level.
|
||||||
#
|
#
|
||||||
# # Top-level domain example
|
# # Top-level domain example
|
||||||
# extract_subdomains('www.example.com', 1) # => ["www"]
|
# extract_subdomains('www.example.com', 1) # => ["www"]
|
||||||
# # Second-level domain example
|
# # Second-level domain example
|
||||||
# extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
|
# extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
|
||||||
def extract_subdomains(host, tld_length)
|
def extract_subdomains(host, tld_length)
|
||||||
if named_host?(host)
|
if named_host?(host)
|
||||||
extract_subdomains_from(host, tld_length)
|
extract_subdomains_from(host, tld_length)
|
||||||
@ -39,10 +41,10 @@ def extract_subdomains(host, tld_length)
|
|||||||
|
|
||||||
# Returns the subdomains of a host as a String given the domain level.
|
# Returns the subdomains of a host as a String given the domain level.
|
||||||
#
|
#
|
||||||
# # Top-level domain example
|
# # Top-level domain example
|
||||||
# extract_subdomain('www.example.com', 1) # => "www"
|
# extract_subdomain('www.example.com', 1) # => "www"
|
||||||
# # Second-level domain example
|
# # Second-level domain example
|
||||||
# extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
|
# extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
|
||||||
def extract_subdomain(host, tld_length)
|
def extract_subdomain(host, tld_length)
|
||||||
extract_subdomains(host, tld_length).join(".")
|
extract_subdomains(host, tld_length).join(".")
|
||||||
end
|
end
|
||||||
@ -184,33 +186,33 @@ def initialize
|
|||||||
|
|
||||||
# Returns the complete URL used for this request.
|
# Returns the complete URL used for this request.
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
||||||
# req.url # => "http://example.com"
|
# req.url # => "http://example.com"
|
||||||
def url
|
def url
|
||||||
protocol + host_with_port + fullpath
|
protocol + host_with_port + fullpath
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
||||||
# req.protocol # => "http://"
|
# req.protocol # => "http://"
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
|
||||||
# req.protocol # => "https://"
|
# req.protocol # => "https://"
|
||||||
def protocol
|
def protocol
|
||||||
@protocol ||= ssl? ? "https://" : "http://"
|
@protocol ||= ssl? ? "https://" : "http://"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the \host and port for this request, such as "example.com:8080".
|
# Returns the host and port for this request, such as "example.com:8080".
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
||||||
# req.raw_host_with_port # => "example.com"
|
# req.raw_host_with_port # => "example.com"
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
||||||
# req.raw_host_with_port # => "example.com:80"
|
# req.raw_host_with_port # => "example.com:80"
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.raw_host_with_port # => "example.com:8080"
|
# req.raw_host_with_port # => "example.com:8080"
|
||||||
def raw_host_with_port
|
def raw_host_with_port
|
||||||
if forwarded = x_forwarded_host.presence
|
if forwarded = x_forwarded_host.presence
|
||||||
forwarded.split(/,\s?/).last
|
forwarded.split(/,\s?/).last
|
||||||
@ -221,35 +223,35 @@ def raw_host_with_port
|
|||||||
|
|
||||||
# Returns the host for this request, such as "example.com".
|
# Returns the host for this request, such as "example.com".
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.host # => "example.com"
|
# req.host # => "example.com"
|
||||||
def host
|
def host
|
||||||
raw_host_with_port.sub(/:\d+$/, "")
|
raw_host_with_port.sub(/:\d+$/, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a \host:\port string for this request, such as "example.com" or
|
# Returns a host:port string for this request, such as "example.com" or
|
||||||
# "example.com:8080". Port is only included if it is not a default port
|
# "example.com:8080". Port is only included if it is not a default port (80 or
|
||||||
# (80 or 443)
|
# 443)
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
||||||
# req.host_with_port # => "example.com"
|
# req.host_with_port # => "example.com"
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
||||||
# req.host_with_port # => "example.com"
|
# req.host_with_port # => "example.com"
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.host_with_port # => "example.com:8080"
|
# req.host_with_port # => "example.com:8080"
|
||||||
def host_with_port
|
def host_with_port
|
||||||
"#{host}#{port_string}"
|
"#{host}#{port_string}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the port number of this request as an integer.
|
# Returns the port number of this request as an integer.
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com'
|
||||||
# req.port # => 80
|
# req.port # => 80
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.port # => 8080
|
# req.port # => 8080
|
||||||
def port
|
def port
|
||||||
@port ||= if raw_host_with_port =~ /:(\d+)$/
|
@port ||= if raw_host_with_port =~ /:(\d+)$/
|
||||||
$1.to_i
|
$1.to_i
|
||||||
@ -258,10 +260,10 @@ def port
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the standard \port number for this request's protocol.
|
# Returns the standard port number for this request's protocol.
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.standard_port # => 80
|
# req.standard_port # => 80
|
||||||
def standard_port
|
def standard_port
|
||||||
if "https://" == protocol
|
if "https://" == protocol
|
||||||
443
|
443
|
||||||
@ -272,68 +274,68 @@ def standard_port
|
|||||||
|
|
||||||
# Returns whether this request is using the standard port
|
# Returns whether this request is using the standard port
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
||||||
# req.standard_port? # => true
|
# req.standard_port? # => true
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.standard_port? # => false
|
# req.standard_port? # => false
|
||||||
def standard_port?
|
def standard_port?
|
||||||
port == standard_port
|
port == standard_port
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a number \port suffix like 8080 if the \port number of this request
|
# Returns a number port suffix like 8080 if the port number of this request is
|
||||||
# is not the default HTTP \port 80 or HTTPS \port 443.
|
# not the default HTTP port 80 or HTTPS port 443.
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
||||||
# req.optional_port # => nil
|
# req.optional_port # => nil
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.optional_port # => 8080
|
# req.optional_port # => 8080
|
||||||
def optional_port
|
def optional_port
|
||||||
standard_port? ? nil : port
|
standard_port? ? nil : port
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a string \port suffix, including colon, like ":8080" if the \port
|
# Returns a string port suffix, including colon, like ":8080" if the port number
|
||||||
# number of this request is not the default HTTP \port 80 or HTTPS \port 443.
|
# of this request is not the default HTTP port 80 or HTTPS port 443.
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80'
|
||||||
# req.port_string # => ""
|
# req.port_string # => ""
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
# req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
|
||||||
# req.port_string # => ":8080"
|
# req.port_string # => ":8080"
|
||||||
def port_string
|
def port_string
|
||||||
standard_port? ? "" : ":#{port}"
|
standard_port? ? "" : ":#{port}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the requested port, such as 8080, based on SERVER_PORT
|
# Returns the requested port, such as 8080, based on SERVER_PORT
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
|
# req = ActionDispatch::Request.new 'SERVER_PORT' => '80'
|
||||||
# req.server_port # => 80
|
# req.server_port # => 80
|
||||||
#
|
#
|
||||||
# req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
|
# req = ActionDispatch::Request.new 'SERVER_PORT' => '8080'
|
||||||
# req.server_port # => 8080
|
# req.server_port # => 8080
|
||||||
def server_port
|
def server_port
|
||||||
get_header("SERVER_PORT").to_i
|
get_header("SERVER_PORT").to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
|
# Returns the domain part of a host, such as "rubyonrails.org" in
|
||||||
# a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
# "www.rubyonrails.org". You can specify a different `tld_length`, such as 2 to
|
||||||
|
# catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
|
||||||
def domain(tld_length = @@tld_length)
|
def domain(tld_length = @@tld_length)
|
||||||
ActionDispatch::Http::URL.extract_domain(host, tld_length)
|
ActionDispatch::Http::URL.extract_domain(host, tld_length)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be
|
# Returns all the subdomains as an array, so `["dev", "www"]` would be returned
|
||||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
# for "dev.www.rubyonrails.org". You can specify a different `tld_length`, such
|
||||||
# such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt>
|
# as 2 to catch `["www"]` instead of `["www", "rubyonrails"]` in
|
||||||
# in "www.rubyonrails.co.uk".
|
# "www.rubyonrails.co.uk".
|
||||||
def subdomains(tld_length = @@tld_length)
|
def subdomains(tld_length = @@tld_length)
|
||||||
ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
|
ActionDispatch::Http::URL.extract_subdomains(host, tld_length)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all the \subdomains as a string, so <tt>"dev.www"</tt> would be
|
# Returns all the subdomains as a string, so `"dev.www"` would be returned for
|
||||||
# returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>,
|
# "dev.www.rubyonrails.org". You can specify a different `tld_length`, such as 2
|
||||||
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
|
# to catch `"www"` instead of `"www.rubyonrails"` in "www.rubyonrails.co.uk".
|
||||||
# in "www.rubyonrails.co.uk".
|
|
||||||
def subdomain(tld_length = @@tld_length)
|
def subdomain(tld_length = @@tld_length)
|
||||||
ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
|
ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/journey/router"
|
require "action_dispatch/journey/router"
|
||||||
require "action_dispatch/journey/gtg/builder"
|
require "action_dispatch/journey/gtg/builder"
|
||||||
require "action_dispatch/journey/gtg/simulator"
|
require "action_dispatch/journey/gtg/simulator"
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_controller/metal/exceptions"
|
require "action_controller/metal/exceptions"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
module Journey
|
module Journey
|
||||||
# The Formatter class is used for formatting URLs. For example, parameters
|
# The Formatter class is used for formatting URLs. For example, parameters
|
||||||
# passed to +url_for+ in Rails will eventually call Formatter#generate.
|
# passed to `url_for` in Rails will eventually call Formatter#generate.
|
||||||
class Formatter
|
class Formatter
|
||||||
attr_reader :routes
|
attr_reader :routes
|
||||||
|
|
||||||
@ -66,16 +68,16 @@ def generate(name, options, path_parameters)
|
|||||||
match_route(name, constraints) do |route|
|
match_route(name, constraints) do |route|
|
||||||
parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
|
parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
|
||||||
|
|
||||||
# Skip this route unless a name has been provided or it is a
|
# Skip this route unless a name has been provided or it is a standard Rails
|
||||||
# standard Rails route since we can't determine whether an options
|
# route since we can't determine whether an options hash passed to url_for
|
||||||
# hash passed to url_for matches a Rack application or a redirect.
|
# matches a Rack application or a redirect.
|
||||||
next unless name || route.dispatcher?
|
next unless name || route.dispatcher?
|
||||||
|
|
||||||
missing_keys = missing_keys(route, parameterized_parts)
|
missing_keys = missing_keys(route, parameterized_parts)
|
||||||
next if missing_keys && !missing_keys.empty?
|
next if missing_keys && !missing_keys.empty?
|
||||||
params = options.delete_if do |key, _|
|
params = options.delete_if do |key, _|
|
||||||
# top-level params' normal behavior of generating query_params
|
# top-level params' normal behavior of generating query_params should be
|
||||||
# should be preserved even if the same key is also a bind_param
|
# preserved even if the same key is also a bind_param
|
||||||
parameterized_parts.key?(key) || route.defaults.key?(key) ||
|
parameterized_parts.key?(key) || route.defaults.key?(key) ||
|
||||||
(path_params.key?(key) && !original_options.key?(key))
|
(path_params.key?(key) && !original_options.key?(key))
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/journey/gtg/transition_table"
|
require "action_dispatch/journey/gtg/transition_table"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
@ -66,9 +68,8 @@ def nullable?(node)
|
|||||||
when Nodes::Group
|
when Nodes::Group
|
||||||
true
|
true
|
||||||
when Nodes::Star
|
when Nodes::Star
|
||||||
# the default star regex is /(.+)/ which is NOT nullable
|
# the default star regex is /(.+)/ which is NOT nullable but since different
|
||||||
# but since different constraints can be provided we must
|
# constraints can be provided we must actually check if this is the case or not.
|
||||||
# actually check if this is the case or not.
|
|
||||||
node.regexp.match?("")
|
node.regexp.match?("")
|
||||||
when Nodes::Or
|
when Nodes::Or
|
||||||
node.children.any? { |c| nullable?(c) }
|
node.children.any? { |c| nullable?(c) }
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "strscan"
|
require "strscan"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/journey/nfa/dot"
|
require "action_dispatch/journey/nfa/dot"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
@ -55,8 +57,8 @@ def move(t, full_string, start_index, end_index)
|
|||||||
|
|
||||||
t.each { |s, previous_start|
|
t.each { |s, previous_start|
|
||||||
if previous_start.nil?
|
if previous_start.nil?
|
||||||
# In the simple case of a "default" param regex do this fast-path
|
# In the simple case of a "default" param regex do this fast-path and add all
|
||||||
# and add all next states.
|
# next states.
|
||||||
if token_matches_default_component && states = @stdparam_states[s]
|
if token_matches_default_component && states = @stdparam_states[s]
|
||||||
states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
|
states.each { |re, v| next_states << [v, nil].freeze if !v.nil? }
|
||||||
end
|
end
|
||||||
@ -67,10 +69,10 @@ def move(t, full_string, start_index, end_index)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# For regexes that aren't the "default" style, they may potentially
|
# For regexes that aren't the "default" style, they may potentially not be
|
||||||
# not be terminated by the first "token" [./?], so we need to continue
|
# terminated by the first "token" [./?], so we need to continue to attempt to
|
||||||
# to attempt to match this regexp as well as any successful paths that
|
# match this regexp as well as any successful paths that continue out of it.
|
||||||
# continue out of it. both paths could be valid.
|
# both paths could be valid.
|
||||||
if states = @regexp_states[s]
|
if states = @regexp_states[s]
|
||||||
slice_start = if previous_start.nil?
|
slice_start = if previous_start.nil?
|
||||||
start_index
|
start_index
|
||||||
@ -86,8 +88,8 @@ def move(t, full_string, start_index, end_index)
|
|||||||
next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
|
next_states << [v, nil].freeze if !v.nil? && re.match?(curr_slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
# and regardless, we must continue accepting tokens and retrying this regexp.
|
# and regardless, we must continue accepting tokens and retrying this regexp. we
|
||||||
# we need to remember where we started as well so we can take bigger slices.
|
# need to remember where we started as well so we can take bigger slices.
|
||||||
next_states << [s, slice_start].freeze
|
next_states << [s, slice_start].freeze
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Journey # :nodoc:
|
module Journey # :nodoc:
|
||||||
module NFA # :nodoc:
|
module NFA # :nodoc:
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/journey/visitors"
|
require "action_dispatch/journey/visitors"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
@ -21,9 +23,8 @@ def initialize(tree, formatted)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def requirements=(requirements)
|
def requirements=(requirements)
|
||||||
# inject any regexp requirements for `star` nodes so they can be
|
# inject any regexp requirements for `star` nodes so they can be determined
|
||||||
# determined nullable, which requires knowing if the regex accepts an
|
# nullable, which requires knowing if the regex accepts an empty string.
|
||||||
# empty string.
|
|
||||||
(symbols + stars).each do |node|
|
(symbols + stars).each do |node|
|
||||||
re = requirements[node.to_sym]
|
re = requirements[node.to_sym]
|
||||||
node.regexp = re if re
|
node.regexp = re if re
|
||||||
@ -51,8 +52,8 @@ def visit_tree(formatted)
|
|||||||
stars << node
|
stars << node
|
||||||
|
|
||||||
if formatted != false
|
if formatted != false
|
||||||
# Add a constraint for wildcard route to make it non-greedy and
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
||||||
# match the optional format part of the route by default.
|
# optional format part of the route by default.
|
||||||
wildcard_options[node.name.to_sym] ||= /.+?/m
|
wildcard_options[node.name.to_sym] ||= /.+?/m
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#
|
#
|
||||||
# DO NOT MODIFY!!!!
|
# DO NOT MODIFY!!!!
|
||||||
# This file is automatically generated by Racc 1.4.16
|
# This file is automatically generated by Racc 1.4.16 from
|
||||||
# from Racc grammar file "".
|
# Racc grammar file "".
|
||||||
#
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require 'racc/parser.rb'
|
require 'racc/parser.rb'
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/journey/scanner"
|
require "action_dispatch/journey/scanner"
|
||||||
require "action_dispatch/journey/nodes/node"
|
require "action_dispatch/journey/nodes/node"
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Journey # :nodoc:
|
module Journey # :nodoc:
|
||||||
module Path # :nodoc:
|
module Path # :nodoc:
|
||||||
@ -32,7 +34,8 @@ def eager_load!
|
|||||||
end
|
end
|
||||||
|
|
||||||
def requirements_anchored?
|
def requirements_anchored?
|
||||||
# each required param must not be surrounded by a literal, otherwise it isn't simple to chunk-match the url piecemeal
|
# each required param must not be surrounded by a literal, otherwise it isn't
|
||||||
|
# simple to chunk-match the url piecemeal
|
||||||
terminals = ast.terminals
|
terminals = ast.terminals
|
||||||
|
|
||||||
terminals.each_with_index { |s, index|
|
terminals.each_with_index { |s, index|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
module Journey
|
module Journey
|
||||||
@ -52,7 +54,7 @@ def self.verb_matcher(verb)
|
|||||||
|
|
||||||
##
|
##
|
||||||
# +path+ is a path constraint.
|
# +path+ is a path constraint.
|
||||||
# +constraints+ is a hash of constraints to be applied to this route.
|
# `constraints` is a hash of constraints to be applied to this route.
|
||||||
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
|
def initialize(name:, app: nil, path:, constraints: {}, required_defaults: [], defaults: {}, request_method_match: nil, precedence: 0, scope_options: {}, internal: false, source_location: nil)
|
||||||
@name = name
|
@name = name
|
||||||
@app = app
|
@app = app
|
||||||
@ -82,14 +84,14 @@ def eager_load!
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Needed for `bin/rails routes`. Picks up succinctly defined requirements
|
# Needed for `bin/rails routes`. Picks up succinctly defined requirements for a
|
||||||
# for a route, for example route
|
# route, for example route
|
||||||
#
|
#
|
||||||
# get 'photo/:id', :controller => 'photos', :action => 'show',
|
# get 'photo/:id', :controller => 'photos', :action => 'show',
|
||||||
# :id => /[A-Z]\d{5}/
|
# :id => /[A-Z]\d{5}/
|
||||||
#
|
#
|
||||||
# will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/}
|
# will have {:controller=>"photos", :action=>"show", :[id=>/](A-Z){5}/} as
|
||||||
# as requirements.
|
# requirements.
|
||||||
def requirements
|
def requirements
|
||||||
@defaults.merge(path.requirements).delete_if { |_, v|
|
@defaults.merge(path.requirements).delete_if { |_, v|
|
||||||
/.+?/m == v
|
/.+?/m == v
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "action_dispatch/journey/router/utils"
|
require "action_dispatch/journey/router/utils"
|
||||||
require "action_dispatch/journey/routes"
|
require "action_dispatch/journey/routes"
|
||||||
require "action_dispatch/journey/formatter"
|
require "action_dispatch/journey/formatter"
|
||||||
@ -22,8 +24,8 @@ def initialize(routes)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def eager_load!
|
def eager_load!
|
||||||
# Eagerly trigger the simulator's initialization so
|
# Eagerly trigger the simulator's initialization so it doesn't happen during a
|
||||||
# it doesn't happen during a request cycle.
|
# request cycle.
|
||||||
simulator
|
simulator
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Journey # :nodoc:
|
module Journey # :nodoc:
|
||||||
class Router # :nodoc:
|
class Router # :nodoc:
|
||||||
class Utils # :nodoc:
|
class Utils # :nodoc:
|
||||||
# Normalizes URI path.
|
# Normalizes URI path.
|
||||||
#
|
#
|
||||||
# Strips off trailing slash and ensures there is a leading slash.
|
# Strips off trailing slash and ensures there is a leading slash. Also converts
|
||||||
# Also converts downcase URL encoded string to uppercase.
|
# downcase URL encoded string to uppercase.
|
||||||
#
|
#
|
||||||
# normalize_path("/foo") # => "/foo"
|
# normalize_path("/foo") # => "/foo"
|
||||||
# normalize_path("/foo/") # => "/foo"
|
# normalize_path("/foo/") # => "/foo"
|
||||||
# normalize_path("foo") # => "/foo"
|
# normalize_path("foo") # => "/foo"
|
||||||
# normalize_path("") # => "/"
|
# normalize_path("") # => "/"
|
||||||
# normalize_path("/%ab") # => "/%AB"
|
# normalize_path("/%ab") # => "/%AB"
|
||||||
def self.normalize_path(path)
|
def self.normalize_path(path)
|
||||||
path ||= ""
|
path ||= ""
|
||||||
encoding = path.encoding
|
encoding = path.encoding
|
||||||
@ -28,8 +30,7 @@ def self.normalize_path(path)
|
|||||||
path.force_encoding(encoding)
|
path.force_encoding(encoding)
|
||||||
end
|
end
|
||||||
|
|
||||||
# URI path and fragment escaping
|
# URI path and fragment escaping https://tools.ietf.org/html/rfc3986
|
||||||
# https://tools.ietf.org/html/rfc3986
|
|
||||||
class UriEncoder # :nodoc:
|
class UriEncoder # :nodoc:
|
||||||
ENCODE = "%%%02X"
|
ENCODE = "%%%02X"
|
||||||
US_ASCII = Encoding::US_ASCII
|
US_ASCII = Encoding::US_ASCII
|
||||||
@ -93,8 +94,8 @@ def self.escape_fragment(fragment)
|
|||||||
|
|
||||||
# Replaces any escaped sequences with their unescaped representations.
|
# Replaces any escaped sequences with their unescaped representations.
|
||||||
#
|
#
|
||||||
# uri = "/topics?title=Ruby%20on%20Rails"
|
# uri = "/topics?title=Ruby%20on%20Rails"
|
||||||
# unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
|
# unescape_uri(uri) #=> "/topics?title=Ruby on Rails"
|
||||||
def self.unescape_uri(uri)
|
def self.unescape_uri(uri)
|
||||||
ENCODER.unescape_uri(uri)
|
ENCODER.unescape_uri(uri)
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Journey # :nodoc:
|
module Journey # :nodoc:
|
||||||
# The Routing table. Contains all routes for a system. Routes can be
|
# The Routing table. Contains all routes for a system. Routes can be added to
|
||||||
# added to the table by calling Routes#add_route.
|
# the table by calling Routes#add_route.
|
||||||
class Routes # :nodoc:
|
class Routes # :nodoc:
|
||||||
include Enumerable
|
include Enumerable
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "strscan"
|
require "strscan"
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
@ -33,8 +35,8 @@ def next_token
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards
|
# takes advantage of String @- deduping capabilities in Ruby 2.5 upwards see:
|
||||||
# see: https://bugs.ruby-lang.org/issues/13077
|
# https://bugs.ruby-lang.org/issues/13077
|
||||||
def dedup_scan(regex)
|
def dedup_scan(regex)
|
||||||
r = @ss.scan(regex)
|
r = @ss.scan(regex)
|
||||||
r ? -r : nil
|
r ? -r : nil
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# :stopdoc:
|
# :stopdoc:
|
||||||
module Journey
|
module Journey
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
class LogSubscriber < ActiveSupport::LogSubscriber
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
||||||
def redirect(event)
|
def redirect(event)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
require "uri"
|
require "uri"
|
||||||
require "active_support/actionable_error"
|
require "active_support/actionable_error"
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# = Action Dispatch \AssumeSSL
|
# # Action Dispatch AssumeSSL
|
||||||
#
|
#
|
||||||
# When proxying through a load balancer that terminates SSL, the forwarded request will appear
|
# When proxying through a load balancer that terminates SSL, the forwarded
|
||||||
# as though it's HTTP instead of HTTPS to the application. This makes redirects and cookie
|
# request will appear as though it's HTTP instead of HTTPS to the application.
|
||||||
# security target HTTP instead of HTTPS. This middleware makes the server assume that the
|
# This makes redirects and cookie security target HTTP instead of HTTPS. This
|
||||||
# proxy already terminated SSL, and that the request really is HTTPS.
|
# middleware makes the server assume that the proxy already terminated SSL, and
|
||||||
|
# that the request really is HTTPS.
|
||||||
class AssumeSSL
|
class AssumeSSL
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
@app = app
|
@app = app
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# :markup: markdown
|
||||||
|
|
||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
# = Action Dispatch \Callbacks
|
# # Action Dispatch Callbacks
|
||||||
#
|
#
|
||||||
# Provides callbacks to be executed before and after dispatching the request.
|
# Provides callbacks to be executed before and after dispatching the request.
|
||||||
class Callbacks
|
class Callbacks
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user