actionpack 7.1.3 → 7.2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -501
- data/lib/abstract_controller/asset_paths.rb +2 -0
- data/lib/abstract_controller/base.rb +102 -98
- data/lib/abstract_controller/caching/fragments.rb +50 -53
- data/lib/abstract_controller/caching.rb +2 -0
- data/lib/abstract_controller/callbacks.rb +66 -64
- data/lib/abstract_controller/collector.rb +6 -6
- data/lib/abstract_controller/deprecator.rb +2 -0
- data/lib/abstract_controller/error.rb +2 -0
- data/lib/abstract_controller/helpers.rb +70 -85
- data/lib/abstract_controller/logger.rb +2 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
- data/lib/abstract_controller/rendering.rb +13 -12
- data/lib/abstract_controller/translation.rb +15 -7
- data/lib/abstract_controller/url_for.rb +8 -6
- data/lib/abstract_controller.rb +2 -0
- data/lib/action_controller/api/api_rendering.rb +2 -0
- data/lib/action_controller/api.rb +74 -72
- data/lib/action_controller/base.rb +198 -126
- data/lib/action_controller/caching.rb +15 -12
- data/lib/action_controller/deprecator.rb +2 -0
- data/lib/action_controller/form_builder.rb +20 -17
- data/lib/action_controller/log_subscriber.rb +3 -1
- 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 +188 -174
- data/lib/action_controller/metal/content_security_policy.rb +25 -24
- data/lib/action_controller/metal/cookies.rb +4 -2
- data/lib/action_controller/metal/data_streaming.rb +64 -55
- data/lib/action_controller/metal/default_headers.rb +5 -3
- data/lib/action_controller/metal/etag_with_flash.rb +3 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
- data/lib/action_controller/metal/exceptions.rb +11 -9
- data/lib/action_controller/metal/flash.rb +12 -10
- data/lib/action_controller/metal/head.rb +12 -10
- data/lib/action_controller/metal/helpers.rb +63 -55
- data/lib/action_controller/metal/http_authentication.rb +210 -205
- data/lib/action_controller/metal/implicit_render.rb +17 -15
- data/lib/action_controller/metal/instrumentation.rb +15 -12
- data/lib/action_controller/metal/live.rb +113 -107
- 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 +57 -59
- data/lib/action_controller/metal/permissions_policy.rb +13 -12
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +108 -82
- data/lib/action_controller/metal/renderers.rb +50 -49
- data/lib/action_controller/metal/rendering.rb +103 -75
- data/lib/action_controller/metal/request_forgery_protection.rb +162 -133
- data/lib/action_controller/metal/rescue.rb +11 -9
- data/lib/action_controller/metal/streaming.rb +138 -136
- data/lib/action_controller/metal/strong_parameters.rb +525 -480
- data/lib/action_controller/metal/testing.rb +2 -0
- data/lib/action_controller/metal/url_for.rb +17 -15
- data/lib/action_controller/metal.rb +86 -60
- data/lib/action_controller/railtie.rb +3 -0
- data/lib/action_controller/railties/helpers.rb +2 -0
- data/lib/action_controller/renderer.rb +42 -36
- data/lib/action_controller/template_assertions.rb +4 -2
- data/lib/action_controller/test_case.rb +146 -126
- data/lib/action_controller.rb +10 -3
- data/lib/action_dispatch/constants.rb +2 -0
- data/lib/action_dispatch/deprecator.rb +2 -0
- data/lib/action_dispatch/http/cache.rb +27 -26
- data/lib/action_dispatch/http/content_disposition.rb +2 -0
- data/lib/action_dispatch/http/content_security_policy.rb +44 -38
- data/lib/action_dispatch/http/filter_parameters.rb +18 -9
- data/lib/action_dispatch/http/filter_redirect.rb +22 -1
- data/lib/action_dispatch/http/headers.rb +22 -22
- data/lib/action_dispatch/http/mime_negotiation.rb +30 -41
- data/lib/action_dispatch/http/mime_type.rb +31 -24
- data/lib/action_dispatch/http/mime_types.rb +2 -0
- data/lib/action_dispatch/http/parameters.rb +11 -9
- data/lib/action_dispatch/http/permissions_policy.rb +20 -44
- data/lib/action_dispatch/http/rack_cache.rb +2 -0
- data/lib/action_dispatch/http/request.rb +94 -75
- data/lib/action_dispatch/http/response.rb +73 -61
- data/lib/action_dispatch/http/upload.rb +18 -16
- data/lib/action_dispatch/http/url.rb +75 -73
- data/lib/action_dispatch/journey/formatter.rb +13 -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 +4 -1
- data/lib/action_dispatch/journey/route.rb +9 -7
- data/lib/action_dispatch/journey/router/utils.rb +16 -15
- data/lib/action_dispatch/journey/router.rb +4 -2
- data/lib/action_dispatch/journey/routes.rb +4 -2
- 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 +2 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
- data/lib/action_dispatch/middleware/callbacks.rb +3 -1
- data/lib/action_dispatch/middleware/cookies.rb +119 -104
- data/lib/action_dispatch/middleware/debug_exceptions.rb +13 -5
- data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
- data/lib/action_dispatch/middleware/debug_view.rb +2 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +6 -11
- data/lib/action_dispatch/middleware/executor.rb +8 -0
- data/lib/action_dispatch/middleware/flash.rb +63 -51
- data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
- data/lib/action_dispatch/middleware/public_exceptions.rb +8 -6
- data/lib/action_dispatch/middleware/reloader.rb +5 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +77 -72
- data/lib/action_dispatch/middleware/request_id.rb +14 -9
- data/lib/action_dispatch/middleware/server_timing.rb +4 -2
- data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +13 -8
- data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +31 -21
- data/lib/action_dispatch/middleware/ssl.rb +43 -40
- data/lib/action_dispatch/middleware/stack.rb +11 -10
- data/lib/action_dispatch/middleware/static.rb +33 -31
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
- data/lib/action_dispatch/railtie.rb +2 -4
- data/lib/action_dispatch/request/session.rb +23 -21
- data/lib/action_dispatch/request/utils.rb +2 -0
- data/lib/action_dispatch/routing/endpoint.rb +2 -0
- data/lib/action_dispatch/routing/inspector.rb +5 -3
- data/lib/action_dispatch/routing/mapper.rb +671 -636
- data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
- data/lib/action_dispatch/routing/redirection.rb +37 -32
- data/lib/action_dispatch/routing/route_set.rb +59 -45
- data/lib/action_dispatch/routing/routes_proxy.rb +6 -4
- data/lib/action_dispatch/routing/url_for.rb +130 -125
- data/lib/action_dispatch/routing.rb +150 -148
- data/lib/action_dispatch/system_test_case.rb +91 -81
- data/lib/action_dispatch/system_testing/browser.rb +10 -3
- data/lib/action_dispatch/system_testing/driver.rb +3 -1
- data/lib/action_dispatch/system_testing/server.rb +2 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +32 -21
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
- data/lib/action_dispatch/testing/assertion_response.rb +8 -6
- data/lib/action_dispatch/testing/assertions/response.rb +26 -23
- data/lib/action_dispatch/testing/assertions/routing.rb +153 -84
- data/lib/action_dispatch/testing/assertions.rb +2 -0
- data/lib/action_dispatch/testing/integration.rb +223 -222
- data/lib/action_dispatch/testing/request_encoder.rb +2 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +12 -8
- data/lib/action_dispatch/testing/test_request.rb +3 -1
- data/lib/action_dispatch/testing/test_response.rb +27 -26
- data/lib/action_dispatch.rb +22 -28
- data/lib/action_pack/gem_version.rb +6 -4
- data/lib/action_pack/version.rb +3 -1
- data/lib/action_pack.rb +17 -16
- metadata +39 -16
@@ -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
|
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,124 @@ 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-Contr
|
80
|
+
# ol).
|
81
|
+
#
|
82
|
+
# `:template`
|
83
|
+
# : By default, the template digest for the current controller/action is
|
84
|
+
# included in ETags. If the action renders a different template, you can
|
85
|
+
# include its digest instead. If the action doesn't render a template at
|
86
|
+
# all, you can pass `template: false` to skip any attempt to check for a
|
87
|
+
# template digest.
|
88
|
+
#
|
89
|
+
#
|
90
|
+
# #### Examples
|
91
|
+
#
|
92
|
+
# def show
|
93
|
+
# @article = Article.find(params[:id])
|
94
|
+
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# This will send a `304 Not Modified` response if the request specifies a
|
98
|
+
# matching ETag and `If-Modified-Since` header. Otherwise, it will render the
|
99
|
+
# `show` template.
|
90
100
|
#
|
91
101
|
# You can also just pass a record:
|
92
102
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
103
|
+
# def show
|
104
|
+
# @article = Article.find(params[:id])
|
105
|
+
# fresh_when(@article)
|
106
|
+
# end
|
97
107
|
#
|
98
|
-
#
|
99
|
-
# record's
|
108
|
+
# `etag` will be set to the record, and `last_modified` will be set to the
|
109
|
+
# record's `updated_at`.
|
100
110
|
#
|
101
|
-
# You can also pass an object that responds to
|
102
|
-
#
|
111
|
+
# You can also pass an object that responds to `maximum`, such as a collection
|
112
|
+
# of records:
|
103
113
|
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
114
|
+
# def index
|
115
|
+
# @articles = Article.all
|
116
|
+
# fresh_when(@articles)
|
117
|
+
# end
|
108
118
|
#
|
109
|
-
# In this case,
|
110
|
-
#
|
111
|
-
#
|
119
|
+
# In this case, `etag` will be set to the collection, and `last_modified` will
|
120
|
+
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
|
121
|
+
# record).
|
112
122
|
#
|
113
|
-
# When passing a record or a collection, you can still specify other
|
114
|
-
#
|
123
|
+
# When passing a record or a collection, you can still specify other options,
|
124
|
+
# such as `:public` and `:cache_control`:
|
115
125
|
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
126
|
+
# def show
|
127
|
+
# @article = Article.find(params[:id])
|
128
|
+
# fresh_when(@article, public: true, cache_control: { no_cache: true })
|
129
|
+
# end
|
120
130
|
#
|
121
|
-
# The above will set
|
131
|
+
# The above will set `Cache-Control: public, no-cache` in the response.
|
122
132
|
#
|
123
133
|
# When rendering a different template than the controller/action's default
|
124
134
|
# template, you can indicate which digest to include in the ETag:
|
125
135
|
#
|
126
|
-
#
|
136
|
+
# before_action { fresh_when @article, template: "widgets/show" }
|
127
137
|
#
|
128
138
|
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
|
129
139
|
response.cache_control.delete(:no_store)
|
@@ -145,131 +155,135 @@ module ActionController
|
|
145
155
|
head :not_modified if request.fresh?(response)
|
146
156
|
end
|
147
157
|
|
148
|
-
# Sets the
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
158
|
+
# Sets the `etag` and/or `last_modified` on the response and checks them against
|
159
|
+
# the request. If the request doesn't match the provided options, it is
|
160
|
+
# considered stale, and the response should be rendered from scratch. Otherwise,
|
161
|
+
# it is fresh, and a `304 Not Modified` is sent.
|
152
162
|
#
|
153
|
-
#
|
163
|
+
# #### Options
|
154
164
|
#
|
155
165
|
# See #fresh_when for supported options.
|
156
166
|
#
|
157
|
-
#
|
167
|
+
# #### Examples
|
158
168
|
#
|
159
|
-
#
|
160
|
-
#
|
169
|
+
# def show
|
170
|
+
# @article = Article.find(params[:id])
|
161
171
|
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
172
|
+
# if stale?(etag: @article, last_modified: @article.updated_at)
|
173
|
+
# @statistics = @article.really_expensive_call
|
174
|
+
# respond_to do |format|
|
175
|
+
# # all the supported formats
|
176
|
+
# end
|
166
177
|
# end
|
167
178
|
# end
|
168
|
-
# end
|
169
179
|
#
|
170
180
|
# You can also just pass a record:
|
171
181
|
#
|
172
|
-
#
|
173
|
-
#
|
182
|
+
# def show
|
183
|
+
# @article = Article.find(params[:id])
|
174
184
|
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
-
#
|
185
|
+
# if stale?(@article)
|
186
|
+
# @statistics = @article.really_expensive_call
|
187
|
+
# respond_to do |format|
|
188
|
+
# # all the supported formats
|
189
|
+
# end
|
179
190
|
# end
|
180
191
|
# end
|
181
|
-
# end
|
182
192
|
#
|
183
|
-
#
|
184
|
-
# record's
|
193
|
+
# `etag` will be set to the record, and `last_modified` will be set to the
|
194
|
+
# record's `updated_at`.
|
185
195
|
#
|
186
|
-
# You can also pass an object that responds to
|
187
|
-
#
|
196
|
+
# You can also pass an object that responds to `maximum`, such as a collection
|
197
|
+
# of records:
|
188
198
|
#
|
189
|
-
#
|
190
|
-
#
|
199
|
+
# def index
|
200
|
+
# @articles = Article.all
|
191
201
|
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
202
|
+
# if stale?(@articles)
|
203
|
+
# @statistics = @articles.really_expensive_call
|
204
|
+
# respond_to do |format|
|
205
|
+
# # all the supported formats
|
206
|
+
# end
|
196
207
|
# end
|
197
208
|
# end
|
198
|
-
# end
|
199
209
|
#
|
200
|
-
# In this case,
|
201
|
-
#
|
202
|
-
#
|
210
|
+
# In this case, `etag` will be set to the collection, and `last_modified` will
|
211
|
+
# be set to `maximum(:updated_at)` (the timestamp of the most recently updated
|
212
|
+
# record).
|
203
213
|
#
|
204
|
-
# When passing a record or a collection, you can still specify other
|
205
|
-
#
|
214
|
+
# When passing a record or a collection, you can still specify other options,
|
215
|
+
# such as `:public` and `:cache_control`:
|
206
216
|
#
|
207
|
-
#
|
208
|
-
#
|
217
|
+
# def show
|
218
|
+
# @article = Article.find(params[:id])
|
209
219
|
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
220
|
+
# if stale?(@article, public: true, cache_control: { no_cache: true })
|
221
|
+
# @statistics = @articles.really_expensive_call
|
222
|
+
# respond_to do |format|
|
223
|
+
# # all the supported formats
|
224
|
+
# end
|
214
225
|
# end
|
215
226
|
# end
|
216
|
-
# end
|
217
227
|
#
|
218
|
-
# The above will set
|
228
|
+
# The above will set `Cache-Control: public, no-cache` in the response.
|
219
229
|
#
|
220
230
|
# When rendering a different template than the controller/action's default
|
221
231
|
# template, you can indicate which digest to include in the ETag:
|
222
232
|
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
233
|
+
# def show
|
234
|
+
# super if stale?(@article, template: "widgets/show")
|
235
|
+
# end
|
226
236
|
#
|
227
237
|
def stale?(object = nil, **freshness_kwargs)
|
228
238
|
fresh_when(object, **freshness_kwargs)
|
229
239
|
!request.fresh?(response)
|
230
240
|
end
|
231
241
|
|
232
|
-
# Sets the
|
233
|
-
#
|
242
|
+
# Sets the `Cache-Control` header, overwriting existing directives. This method
|
243
|
+
# will also ensure an HTTP `Date` header for client compatibility.
|
244
|
+
#
|
245
|
+
# Defaults to issuing the `private` directive, so that intermediate caches must
|
246
|
+
# not cache the response.
|
247
|
+
#
|
248
|
+
# #### Options
|
249
|
+
#
|
250
|
+
# `:public`
|
251
|
+
# : If true, replaces the default `private` directive with the `public`
|
252
|
+
# directive.
|
253
|
+
#
|
254
|
+
# `:must_revalidate`
|
255
|
+
# : If true, adds the `must-revalidate` directive.
|
234
256
|
#
|
235
|
-
#
|
236
|
-
#
|
257
|
+
# `:stale_while_revalidate`
|
258
|
+
# : Sets the value of the `stale-while-revalidate` directive.
|
237
259
|
#
|
238
|
-
#
|
260
|
+
# `:stale_if_error`
|
261
|
+
# : Sets the value of the `stale-if-error` directive.
|
239
262
|
#
|
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
263
|
#
|
250
|
-
# Any additional key-value pairs are concatenated as directives. For a list
|
251
|
-
#
|
252
|
-
# MDN
|
264
|
+
# Any additional key-value pairs are concatenated as directives. For a list of
|
265
|
+
# supported `Cache-Control` directives, see the [article on
|
266
|
+
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
|
253
267
|
#
|
254
|
-
#
|
268
|
+
# #### Examples
|
255
269
|
#
|
256
|
-
#
|
257
|
-
#
|
270
|
+
# expires_in 10.minutes
|
271
|
+
# # => Cache-Control: max-age=600, private
|
258
272
|
#
|
259
|
-
#
|
260
|
-
#
|
273
|
+
# expires_in 10.minutes, public: true
|
274
|
+
# # => Cache-Control: max-age=600, public
|
261
275
|
#
|
262
|
-
#
|
263
|
-
#
|
276
|
+
# expires_in 10.minutes, public: true, must_revalidate: true
|
277
|
+
# # => Cache-Control: max-age=600, public, must-revalidate
|
264
278
|
#
|
265
|
-
#
|
266
|
-
#
|
279
|
+
# expires_in 1.hour, stale_while_revalidate: 60.seconds
|
280
|
+
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
|
267
281
|
#
|
268
|
-
#
|
269
|
-
#
|
282
|
+
# expires_in 1.hour, stale_if_error: 5.minutes
|
283
|
+
# # => Cache-Control: max-age=3600, private, stale-if-error=300
|
270
284
|
#
|
271
|
-
#
|
272
|
-
#
|
285
|
+
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
|
286
|
+
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
|
273
287
|
#
|
274
288
|
def expires_in(seconds, options = {})
|
275
289
|
response.cache_control.delete(:no_store)
|
@@ -286,8 +300,8 @@ module ActionController
|
|
286
300
|
response.date = Time.now unless response.date?
|
287
301
|
end
|
288
302
|
|
289
|
-
# Sets an HTTP 1.1
|
290
|
-
#
|
303
|
+
# Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
|
304
|
+
# will be marked as stale, so clients must always revalidate.
|
291
305
|
# Intermediate/browser caches may still store the asset.
|
292
306
|
def expires_now
|
293
307
|
response.cache_control.replace(no_cache: true)
|
@@ -295,12 +309,12 @@ module ActionController
|
|
295
309
|
|
296
310
|
# Cache or yield the block. The cache is supposed to never expire.
|
297
311
|
#
|
298
|
-
# You can use this method when you have an HTTP response that never changes,
|
299
|
-
#
|
312
|
+
# You can use this method when you have an HTTP response that never changes, and
|
313
|
+
# the browser and proxies should cache it indefinitely.
|
300
314
|
#
|
301
|
-
# *
|
302
|
-
#
|
303
|
-
#
|
315
|
+
# * `public`: By default, HTTP responses are private, cached only on the
|
316
|
+
# user's web browser. To allow proxies to cache the response, set `true` to
|
317
|
+
# indicate that they can serve the cached response to all users.
|
304
318
|
def http_cache_forever(public: false)
|
305
319
|
expires_in 100.years, public: public
|
306
320
|
|
@@ -309,8 +323,8 @@ module ActionController
|
|
309
323
|
public: public)
|
310
324
|
end
|
311
325
|
|
312
|
-
# Sets an HTTP 1.1
|
313
|
-
#
|
326
|
+
# Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
|
327
|
+
# may not be stored in any cache.
|
314
328
|
def no_store
|
315
329
|
response.cache_control.replace(no_store: true)
|
316
330
|
end
|