actionpack 7.0.8.7 → 7.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +90 -537
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +119 -106
- data/lib/abstract_controller/caching/fragments.rb +51 -52
- data/lib/abstract_controller/caching.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +94 -67
- data/lib/abstract_controller/collector.rb +6 -6
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +121 -91
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
- data/lib/abstract_controller/rendering.rb +14 -13
- data/lib/abstract_controller/translation.rb +12 -30
- data/lib/abstract_controller/url_for.rb +9 -5
- data/lib/abstract_controller.rb +8 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +78 -73
- data/lib/action_controller/base.rb +199 -141
- data/lib/action_controller/caching.rb +16 -11
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +21 -16
- data/lib/action_controller/log_subscriber.rb +19 -5
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
- data/lib/action_controller/metal/conditional_get.rb +187 -174
- data/lib/action_controller/metal/content_security_policy.rb +26 -25
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +65 -54
- data/lib/action_controller/metal/default_headers.rb +6 -2
- data/lib/action_controller/metal/etag_with_flash.rb +4 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
- data/lib/action_controller/metal/exceptions.rb +19 -9
- data/lib/action_controller/metal/flash.rb +12 -10
- data/lib/action_controller/metal/head.rb +20 -16
- data/lib/action_controller/metal/helpers.rb +64 -67
- data/lib/action_controller/metal/http_authentication.rb +212 -199
- data/lib/action_controller/metal/implicit_render.rb +21 -17
- data/lib/action_controller/metal/instrumentation.rb +22 -12
- data/lib/action_controller/metal/live.rb +125 -92
- data/lib/action_controller/metal/logging.rb +6 -4
- data/lib/action_controller/metal/mime_responds.rb +151 -142
- data/lib/action_controller/metal/parameter_encoding.rb +34 -32
- data/lib/action_controller/metal/params_wrapper.rb +58 -58
- data/lib/action_controller/metal/permissions_policy.rb +14 -13
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +110 -84
- data/lib/action_controller/metal/renderers.rb +50 -49
- data/lib/action_controller/metal/rendering.rb +103 -82
- data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
- data/lib/action_controller/metal/rescue.rb +12 -8
- data/lib/action_controller/metal/streaming.rb +174 -132
- data/lib/action_controller/metal/strong_parameters.rb +598 -473
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +23 -14
- data/lib/action_controller/metal.rb +145 -61
- data/lib/action_controller/railtie.rb +25 -9
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +105 -66
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +157 -128
- data/lib/action_controller.rb +17 -3
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +28 -29
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +48 -45
- data/lib/action_dispatch/http/filter_parameters.rb +18 -8
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +23 -21
- data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
- data/lib/action_dispatch/http/mime_type.rb +60 -30
- data/lib/action_dispatch/http/mime_types.rb +5 -1
- data/lib/action_dispatch/http/parameters.rb +12 -10
- data/lib/action_dispatch/http/permissions_policy.rb +32 -27
- data/lib/action_dispatch/http/rack_cache.rb +4 -0
- data/lib/action_dispatch/http/request.rb +132 -79
- data/lib/action_dispatch/http/response.rb +136 -103
- data/lib/action_dispatch/http/upload.rb +19 -15
- data/lib/action_dispatch/http/url.rb +75 -73
- data/lib/action_dispatch/journey/formatter.rb +19 -6
- data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
- data/lib/action_dispatch/journey/nodes/node.rb +6 -5
- data/lib/action_dispatch/journey/parser.rb +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +2 -0
- data/lib/action_dispatch/journey/path/pattern.rb +18 -15
- data/lib/action_dispatch/journey/route.rb +12 -9
- data/lib/action_dispatch/journey/router/utils.rb +16 -15
- data/lib/action_dispatch/journey/router.rb +13 -10
- data/lib/action_dispatch/journey/routes.rb +6 -4
- data/lib/action_dispatch/journey/scanner.rb +4 -2
- data/lib/action_dispatch/journey/visitors.rb +2 -0
- data/lib/action_dispatch/journey.rb +2 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +4 -0
- data/lib/action_dispatch/middleware/cookies.rb +192 -194
- data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
- data/lib/action_dispatch/middleware/debug_view.rb +9 -2
- data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
- data/lib/action_dispatch/middleware/executor.rb +9 -1
- data/lib/action_dispatch/middleware/flash.rb +65 -46
- data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
- data/lib/action_dispatch/middleware/reloader.rb +9 -5
- data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
- data/lib/action_dispatch/middleware/request_id.rb +15 -8
- data/lib/action_dispatch/middleware/server_timing.rb +8 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
- data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
- data/lib/action_dispatch/middleware/ssl.rb +60 -45
- data/lib/action_dispatch/middleware/stack.rb +15 -9
- data/lib/action_dispatch/middleware/static.rb +40 -34
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
- data/lib/action_dispatch/railtie.rb +12 -4
- data/lib/action_dispatch/request/session.rb +39 -27
- data/lib/action_dispatch/request/utils.rb +10 -3
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +59 -9
- data/lib/action_dispatch/routing/mapper.rb +686 -639
- data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
- data/lib/action_dispatch/routing/redirection.rb +52 -38
- data/lib/action_dispatch/routing/route_set.rb +106 -62
- data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
- data/lib/action_dispatch/routing/url_for.rb +131 -122
- data/lib/action_dispatch/routing.rb +152 -150
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +27 -19
- data/lib/action_dispatch/system_testing/driver.rb +16 -22
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +9 -7
- data/lib/action_dispatch/testing/assertions/response.rb +36 -26
- data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
- data/lib/action_dispatch/testing/assertions.rb +5 -1
- data/lib/action_dispatch/testing/integration.rb +240 -229
- data/lib/action_dispatch/testing/request_encoder.rb +6 -1
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +14 -9
- data/lib/action_dispatch/testing/test_request.rb +4 -2
- data/lib/action_dispatch/testing/test_response.rb +34 -19
- data/lib/action_dispatch.rb +52 -21
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +18 -17
- metadata +86 -27
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionController # :nodoc:
|
6
|
+
module AllowBrowser
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Specify the browser versions that will be allowed to access all actions (or
|
11
|
+
# some, as limited by `only:` or `except:`). Only browsers matched in the hash
|
12
|
+
# or named set passed to `versions:` will be blocked if they're below the
|
13
|
+
# versions specified. This means that all other browsers, as well as agents that
|
14
|
+
# aren't reporting a user-agent header, will be allowed access.
|
15
|
+
#
|
16
|
+
# A browser that's blocked will by default be served the file in
|
17
|
+
# public/406-unsupported-browser.html with a HTTP status code of "406 Not
|
18
|
+
# Acceptable".
|
19
|
+
#
|
20
|
+
# In addition to specifically named browser versions, you can also pass
|
21
|
+
# `:modern` as the set to restrict support to browsers natively supporting webp
|
22
|
+
# images, web push, badges, import maps, CSS nesting, and CSS :has. This
|
23
|
+
# includes Safari 17.2+, Chrome 120+, Firefox 121+, Opera 106+.
|
24
|
+
#
|
25
|
+
# You can use https://caniuse.com to check for browser versions supporting the
|
26
|
+
# features you use.
|
27
|
+
#
|
28
|
+
# You can use `ActiveSupport::Notifications` to subscribe to events of browsers
|
29
|
+
# being blocked using the `browser_block.action_controller` event name.
|
30
|
+
#
|
31
|
+
# Examples:
|
32
|
+
#
|
33
|
+
# class ApplicationController < ActionController::Base
|
34
|
+
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has
|
35
|
+
# allow_browser versions: :modern
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class ApplicationController < ActionController::Base
|
39
|
+
# # 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+.
|
40
|
+
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class MessagesController < ApplicationController
|
44
|
+
# # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
|
45
|
+
# allow_browser versions: { opera: 104, chrome: 119 }, only: :show
|
46
|
+
# end
|
47
|
+
def allow_browser(versions:, block: -> { render file: Rails.root.join("public/406-unsupported-browser.html"), layout: false, status: :not_acceptable }, **options)
|
48
|
+
before_action -> { allow_browser(versions: versions, block: block) }, **options
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def allow_browser(versions:, block:)
|
54
|
+
require "useragent"
|
55
|
+
|
56
|
+
if BrowserBlocker.new(request, versions: versions).blocked?
|
57
|
+
ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do
|
58
|
+
instance_exec(&block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class BrowserBlocker # :nodoc:
|
64
|
+
SETS = {
|
65
|
+
modern: { safari: 17.2, chrome: 120, firefox: 121, opera: 106, ie: false }
|
66
|
+
}
|
67
|
+
|
68
|
+
attr_reader :request, :versions
|
69
|
+
|
70
|
+
def initialize(request, versions:)
|
71
|
+
@request, @versions = request, versions
|
72
|
+
end
|
73
|
+
|
74
|
+
def blocked?
|
75
|
+
user_agent_version_reported? && unsupported_browser?
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def parsed_user_agent
|
80
|
+
@parsed_user_agent ||= UserAgent.parse(request.user_agent)
|
81
|
+
end
|
82
|
+
|
83
|
+
def user_agent_version_reported?
|
84
|
+
request.user_agent.present? && parsed_user_agent.version.to_s.present?
|
85
|
+
end
|
86
|
+
|
87
|
+
def unsupported_browser?
|
88
|
+
version_guarded_browser? && version_below_minimum_required? && !bot?
|
89
|
+
end
|
90
|
+
|
91
|
+
def version_guarded_browser?
|
92
|
+
minimum_browser_version_for_browser != nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def bot?
|
96
|
+
parsed_user_agent.bot?
|
97
|
+
end
|
98
|
+
|
99
|
+
def version_below_minimum_required?
|
100
|
+
if minimum_browser_version_for_browser
|
101
|
+
parsed_user_agent.version < UserAgent::Version.new(minimum_browser_version_for_browser.to_s)
|
102
|
+
else
|
103
|
+
true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def minimum_browser_version_for_browser
|
108
|
+
expanded_versions[normalized_browser_name]
|
109
|
+
end
|
110
|
+
|
111
|
+
def expanded_versions
|
112
|
+
@expanded_versions ||= (SETS[versions] || versions).with_indifferent_access
|
113
|
+
end
|
114
|
+
|
115
|
+
def normalized_browser_name
|
116
|
+
case name = parsed_user_agent.browser.downcase
|
117
|
+
when "internet explorer" then "ie"
|
118
|
+
else name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "active_support/core_ext/object/try"
|
4
6
|
require "active_support/core_ext/integer/time"
|
5
7
|
|
@@ -14,116 +16,123 @@ module ActionController
|
|
14
16
|
end
|
15
17
|
|
16
18
|
module ClassMethods
|
17
|
-
# Allows you to consider additional controller-wide information when generating
|
18
|
-
# For example, if you serve pages tailored depending on who's logged in
|
19
|
-
# may want to add the current user id to be part of the ETag
|
20
|
-
# of cached pages.
|
19
|
+
# Allows you to consider additional controller-wide information when generating
|
20
|
+
# an ETag. For example, if you serve pages tailored depending on who's logged in
|
21
|
+
# at the moment, you may want to add the current user id to be part of the ETag
|
22
|
+
# to prevent unauthorized displaying of cached pages.
|
21
23
|
#
|
22
|
-
#
|
23
|
-
#
|
24
|
+
# class InvoicesController < ApplicationController
|
25
|
+
# etag { current_user&.id }
|
24
26
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
27
|
+
# def show
|
28
|
+
# # Etag will differ even for the same invoice when it's viewed by a different current_user
|
29
|
+
# @invoice = Invoice.find(params[:id])
|
30
|
+
# fresh_when etag: @invoice
|
31
|
+
# end
|
29
32
|
# end
|
30
|
-
# end
|
31
33
|
def etag(&etagger)
|
32
34
|
self.etaggers += [etagger]
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
# Sets the
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# Sets a "weak" ETag validator on the response. See the
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
38
|
+
# Sets the `etag`, `last_modified`, or both on the response, and renders a `304
|
39
|
+
# Not Modified` response if the request is already fresh.
|
40
|
+
#
|
41
|
+
# #### Options
|
42
|
+
#
|
43
|
+
# `:etag`
|
44
|
+
# : Sets a "weak" ETag validator on the response. See the `:weak_etag` option.
|
45
|
+
#
|
46
|
+
# `:weak_etag`
|
47
|
+
# : Sets a "weak" ETag validator on the response. Requests that specify an
|
48
|
+
# `If-None-Match` header may receive a `304 Not Modified` response if the
|
49
|
+
# ETag matches exactly.
|
50
|
+
#
|
51
|
+
# : A weak ETag indicates semantic equivalence, not byte-for-byte equality, so
|
52
|
+
# they're good for caching HTML pages in browser caches. They can't be used
|
53
|
+
# for responses that must be byte-identical, like serving `Range` requests
|
54
|
+
# within a PDF file.
|
55
|
+
#
|
56
|
+
# `:strong_etag`
|
57
|
+
# : Sets a "strong" ETag validator on the response. Requests that specify an
|
58
|
+
# `If-None-Match` header may receive a `304 Not Modified` response if the
|
59
|
+
# ETag matches exactly.
|
60
|
+
#
|
61
|
+
# : A strong ETag implies exact equality -- the response must match byte for
|
62
|
+
# byte. This is necessary for serving `Range` requests within a large video
|
63
|
+
# or PDF file, for example, or for compatibility with some CDNs that don't
|
64
|
+
# support weak ETags.
|
65
|
+
#
|
66
|
+
# `:last_modified`
|
67
|
+
# : Sets a "weak" last-update validator on the response. Subsequent requests
|
68
|
+
# that specify an `If-Modified-Since` header may receive a `304 Not
|
69
|
+
# Modified` response if `last_modified` <= `If-Modified-Since`.
|
70
|
+
#
|
71
|
+
# `:public`
|
72
|
+
# : By default the `Cache-Control` header is private. Set this option to
|
73
|
+
# `true` if you want your application to be cacheable by other devices, such
|
74
|
+
# as proxy caches.
|
75
|
+
#
|
76
|
+
# `:cache_control`
|
77
|
+
# : When given, will overwrite an existing `Cache-Control` header. For a list
|
78
|
+
# of `Cache-Control` directives, see the [article on
|
79
|
+
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
|
80
|
+
#
|
81
|
+
# `:template`
|
82
|
+
# : By default, the template digest for the current controller/action is
|
83
|
+
# included in ETags. If the action renders a different template, you can
|
84
|
+
# include its digest instead. If the action doesn't render a template at
|
85
|
+
# all, you can pass `template: false` to skip any attempt to check for a
|
86
|
+
# template digest.
|
87
|
+
#
|
88
|
+
#
|
89
|
+
# #### Examples
|
90
|
+
#
|
91
|
+
# def show
|
92
|
+
# @article = Article.find(params[:id])
|
93
|
+
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# This will send a `304 Not Modified` response if the request specifies a
|
97
|
+
# matching ETag and `If-Modified-Since` header. Otherwise, it will render the
|
98
|
+
# `show` template.
|
90
99
|
#
|
91
100
|
# You can also just pass a record:
|
92
101
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
102
|
+
# def show
|
103
|
+
# @article = Article.find(params[:id])
|
104
|
+
# fresh_when(@article)
|
105
|
+
# end
|
97
106
|
#
|
98
|
-
#
|
99
|
-
# record's
|
107
|
+
# `etag` will be set to the record, and `last_modified` will be set to the
|
108
|
+
# record's `updated_at`.
|
100
109
|
#
|
101
|
-
# You can also pass an object that responds to
|
102
|
-
#
|
110
|
+
# You can also pass an object that responds to `maximum`, such as a collection
|
111
|
+
# of records:
|
103
112
|
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
113
|
+
# def index
|
114
|
+
# @articles = Article.all
|
115
|
+
# fresh_when(@articles)
|
116
|
+
# end
|
108
117
|
#
|
109
|
-
# In this case,
|
110
|
-
#
|
111
|
-
#
|
118
|
+
# In this case, `etag` will be set to the collection, and `last_modified` will
|
119
|
+
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
|
120
|
+
# record).
|
112
121
|
#
|
113
|
-
# When passing a record or a collection, you can still specify other
|
114
|
-
#
|
122
|
+
# When passing a record or a collection, you can still specify other options,
|
123
|
+
# such as `:public` and `:cache_control`:
|
115
124
|
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
125
|
+
# def show
|
126
|
+
# @article = Article.find(params[:id])
|
127
|
+
# fresh_when(@article, public: true, cache_control: { no_cache: true })
|
128
|
+
# end
|
120
129
|
#
|
121
|
-
# The above will set
|
130
|
+
# The above will set `Cache-Control: public, no-cache` in the response.
|
122
131
|
#
|
123
132
|
# When rendering a different template than the controller/action's default
|
124
133
|
# template, you can indicate which digest to include in the ETag:
|
125
134
|
#
|
126
|
-
#
|
135
|
+
# before_action { fresh_when @article, template: "widgets/show" }
|
127
136
|
#
|
128
137
|
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
|
129
138
|
response.cache_control.delete(:no_store)
|
@@ -145,131 +154,135 @@ module ActionController
|
|
145
154
|
head :not_modified if request.fresh?(response)
|
146
155
|
end
|
147
156
|
|
148
|
-
# Sets the
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
157
|
+
# Sets the `etag` and/or `last_modified` on the response and checks them against
|
158
|
+
# the request. If the request doesn't match the provided options, it is
|
159
|
+
# considered stale, and the response should be rendered from scratch. Otherwise,
|
160
|
+
# it is fresh, and a `304 Not Modified` is sent.
|
152
161
|
#
|
153
|
-
#
|
162
|
+
# #### Options
|
154
163
|
#
|
155
164
|
# See #fresh_when for supported options.
|
156
165
|
#
|
157
|
-
#
|
166
|
+
# #### Examples
|
158
167
|
#
|
159
|
-
#
|
160
|
-
#
|
168
|
+
# def show
|
169
|
+
# @article = Article.find(params[:id])
|
161
170
|
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
171
|
+
# if stale?(etag: @article, last_modified: @article.updated_at)
|
172
|
+
# @statistics = @article.really_expensive_call
|
173
|
+
# respond_to do |format|
|
174
|
+
# # all the supported formats
|
175
|
+
# end
|
166
176
|
# end
|
167
177
|
# end
|
168
|
-
# end
|
169
178
|
#
|
170
179
|
# You can also just pass a record:
|
171
180
|
#
|
172
|
-
#
|
173
|
-
#
|
181
|
+
# def show
|
182
|
+
# @article = Article.find(params[:id])
|
174
183
|
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
184
|
+
# if stale?(@article)
|
185
|
+
# @statistics = @article.really_expensive_call
|
186
|
+
# respond_to do |format|
|
187
|
+
# # all the supported formats
|
188
|
+
# end
|
179
189
|
# end
|
180
190
|
# end
|
181
|
-
# end
|
182
191
|
#
|
183
|
-
#
|
184
|
-
# record's
|
192
|
+
# `etag` will be set to the record, and `last_modified` will be set to the
|
193
|
+
# record's `updated_at`.
|
185
194
|
#
|
186
|
-
# You can also pass an object that responds to
|
187
|
-
#
|
195
|
+
# You can also pass an object that responds to `maximum`, such as a collection
|
196
|
+
# of records:
|
188
197
|
#
|
189
|
-
#
|
190
|
-
#
|
198
|
+
# def index
|
199
|
+
# @articles = Article.all
|
191
200
|
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
201
|
+
# if stale?(@articles)
|
202
|
+
# @statistics = @articles.really_expensive_call
|
203
|
+
# respond_to do |format|
|
204
|
+
# # all the supported formats
|
205
|
+
# end
|
196
206
|
# end
|
197
207
|
# end
|
198
|
-
# end
|
199
208
|
#
|
200
|
-
# In this case,
|
201
|
-
#
|
202
|
-
#
|
209
|
+
# In this case, `etag` will be set to the collection, and `last_modified` will
|
210
|
+
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
|
211
|
+
# record).
|
203
212
|
#
|
204
|
-
# When passing a record or a collection, you can still specify other
|
205
|
-
#
|
213
|
+
# When passing a record or a collection, you can still specify other options,
|
214
|
+
# such as `:public` and `:cache_control`:
|
206
215
|
#
|
207
|
-
#
|
208
|
-
#
|
216
|
+
# def show
|
217
|
+
# @article = Article.find(params[:id])
|
209
218
|
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
219
|
+
# if stale?(@article, public: true, cache_control: { no_cache: true })
|
220
|
+
# @statistics = @articles.really_expensive_call
|
221
|
+
# respond_to do |format|
|
222
|
+
# # all the supported formats
|
223
|
+
# end
|
214
224
|
# end
|
215
225
|
# end
|
216
|
-
# end
|
217
226
|
#
|
218
|
-
# The above will set
|
227
|
+
# The above will set `Cache-Control: public, no-cache` in the response.
|
219
228
|
#
|
220
229
|
# When rendering a different template than the controller/action's default
|
221
230
|
# template, you can indicate which digest to include in the ETag:
|
222
231
|
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
232
|
+
# def show
|
233
|
+
# super if stale?(@article, template: "widgets/show")
|
234
|
+
# end
|
226
235
|
#
|
227
236
|
def stale?(object = nil, **freshness_kwargs)
|
228
237
|
fresh_when(object, **freshness_kwargs)
|
229
238
|
!request.fresh?(response)
|
230
239
|
end
|
231
240
|
|
232
|
-
# Sets the
|
233
|
-
#
|
241
|
+
# Sets the `Cache-Control` header, overwriting existing directives. This method
|
242
|
+
# will also ensure an HTTP `Date` header for client compatibility.
|
243
|
+
#
|
244
|
+
# Defaults to issuing the `private` directive, so that intermediate caches must
|
245
|
+
# not cache the response.
|
246
|
+
#
|
247
|
+
# #### Options
|
248
|
+
#
|
249
|
+
# `:public`
|
250
|
+
# : If true, replaces the default `private` directive with the `public`
|
251
|
+
# directive.
|
252
|
+
#
|
253
|
+
# `:must_revalidate`
|
254
|
+
# : If true, adds the `must-revalidate` directive.
|
234
255
|
#
|
235
|
-
#
|
236
|
-
#
|
256
|
+
# `:stale_while_revalidate`
|
257
|
+
# : Sets the value of the `stale-while-revalidate` directive.
|
237
258
|
#
|
238
|
-
#
|
259
|
+
# `:stale_if_error`
|
260
|
+
# : Sets the value of the `stale-if-error` directive.
|
239
261
|
#
|
240
|
-
# [+:public+]
|
241
|
-
# If true, replaces the default +private+ directive with the +public+
|
242
|
-
# directive.
|
243
|
-
# [+:must_revalidate+]
|
244
|
-
# If true, adds the +must-revalidate+ directive.
|
245
|
-
# [+:stale_while_revalidate+]
|
246
|
-
# Sets the value of the +stale-while-revalidate+ directive.
|
247
|
-
# [+:stale_if_error+]
|
248
|
-
# Sets the value of the +stale-if-error+ directive.
|
249
262
|
#
|
250
|
-
# Any additional key-value pairs are concatenated as directives. For a list
|
251
|
-
#
|
252
|
-
# MDN
|
263
|
+
# Any additional key-value pairs are concatenated as directives. For a list of
|
264
|
+
# supported `Cache-Control` directives, see the [article on
|
265
|
+
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
|
253
266
|
#
|
254
|
-
#
|
267
|
+
# #### Examples
|
255
268
|
#
|
256
|
-
#
|
257
|
-
#
|
269
|
+
# expires_in 10.minutes
|
270
|
+
# # => Cache-Control: max-age=600, private
|
258
271
|
#
|
259
|
-
#
|
260
|
-
#
|
272
|
+
# expires_in 10.minutes, public: true
|
273
|
+
# # => Cache-Control: max-age=600, public
|
261
274
|
#
|
262
|
-
#
|
263
|
-
#
|
275
|
+
# expires_in 10.minutes, public: true, must_revalidate: true
|
276
|
+
# # => Cache-Control: max-age=600, public, must-revalidate
|
264
277
|
#
|
265
|
-
#
|
266
|
-
#
|
278
|
+
# expires_in 1.hour, stale_while_revalidate: 60.seconds
|
279
|
+
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
|
267
280
|
#
|
268
|
-
#
|
269
|
-
#
|
281
|
+
# expires_in 1.hour, stale_if_error: 5.minutes
|
282
|
+
# # => Cache-Control: max-age=3600, private, stale-if-error=300
|
270
283
|
#
|
271
|
-
#
|
272
|
-
#
|
284
|
+
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
|
285
|
+
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
|
273
286
|
#
|
274
287
|
def expires_in(seconds, options = {})
|
275
288
|
response.cache_control.delete(:no_store)
|
@@ -286,8 +299,8 @@ module ActionController
|
|
286
299
|
response.date = Time.now unless response.date?
|
287
300
|
end
|
288
301
|
|
289
|
-
# Sets an HTTP 1.1
|
290
|
-
#
|
302
|
+
# Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
|
303
|
+
# will be marked as stale, so clients must always revalidate.
|
291
304
|
# Intermediate/browser caches may still store the asset.
|
292
305
|
def expires_now
|
293
306
|
response.cache_control.replace(no_cache: true)
|
@@ -295,12 +308,12 @@ module ActionController
|
|
295
308
|
|
296
309
|
# Cache or yield the block. The cache is supposed to never expire.
|
297
310
|
#
|
298
|
-
# You can use this method when you have an HTTP response that never changes,
|
299
|
-
#
|
311
|
+
# You can use this method when you have an HTTP response that never changes, and
|
312
|
+
# the browser and proxies should cache it indefinitely.
|
300
313
|
#
|
301
|
-
# *
|
302
|
-
#
|
303
|
-
#
|
314
|
+
# * `public`: By default, HTTP responses are private, cached only on the
|
315
|
+
# user's web browser. To allow proxies to cache the response, set `true` to
|
316
|
+
# indicate that they can serve the cached response to all users.
|
304
317
|
def http_cache_forever(public: false)
|
305
318
|
expires_in 100.years, public: public
|
306
319
|
|
@@ -309,8 +322,8 @@ module ActionController
|
|
309
322
|
public: public)
|
310
323
|
end
|
311
324
|
|
312
|
-
# Sets an HTTP 1.1
|
313
|
-
#
|
325
|
+
# Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
|
326
|
+
# may not be stored in any cache.
|
314
327
|
def no_store
|
315
328
|
response.cache_control.replace(no_store: true)
|
316
329
|
end
|