omg-actionpack 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +129 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller/asset_paths.rb +14 -0
- data/lib/abstract_controller/base.rb +299 -0
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +265 -0
- data/lib/abstract_controller/collector.rb +44 -0
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +243 -0
- data/lib/abstract_controller/logger.rb +16 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
- data/lib/abstract_controller/rendering.rb +126 -0
- data/lib/abstract_controller/translation.rb +42 -0
- data/lib/abstract_controller/url_for.rb +37 -0
- data/lib/abstract_controller.rb +36 -0
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +155 -0
- data/lib/action_controller/base.rb +332 -0
- data/lib/action_controller/caching.rb +49 -0
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +96 -0
- data/lib/action_controller/metal/allow_browser.rb +123 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +341 -0
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +20 -0
- data/lib/action_controller/metal/data_streaming.rb +154 -0
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
- data/lib/action_controller/metal/exceptions.rb +106 -0
- data/lib/action_controller/metal/flash.rb +67 -0
- data/lib/action_controller/metal/head.rb +67 -0
- data/lib/action_controller/metal/helpers.rb +129 -0
- data/lib/action_controller/metal/http_authentication.rb +565 -0
- data/lib/action_controller/metal/implicit_render.rb +67 -0
- data/lib/action_controller/metal/instrumentation.rb +120 -0
- data/lib/action_controller/metal/live.rb +398 -0
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +337 -0
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +312 -0
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +251 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +260 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
- data/lib/action_controller/metal/rescue.rb +33 -0
- data/lib/action_controller/metal/streaming.rb +183 -0
- data/lib/action_controller/metal/strong_parameters.rb +1546 -0
- data/lib/action_controller/metal/testing.rb +25 -0
- data/lib/action_controller/metal/url_for.rb +65 -0
- data/lib/action_controller/metal.rb +339 -0
- data/lib/action_controller/railtie.rb +149 -0
- data/lib/action_controller/railties/helpers.rb +26 -0
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +691 -0
- data/lib/action_controller.rb +80 -0
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +249 -0
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +365 -0
- data/lib/action_dispatch/http/filter_parameters.rb +80 -0
- data/lib/action_dispatch/http/filter_redirect.rb +50 -0
- data/lib/action_dispatch/http/headers.rb +134 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
- data/lib/action_dispatch/http/mime_type.rb +389 -0
- data/lib/action_dispatch/http/mime_types.rb +54 -0
- data/lib/action_dispatch/http/parameters.rb +119 -0
- data/lib/action_dispatch/http/permissions_policy.rb +189 -0
- data/lib/action_dispatch/http/rack_cache.rb +67 -0
- data/lib/action_dispatch/http/request.rb +498 -0
- data/lib/action_dispatch/http/response.rb +556 -0
- data/lib/action_dispatch/http/upload.rb +107 -0
- data/lib/action_dispatch/http/url.rb +344 -0
- data/lib/action_dispatch/journey/formatter.rb +226 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
- data/lib/action_dispatch/journey/nodes/node.rb +208 -0
- data/lib/action_dispatch/journey/parser.rb +103 -0
- data/lib/action_dispatch/journey/path/pattern.rb +209 -0
- data/lib/action_dispatch/journey/route.rb +189 -0
- data/lib/action_dispatch/journey/router/utils.rb +105 -0
- data/lib/action_dispatch/journey/router.rb +151 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +70 -0
- data/lib/action_dispatch/journey/visitors.rb +267 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +38 -0
- data/lib/action_dispatch/middleware/cookies.rb +719 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +318 -0
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
- data/lib/action_dispatch/middleware/reloader.rb +16 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
- data/lib/action_dispatch/middleware/request_id.rb +50 -0
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
- data/lib/action_dispatch/middleware/ssl.rb +180 -0
- data/lib/action_dispatch/middleware/stack.rb +194 -0
- data/lib/action_dispatch/middleware/static.rb +192 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
- data/lib/action_dispatch/railtie.rb +77 -0
- data/lib/action_dispatch/request/session.rb +283 -0
- data/lib/action_dispatch/request/utils.rb +109 -0
- data/lib/action_dispatch/routing/endpoint.rb +19 -0
- data/lib/action_dispatch/routing/inspector.rb +323 -0
- data/lib/action_dispatch/routing/mapper.rb +2372 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
- data/lib/action_dispatch/routing/redirection.rb +218 -0
- data/lib/action_dispatch/routing/route_set.rb +958 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
- data/lib/action_dispatch/routing/url_for.rb +244 -0
- data/lib/action_dispatch/routing.rb +262 -0
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +75 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +114 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
- data/lib/action_dispatch/testing/assertions.rb +25 -0
- data/lib/action_dispatch/testing/integration.rb +694 -0
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +57 -0
- data/lib/action_dispatch/testing/test_request.rb +73 -0
- data/lib/action_dispatch/testing/test_response.rb +58 -0
- data/lib/action_dispatch.rb +147 -0
- data/lib/action_pack/gem_version.rb +19 -0
- data/lib/action_pack/version.rb +12 -0
- data/lib/action_pack.rb +27 -0
- metadata +375 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
require "active_support/core_ext/object/try"
|
|
6
|
+
require "active_support/core_ext/integer/time"
|
|
7
|
+
|
|
8
|
+
module ActionController
|
|
9
|
+
module ConditionalGet
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
|
|
12
|
+
include Head
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
class_attribute :etaggers, default: []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
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.
|
|
23
|
+
#
|
|
24
|
+
# class InvoicesController < ApplicationController
|
|
25
|
+
# etag { current_user&.id }
|
|
26
|
+
#
|
|
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
|
|
32
|
+
# end
|
|
33
|
+
def etag(&etagger)
|
|
34
|
+
self.etaggers += [etagger]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
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.
|
|
100
|
+
#
|
|
101
|
+
# You can also just pass a record:
|
|
102
|
+
#
|
|
103
|
+
# def show
|
|
104
|
+
# @article = Article.find(params[:id])
|
|
105
|
+
# fresh_when(@article)
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# `etag` will be set to the record, and `last_modified` will be set to the
|
|
109
|
+
# record's `updated_at`.
|
|
110
|
+
#
|
|
111
|
+
# You can also pass an object that responds to `maximum`, such as a collection
|
|
112
|
+
# of records:
|
|
113
|
+
#
|
|
114
|
+
# def index
|
|
115
|
+
# @articles = Article.all
|
|
116
|
+
# fresh_when(@articles)
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
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).
|
|
122
|
+
#
|
|
123
|
+
# When passing a record or a collection, you can still specify other options,
|
|
124
|
+
# such as `:public` and `:cache_control`:
|
|
125
|
+
#
|
|
126
|
+
# def show
|
|
127
|
+
# @article = Article.find(params[:id])
|
|
128
|
+
# fresh_when(@article, public: true, cache_control: { no_cache: true })
|
|
129
|
+
# end
|
|
130
|
+
#
|
|
131
|
+
# The above will set `Cache-Control: public, no-cache` in the response.
|
|
132
|
+
#
|
|
133
|
+
# When rendering a different template than the controller/action's default
|
|
134
|
+
# template, you can indicate which digest to include in the ETag:
|
|
135
|
+
#
|
|
136
|
+
# before_action { fresh_when @article, template: "widgets/show" }
|
|
137
|
+
#
|
|
138
|
+
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
|
|
139
|
+
response.cache_control.delete(:no_store)
|
|
140
|
+
weak_etag ||= etag || object unless strong_etag
|
|
141
|
+
last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
|
|
142
|
+
|
|
143
|
+
if strong_etag
|
|
144
|
+
response.strong_etag = combine_etags strong_etag,
|
|
145
|
+
last_modified: last_modified, public: public, template: template
|
|
146
|
+
elsif weak_etag || template
|
|
147
|
+
response.weak_etag = combine_etags weak_etag,
|
|
148
|
+
last_modified: last_modified, public: public, template: template
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
response.last_modified = last_modified if last_modified
|
|
152
|
+
response.cache_control[:public] = true if public
|
|
153
|
+
response.cache_control.merge!(cache_control)
|
|
154
|
+
|
|
155
|
+
head :not_modified if request.fresh?(response)
|
|
156
|
+
end
|
|
157
|
+
|
|
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.
|
|
162
|
+
#
|
|
163
|
+
# #### Options
|
|
164
|
+
#
|
|
165
|
+
# See #fresh_when for supported options.
|
|
166
|
+
#
|
|
167
|
+
# #### Examples
|
|
168
|
+
#
|
|
169
|
+
# def show
|
|
170
|
+
# @article = Article.find(params[:id])
|
|
171
|
+
#
|
|
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
|
|
177
|
+
# end
|
|
178
|
+
# end
|
|
179
|
+
#
|
|
180
|
+
# You can also just pass a record:
|
|
181
|
+
#
|
|
182
|
+
# def show
|
|
183
|
+
# @article = Article.find(params[:id])
|
|
184
|
+
#
|
|
185
|
+
# if stale?(@article)
|
|
186
|
+
# @statistics = @article.really_expensive_call
|
|
187
|
+
# respond_to do |format|
|
|
188
|
+
# # all the supported formats
|
|
189
|
+
# end
|
|
190
|
+
# end
|
|
191
|
+
# end
|
|
192
|
+
#
|
|
193
|
+
# `etag` will be set to the record, and `last_modified` will be set to the
|
|
194
|
+
# record's `updated_at`.
|
|
195
|
+
#
|
|
196
|
+
# You can also pass an object that responds to `maximum`, such as a collection
|
|
197
|
+
# of records:
|
|
198
|
+
#
|
|
199
|
+
# def index
|
|
200
|
+
# @articles = Article.all
|
|
201
|
+
#
|
|
202
|
+
# if stale?(@articles)
|
|
203
|
+
# @statistics = @articles.really_expensive_call
|
|
204
|
+
# respond_to do |format|
|
|
205
|
+
# # all the supported formats
|
|
206
|
+
# end
|
|
207
|
+
# end
|
|
208
|
+
# end
|
|
209
|
+
#
|
|
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).
|
|
213
|
+
#
|
|
214
|
+
# When passing a record or a collection, you can still specify other options,
|
|
215
|
+
# such as `:public` and `:cache_control`:
|
|
216
|
+
#
|
|
217
|
+
# def show
|
|
218
|
+
# @article = Article.find(params[:id])
|
|
219
|
+
#
|
|
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
|
|
225
|
+
# end
|
|
226
|
+
# end
|
|
227
|
+
#
|
|
228
|
+
# The above will set `Cache-Control: public, no-cache` in the response.
|
|
229
|
+
#
|
|
230
|
+
# When rendering a different template than the controller/action's default
|
|
231
|
+
# template, you can indicate which digest to include in the ETag:
|
|
232
|
+
#
|
|
233
|
+
# def show
|
|
234
|
+
# super if stale?(@article, template: "widgets/show")
|
|
235
|
+
# end
|
|
236
|
+
#
|
|
237
|
+
def stale?(object = nil, **freshness_kwargs)
|
|
238
|
+
fresh_when(object, **freshness_kwargs)
|
|
239
|
+
!request.fresh?(response)
|
|
240
|
+
end
|
|
241
|
+
|
|
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.
|
|
256
|
+
#
|
|
257
|
+
# `:stale_while_revalidate`
|
|
258
|
+
# : Sets the value of the `stale-while-revalidate` directive.
|
|
259
|
+
#
|
|
260
|
+
# `:stale_if_error`
|
|
261
|
+
# : Sets the value of the `stale-if-error` directive.
|
|
262
|
+
#
|
|
263
|
+
# `:immutable`
|
|
264
|
+
# : If true, adds the `immutable` directive.
|
|
265
|
+
#
|
|
266
|
+
#
|
|
267
|
+
# Any additional key-value pairs are concatenated as directives. For a list of
|
|
268
|
+
# supported `Cache-Control` directives, see the [article on
|
|
269
|
+
# MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
|
|
270
|
+
#
|
|
271
|
+
# #### Examples
|
|
272
|
+
#
|
|
273
|
+
# expires_in 10.minutes
|
|
274
|
+
# # => Cache-Control: max-age=600, private
|
|
275
|
+
#
|
|
276
|
+
# expires_in 10.minutes, public: true
|
|
277
|
+
# # => Cache-Control: max-age=600, public
|
|
278
|
+
#
|
|
279
|
+
# expires_in 10.minutes, public: true, must_revalidate: true
|
|
280
|
+
# # => Cache-Control: max-age=600, public, must-revalidate
|
|
281
|
+
#
|
|
282
|
+
# expires_in 1.hour, stale_while_revalidate: 60.seconds
|
|
283
|
+
# # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
|
|
284
|
+
#
|
|
285
|
+
# expires_in 1.hour, stale_if_error: 5.minutes
|
|
286
|
+
# # => Cache-Control: max-age=3600, private, stale-if-error=300
|
|
287
|
+
#
|
|
288
|
+
# expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
|
|
289
|
+
# # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
|
|
290
|
+
#
|
|
291
|
+
def expires_in(seconds, options = {})
|
|
292
|
+
response.cache_control.delete(:no_store)
|
|
293
|
+
response.cache_control.merge!(
|
|
294
|
+
max_age: seconds,
|
|
295
|
+
public: options.delete(:public),
|
|
296
|
+
must_revalidate: options.delete(:must_revalidate),
|
|
297
|
+
stale_while_revalidate: options.delete(:stale_while_revalidate),
|
|
298
|
+
stale_if_error: options.delete(:stale_if_error),
|
|
299
|
+
immutable: options.delete(:immutable),
|
|
300
|
+
)
|
|
301
|
+
options.delete(:private)
|
|
302
|
+
|
|
303
|
+
response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
|
|
304
|
+
response.date = Time.now unless response.date?
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
|
|
308
|
+
# will be marked as stale, so clients must always revalidate.
|
|
309
|
+
# Intermediate/browser caches may still store the asset.
|
|
310
|
+
def expires_now
|
|
311
|
+
response.cache_control.replace(no_cache: true)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Cache or yield the block. The cache is supposed to never expire.
|
|
315
|
+
#
|
|
316
|
+
# You can use this method when you have an HTTP response that never changes, and
|
|
317
|
+
# the browser and proxies should cache it indefinitely.
|
|
318
|
+
#
|
|
319
|
+
# * `public`: By default, HTTP responses are private, cached only on the
|
|
320
|
+
# user's web browser. To allow proxies to cache the response, set `true` to
|
|
321
|
+
# indicate that they can serve the cached response to all users.
|
|
322
|
+
def http_cache_forever(public: false)
|
|
323
|
+
expires_in 100.years, public: public, immutable: true
|
|
324
|
+
|
|
325
|
+
yield if stale?(etag: request.fullpath,
|
|
326
|
+
last_modified: Time.new(2011, 1, 1).utc,
|
|
327
|
+
public: public)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
|
|
331
|
+
# may not be stored in any cache.
|
|
332
|
+
def no_store
|
|
333
|
+
response.cache_control.replace(no_store: true)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
private
|
|
337
|
+
def combine_etags(validator, options)
|
|
338
|
+
[validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActionController # :nodoc:
|
|
6
|
+
module ContentSecurityPolicy
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
include AbstractController::Helpers
|
|
10
|
+
include AbstractController::Callbacks
|
|
11
|
+
|
|
12
|
+
included do
|
|
13
|
+
helper_method :content_security_policy?
|
|
14
|
+
helper_method :content_security_policy_nonce
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
# Overrides parts of the globally configured `Content-Security-Policy` header:
|
|
19
|
+
#
|
|
20
|
+
# class PostsController < ApplicationController
|
|
21
|
+
# content_security_policy do |policy|
|
|
22
|
+
# policy.base_uri "https://www.example.com"
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# Options can be passed similar to `before_action`. For example, pass `only:
|
|
27
|
+
# :index` to override the header on the index action only:
|
|
28
|
+
#
|
|
29
|
+
# class PostsController < ApplicationController
|
|
30
|
+
# content_security_policy(only: :index) do |policy|
|
|
31
|
+
# policy.default_src :self, :https
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# Pass `false` to remove the `Content-Security-Policy` header:
|
|
36
|
+
#
|
|
37
|
+
# class PostsController < ApplicationController
|
|
38
|
+
# content_security_policy false, only: :index
|
|
39
|
+
# end
|
|
40
|
+
def content_security_policy(enabled = true, **options, &block)
|
|
41
|
+
before_action(options) do
|
|
42
|
+
if block_given?
|
|
43
|
+
policy = current_content_security_policy
|
|
44
|
+
instance_exec(policy, &block)
|
|
45
|
+
request.content_security_policy = policy
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless enabled
|
|
49
|
+
request.content_security_policy = nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Overrides the globally configured `Content-Security-Policy-Report-Only`
|
|
55
|
+
# header:
|
|
56
|
+
#
|
|
57
|
+
# class PostsController < ApplicationController
|
|
58
|
+
# content_security_policy_report_only only: :index
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# Pass `false` to remove the `Content-Security-Policy-Report-Only` header:
|
|
62
|
+
#
|
|
63
|
+
# class PostsController < ApplicationController
|
|
64
|
+
# content_security_policy_report_only false, only: :index
|
|
65
|
+
# end
|
|
66
|
+
def content_security_policy_report_only(report_only = true, **options)
|
|
67
|
+
before_action(options) do
|
|
68
|
+
request.content_security_policy_report_only = report_only
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
def content_security_policy?
|
|
75
|
+
request.content_security_policy
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def content_security_policy_nonce
|
|
79
|
+
request.content_security_policy_nonce
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def current_content_security_policy
|
|
83
|
+
request.content_security_policy&.clone || ActionDispatch::ContentSecurityPolicy.new
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActionController # :nodoc:
|
|
6
|
+
module Cookies
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
helper_method :cookies if defined?(helper_method)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
# The cookies for the current request. See ActionDispatch::Cookies for more
|
|
15
|
+
# information.
|
|
16
|
+
def cookies # :doc:
|
|
17
|
+
request.cookie_jar
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
require "action_controller/metal/exceptions"
|
|
6
|
+
require "action_dispatch/http/content_disposition"
|
|
7
|
+
|
|
8
|
+
module ActionController # :nodoc:
|
|
9
|
+
# # Action Controller Data Streaming
|
|
10
|
+
#
|
|
11
|
+
# Methods for sending arbitrary data and for streaming files to the browser,
|
|
12
|
+
# instead of rendering.
|
|
13
|
+
module DataStreaming
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
|
|
16
|
+
include ActionController::Rendering
|
|
17
|
+
|
|
18
|
+
DEFAULT_SEND_FILE_TYPE = "application/octet-stream" # :nodoc:
|
|
19
|
+
DEFAULT_SEND_FILE_DISPOSITION = "attachment" # :nodoc:
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
# Sends the file. This uses a server-appropriate method (such as `X-Sendfile`)
|
|
23
|
+
# via the `Rack::Sendfile` middleware. The header to use is set via
|
|
24
|
+
# `config.action_dispatch.x_sendfile_header`. Your server can also configure
|
|
25
|
+
# this for you by setting the `X-Sendfile-Type` header.
|
|
26
|
+
#
|
|
27
|
+
# Be careful to sanitize the path parameter if it is coming from a web page.
|
|
28
|
+
# `send_file(params[:path])` allows a malicious user to download any file on
|
|
29
|
+
# your server.
|
|
30
|
+
#
|
|
31
|
+
# Options:
|
|
32
|
+
# * `:filename` - suggests a filename for the browser to use. Defaults to
|
|
33
|
+
# `File.basename(path)`.
|
|
34
|
+
# * `:type` - specifies an HTTP content type. You can specify either a string
|
|
35
|
+
# or a symbol for a registered type with `Mime::Type.register`, for example
|
|
36
|
+
# `:json`. If omitted, the type will be inferred from the file extension
|
|
37
|
+
# specified in `:filename`. If no content type is registered for the
|
|
38
|
+
# extension, the default type `application/octet-stream` will be used.
|
|
39
|
+
# * `:disposition` - specifies whether the file will be shown inline or
|
|
40
|
+
# downloaded. Valid values are `"inline"` and `"attachment"` (default).
|
|
41
|
+
# * `:status` - specifies the status code to send with the response. Defaults
|
|
42
|
+
# to 200.
|
|
43
|
+
# * `:url_based_filename` - set to `true` if you want the browser to guess the
|
|
44
|
+
# filename from the URL, which is necessary for i18n filenames on certain
|
|
45
|
+
# browsers (setting `:filename` overrides this option).
|
|
46
|
+
#
|
|
47
|
+
#
|
|
48
|
+
# The default `Content-Type` and `Content-Disposition` headers are set to
|
|
49
|
+
# download arbitrary binary files in as many browsers as possible. IE versions
|
|
50
|
+
# 4, 5, 5.5, and 6 are all known to have a variety of quirks (especially when
|
|
51
|
+
# downloading over SSL).
|
|
52
|
+
#
|
|
53
|
+
# Simple download:
|
|
54
|
+
#
|
|
55
|
+
# send_file '/path/to.zip'
|
|
56
|
+
#
|
|
57
|
+
# Show a JPEG in the browser:
|
|
58
|
+
#
|
|
59
|
+
# send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
|
|
60
|
+
#
|
|
61
|
+
# Show a 404 page in the browser:
|
|
62
|
+
#
|
|
63
|
+
# send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
|
|
64
|
+
#
|
|
65
|
+
# You can use other `Content-*` HTTP headers to provide additional information
|
|
66
|
+
# to the client. See MDN for a [list of HTTP
|
|
67
|
+
# headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
|
|
68
|
+
#
|
|
69
|
+
# Also be aware that the document may be cached by proxies and browsers. The
|
|
70
|
+
# `Pragma` and `Cache-Control` headers declare how the file may be cached by
|
|
71
|
+
# intermediaries. They default to require clients to validate with the server
|
|
72
|
+
# before releasing cached responses. See https://www.mnot.net/cache_docs/ for an
|
|
73
|
+
# overview of web caching and [RFC
|
|
74
|
+
# 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control) for the
|
|
75
|
+
# `Cache-Control` header spec.
|
|
76
|
+
def send_file(path, options = {}) # :doc:
|
|
77
|
+
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path)
|
|
78
|
+
|
|
79
|
+
options[:filename] ||= File.basename(path) unless options[:url_based_filename]
|
|
80
|
+
send_file_headers! options
|
|
81
|
+
|
|
82
|
+
self.status = options[:status] || 200
|
|
83
|
+
self.content_type = options[:content_type] if options.key?(:content_type)
|
|
84
|
+
response.send_file path
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Sends the given binary data to the browser. This method is similar to `render
|
|
88
|
+
# plain: data`, but also allows you to specify whether the browser should
|
|
89
|
+
# display the response as a file attachment (i.e. in a download dialog) or as
|
|
90
|
+
# inline data. You may also set the content type, the file name, and other
|
|
91
|
+
# things.
|
|
92
|
+
#
|
|
93
|
+
# Options:
|
|
94
|
+
# * `:filename` - suggests a filename for the browser to use.
|
|
95
|
+
# * `:type` - specifies an HTTP content type. Defaults to
|
|
96
|
+
# `application/octet-stream`. You can specify either a string or a symbol
|
|
97
|
+
# for a registered type with `Mime::Type.register`, for example `:json`. If
|
|
98
|
+
# omitted, type will be inferred from the file extension specified in
|
|
99
|
+
# `:filename`. If no content type is registered for the extension, the
|
|
100
|
+
# default type `application/octet-stream` will be used.
|
|
101
|
+
# * `:disposition` - specifies whether the file will be shown inline or
|
|
102
|
+
# downloaded. Valid values are `"inline"` and `"attachment"` (default).
|
|
103
|
+
# * `:status` - specifies the status code to send with the response. Defaults
|
|
104
|
+
# to 200.
|
|
105
|
+
#
|
|
106
|
+
#
|
|
107
|
+
# Generic data download:
|
|
108
|
+
#
|
|
109
|
+
# send_data buffer
|
|
110
|
+
#
|
|
111
|
+
# Download a dynamically-generated tarball:
|
|
112
|
+
#
|
|
113
|
+
# send_data generate_tgz('dir'), filename: 'dir.tgz'
|
|
114
|
+
#
|
|
115
|
+
# Display an image Active Record in the browser:
|
|
116
|
+
#
|
|
117
|
+
# send_data image.data, type: image.content_type, disposition: 'inline'
|
|
118
|
+
#
|
|
119
|
+
# See `send_file` for more information on HTTP `Content-*` headers and caching.
|
|
120
|
+
def send_data(data, options = {}) # :doc:
|
|
121
|
+
send_file_headers! options
|
|
122
|
+
render options.slice(:status, :content_type).merge(body: data)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def send_file_headers!(options)
|
|
126
|
+
type_provided = options.has_key?(:type)
|
|
127
|
+
|
|
128
|
+
content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
|
|
129
|
+
self.content_type = content_type
|
|
130
|
+
response.sending_file = true
|
|
131
|
+
|
|
132
|
+
raise ArgumentError, ":type option required" if content_type.nil?
|
|
133
|
+
|
|
134
|
+
if content_type.is_a?(Symbol)
|
|
135
|
+
extension = Mime[content_type]
|
|
136
|
+
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
|
|
137
|
+
self.content_type = extension
|
|
138
|
+
else
|
|
139
|
+
if !type_provided && options[:filename]
|
|
140
|
+
# If type wasn't provided, try guessing from file extension.
|
|
141
|
+
content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type
|
|
142
|
+
end
|
|
143
|
+
self.content_type = content_type
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
|
|
147
|
+
if disposition
|
|
148
|
+
headers["Content-Disposition"] = ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: options[:filename])
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
headers["Content-Transfer-Encoding"] = "binary"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActionController
|
|
6
|
+
# # Action Controller Default Headers
|
|
7
|
+
#
|
|
8
|
+
# Allows configuring default headers that will be automatically merged into each
|
|
9
|
+
# response.
|
|
10
|
+
module DefaultHeaders
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
module ClassMethods
|
|
14
|
+
def make_response!(request)
|
|
15
|
+
ActionDispatch::Response.create.tap do |res|
|
|
16
|
+
res.request = request
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :markup: markdown
|
|
4
|
+
|
|
5
|
+
module ActionController
|
|
6
|
+
# # Action Controller Etag With Flash
|
|
7
|
+
#
|
|
8
|
+
# When you're using the flash, it's generally used as a conditional on the view.
|
|
9
|
+
# This means the content of the view depends on the flash. Which in turn means
|
|
10
|
+
# that the ETag for a response should be computed with the content of the flash
|
|
11
|
+
# in mind. This does that by including the content of the flash as a component
|
|
12
|
+
# in the ETag that's generated for a response.
|
|
13
|
+
module EtagWithFlash
|
|
14
|
+
extend ActiveSupport::Concern
|
|
15
|
+
|
|
16
|
+
include ActionController::ConditionalGet
|
|
17
|
+
|
|
18
|
+
included do
|
|
19
|
+
etag { flash if request.respond_to?(:flash) && !flash.empty? }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|