actionpack 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +429 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller.rb +27 -0
- data/lib/abstract_controller/asset_paths.rb +12 -0
- data/lib/abstract_controller/base.rb +265 -0
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/caching/fragments.rb +166 -0
- data/lib/abstract_controller/callbacks.rb +212 -0
- data/lib/abstract_controller/collector.rb +43 -0
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +194 -0
- data/lib/abstract_controller/logger.rb +14 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
- data/lib/abstract_controller/rendering.rb +127 -0
- data/lib/abstract_controller/translation.rb +31 -0
- data/lib/abstract_controller/url_for.rb +35 -0
- data/lib/action_controller.rb +66 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/base.rb +276 -0
- data/lib/action_controller/caching.rb +46 -0
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +78 -0
- data/lib/action_controller/metal.rb +256 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +274 -0
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +16 -0
- data/lib/action_controller/metal/data_streaming.rb +152 -0
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
- data/lib/action_controller/metal/exceptions.rb +53 -0
- data/lib/action_controller/metal/flash.rb +61 -0
- data/lib/action_controller/metal/force_ssl.rb +99 -0
- data/lib/action_controller/metal/head.rb +60 -0
- data/lib/action_controller/metal/helpers.rb +123 -0
- data/lib/action_controller/metal/http_authentication.rb +519 -0
- data/lib/action_controller/metal/implicit_render.rb +73 -0
- data/lib/action_controller/metal/instrumentation.rb +107 -0
- data/lib/action_controller/metal/live.rb +312 -0
- data/lib/action_controller/metal/mime_responds.rb +313 -0
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +293 -0
- data/lib/action_controller/metal/redirecting.rb +133 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +122 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
- data/lib/action_controller/metal/rescue.rb +28 -0
- data/lib/action_controller/metal/streaming.rb +223 -0
- data/lib/action_controller/metal/strong_parameters.rb +1086 -0
- data/lib/action_controller/metal/testing.rb +16 -0
- data/lib/action_controller/metal/url_for.rb +58 -0
- data/lib/action_controller/railtie.rb +89 -0
- data/lib/action_controller/railties/helpers.rb +24 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +629 -0
- data/lib/action_dispatch.rb +112 -0
- data/lib/action_dispatch/http/cache.rb +222 -0
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +84 -0
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +132 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
- data/lib/action_dispatch/http/mime_type.rb +342 -0
- data/lib/action_dispatch/http/mime_types.rb +50 -0
- data/lib/action_dispatch/http/parameter_filter.rb +86 -0
- data/lib/action_dispatch/http/parameters.rb +126 -0
- data/lib/action_dispatch/http/rack_cache.rb +63 -0
- data/lib/action_dispatch/http/request.rb +430 -0
- data/lib/action_dispatch/http/response.rb +519 -0
- data/lib/action_dispatch/http/upload.rb +84 -0
- data/lib/action_dispatch/http/url.rb +350 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/journey/formatter.rb +189 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
- data/lib/action_dispatch/journey/nodes/node.rb +140 -0
- data/lib/action_dispatch/journey/parser.rb +199 -0
- data/lib/action_dispatch/journey/parser.y +50 -0
- data/lib/action_dispatch/journey/parser_extras.rb +31 -0
- data/lib/action_dispatch/journey/path/pattern.rb +198 -0
- data/lib/action_dispatch/journey/route.rb +203 -0
- data/lib/action_dispatch/journey/router.rb +156 -0
- data/lib/action_dispatch/journey/router/utils.rb +102 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +64 -0
- data/lib/action_dispatch/journey/visitors.rb +268 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/middleware/callbacks.rb +36 -0
- data/lib/action_dispatch/middleware/cookies.rb +685 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +300 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
- data/lib/action_dispatch/middleware/reloader.rb +12 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
- data/lib/action_dispatch/middleware/request_id.rb +43 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
- data/lib/action_dispatch/middleware/ssl.rb +150 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +130 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -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 +27 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -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 +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
- data/lib/action_dispatch/railtie.rb +55 -0
- data/lib/action_dispatch/request/session.rb +234 -0
- data/lib/action_dispatch/request/utils.rb +78 -0
- data/lib/action_dispatch/routing.rb +260 -0
- data/lib/action_dispatch/routing/endpoint.rb +17 -0
- data/lib/action_dispatch/routing/inspector.rb +225 -0
- data/lib/action_dispatch/routing/mapper.rb +2267 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
- data/lib/action_dispatch/routing/redirection.rb +201 -0
- data/lib/action_dispatch/routing/route_set.rb +890 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
- data/lib/action_dispatch/routing/url_for.rb +236 -0
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions.rb +24 -0
- data/lib/action_dispatch/testing/assertions/response.rb +107 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
- data/lib/action_dispatch/testing/integration.rb +652 -0
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +50 -0
- data/lib/action_dispatch/testing/test_request.rb +71 -0
- data/lib/action_dispatch/testing/test_response.rb +53 -0
- data/lib/action_pack.rb +26 -0
- data/lib/action_pack/gem_version.rb +17 -0
- data/lib/action_pack/version.rb +10 -0
- metadata +318 -0
@@ -0,0 +1,256 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "action_dispatch/middleware/stack"
|
5
|
+
require "action_dispatch/http/request"
|
6
|
+
require "action_dispatch/http/response"
|
7
|
+
|
8
|
+
module ActionController
|
9
|
+
# Extend ActionDispatch middleware stack to make it aware of options
|
10
|
+
# allowing the following syntax in controllers:
|
11
|
+
#
|
12
|
+
# class PostsController < ApplicationController
|
13
|
+
# use AuthenticationMiddleware, except: [:index, :show]
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
|
17
|
+
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
|
18
|
+
def initialize(klass, args, actions, strategy, block)
|
19
|
+
@actions = actions
|
20
|
+
@strategy = strategy
|
21
|
+
super(klass, args, block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?(action)
|
25
|
+
@strategy.call @actions, action
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def build(action, app = Proc.new)
|
30
|
+
action = action.to_s
|
31
|
+
|
32
|
+
middlewares.reverse.inject(app) do |a, middleware|
|
33
|
+
middleware.valid?(action) ? middleware.build(a) : a
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
INCLUDE = ->(list, action) { list.include? action }
|
40
|
+
EXCLUDE = ->(list, action) { !list.include? action }
|
41
|
+
NULL = ->(list, action) { true }
|
42
|
+
|
43
|
+
def build_middleware(klass, args, block)
|
44
|
+
options = args.extract_options!
|
45
|
+
only = Array(options.delete(:only)).map(&:to_s)
|
46
|
+
except = Array(options.delete(:except)).map(&:to_s)
|
47
|
+
args << options unless options.empty?
|
48
|
+
|
49
|
+
strategy = NULL
|
50
|
+
list = nil
|
51
|
+
|
52
|
+
if only.any?
|
53
|
+
strategy = INCLUDE
|
54
|
+
list = only
|
55
|
+
elsif except.any?
|
56
|
+
strategy = EXCLUDE
|
57
|
+
list = except
|
58
|
+
end
|
59
|
+
|
60
|
+
Middleware.new(klass, args, list, strategy, block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
|
65
|
+
# valid Rack interface without the additional niceties provided by
|
66
|
+
# <tt>ActionController::Base</tt>.
|
67
|
+
#
|
68
|
+
# A sample metal controller might look like this:
|
69
|
+
#
|
70
|
+
# class HelloController < ActionController::Metal
|
71
|
+
# def index
|
72
|
+
# self.response_body = "Hello World!"
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# And then to route requests to your metal controller, you would add
|
77
|
+
# something like this to <tt>config/routes.rb</tt>:
|
78
|
+
#
|
79
|
+
# get 'hello', to: HelloController.action(:index)
|
80
|
+
#
|
81
|
+
# The +action+ method returns a valid Rack application for the \Rails
|
82
|
+
# router to dispatch to.
|
83
|
+
#
|
84
|
+
# == Rendering Helpers
|
85
|
+
#
|
86
|
+
# <tt>ActionController::Metal</tt> by default provides no utilities for rendering
|
87
|
+
# views, partials, or other responses aside from explicitly calling of
|
88
|
+
# <tt>response_body=</tt>, <tt>content_type=</tt>, and <tt>status=</tt>. To
|
89
|
+
# add the render helpers you're used to having in a normal controller, you
|
90
|
+
# can do the following:
|
91
|
+
#
|
92
|
+
# class HelloController < ActionController::Metal
|
93
|
+
# include AbstractController::Rendering
|
94
|
+
# include ActionView::Layouts
|
95
|
+
# append_view_path "#{Rails.root}/app/views"
|
96
|
+
#
|
97
|
+
# def index
|
98
|
+
# render "hello/index"
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# == Redirection Helpers
|
103
|
+
#
|
104
|
+
# To add redirection helpers to your metal controller, do the following:
|
105
|
+
#
|
106
|
+
# class HelloController < ActionController::Metal
|
107
|
+
# include ActionController::Redirecting
|
108
|
+
# include Rails.application.routes.url_helpers
|
109
|
+
#
|
110
|
+
# def index
|
111
|
+
# redirect_to root_url
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# == Other Helpers
|
116
|
+
#
|
117
|
+
# You can refer to the modules included in <tt>ActionController::Base</tt> to see
|
118
|
+
# other features you can bring into your metal controller.
|
119
|
+
#
|
120
|
+
class Metal < AbstractController::Base
|
121
|
+
abstract!
|
122
|
+
|
123
|
+
# Returns the last part of the controller's name, underscored, without the ending
|
124
|
+
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
|
125
|
+
# Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well.
|
126
|
+
#
|
127
|
+
# ==== Returns
|
128
|
+
# * <tt>string</tt>
|
129
|
+
def self.controller_name
|
130
|
+
@controller_name ||= name.demodulize.sub(/Controller$/, "").underscore
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.make_response!(request)
|
134
|
+
ActionDispatch::Response.new.tap do |res|
|
135
|
+
res.request = request
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.binary_params_for?(action) # :nodoc:
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
# Delegates to the class' <tt>controller_name</tt>.
|
144
|
+
def controller_name
|
145
|
+
self.class.controller_name
|
146
|
+
end
|
147
|
+
|
148
|
+
attr_internal :response, :request
|
149
|
+
delegate :session, to: "@_request"
|
150
|
+
delegate :headers, :status=, :location=, :content_type=,
|
151
|
+
:status, :location, :content_type, to: "@_response"
|
152
|
+
|
153
|
+
def initialize
|
154
|
+
@_request = nil
|
155
|
+
@_response = nil
|
156
|
+
@_routes = nil
|
157
|
+
super
|
158
|
+
end
|
159
|
+
|
160
|
+
def params
|
161
|
+
@_params ||= request.parameters
|
162
|
+
end
|
163
|
+
|
164
|
+
def params=(val)
|
165
|
+
@_params = val
|
166
|
+
end
|
167
|
+
|
168
|
+
alias :response_code :status # :nodoc:
|
169
|
+
|
170
|
+
# Basic url_for that can be overridden for more robust functionality.
|
171
|
+
def url_for(string)
|
172
|
+
string
|
173
|
+
end
|
174
|
+
|
175
|
+
def response_body=(body)
|
176
|
+
body = [body] unless body.nil? || body.respond_to?(:each)
|
177
|
+
response.reset_body!
|
178
|
+
return unless body
|
179
|
+
response.body = body
|
180
|
+
super
|
181
|
+
end
|
182
|
+
|
183
|
+
# Tests if render or redirect has already happened.
|
184
|
+
def performed?
|
185
|
+
response_body || response.committed?
|
186
|
+
end
|
187
|
+
|
188
|
+
def dispatch(name, request, response) #:nodoc:
|
189
|
+
set_request!(request)
|
190
|
+
set_response!(response)
|
191
|
+
process(name)
|
192
|
+
request.commit_flash
|
193
|
+
to_a
|
194
|
+
end
|
195
|
+
|
196
|
+
def set_response!(response) # :nodoc:
|
197
|
+
@_response = response
|
198
|
+
end
|
199
|
+
|
200
|
+
def set_request!(request) #:nodoc:
|
201
|
+
@_request = request
|
202
|
+
@_request.controller_instance = self
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_a #:nodoc:
|
206
|
+
response.to_a
|
207
|
+
end
|
208
|
+
|
209
|
+
def reset_session
|
210
|
+
@_request.reset_session
|
211
|
+
end
|
212
|
+
|
213
|
+
class_attribute :middleware_stack, default: ActionController::MiddlewareStack.new
|
214
|
+
|
215
|
+
def self.inherited(base) # :nodoc:
|
216
|
+
base.middleware_stack = middleware_stack.dup
|
217
|
+
super
|
218
|
+
end
|
219
|
+
|
220
|
+
# Pushes the given Rack middleware and its arguments to the bottom of the
|
221
|
+
# middleware stack.
|
222
|
+
def self.use(*args, &block)
|
223
|
+
middleware_stack.use(*args, &block)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Alias for +middleware_stack+.
|
227
|
+
def self.middleware
|
228
|
+
middleware_stack
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns a Rack endpoint for the given action name.
|
232
|
+
def self.action(name)
|
233
|
+
app = lambda { |env|
|
234
|
+
req = ActionDispatch::Request.new(env)
|
235
|
+
res = make_response! req
|
236
|
+
new.dispatch(name, req, res)
|
237
|
+
}
|
238
|
+
|
239
|
+
if middleware_stack.any?
|
240
|
+
middleware_stack.build(name, app)
|
241
|
+
else
|
242
|
+
app
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Direct dispatch to the controller. Instantiates the controller, then
|
247
|
+
# executes the action named +name+.
|
248
|
+
def self.dispatch(name, req, res)
|
249
|
+
if middleware_stack.any?
|
250
|
+
middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env
|
251
|
+
else
|
252
|
+
new.dispatch(name, req, res)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionController
|
4
|
+
module BasicImplicitRender # :nodoc:
|
5
|
+
def send_action(method, *args)
|
6
|
+
super.tap { default_render unless performed? }
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_render(*args)
|
10
|
+
head :no_content
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/keys"
|
4
|
+
|
5
|
+
module ActionController
|
6
|
+
module ConditionalGet
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
include Head
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :etaggers, default: []
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Allows you to consider additional controller-wide information when generating an ETag.
|
17
|
+
# For example, if you serve pages tailored depending on who's logged in at the moment, you
|
18
|
+
# may want to add the current user id to be part of the ETag to prevent unauthorized displaying
|
19
|
+
# of cached pages.
|
20
|
+
#
|
21
|
+
# class InvoicesController < ApplicationController
|
22
|
+
# etag { current_user.try :id }
|
23
|
+
#
|
24
|
+
# def show
|
25
|
+
# # Etag will differ even for the same invoice when it's viewed by a different current_user
|
26
|
+
# @invoice = Invoice.find(params[:id])
|
27
|
+
# fresh_when(@invoice)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
def etag(&etagger)
|
31
|
+
self.etaggers += [etagger]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sets the +etag+, +last_modified+, or both on the response and renders a
|
36
|
+
# <tt>304 Not Modified</tt> response if the request is already fresh.
|
37
|
+
#
|
38
|
+
# === Parameters:
|
39
|
+
#
|
40
|
+
# * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
|
41
|
+
# +:weak_etag+ option.
|
42
|
+
# * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
|
43
|
+
# Requests that set If-None-Match header may return a 304 Not Modified
|
44
|
+
# response if it matches the ETag exactly. A weak ETag indicates semantic
|
45
|
+
# equivalence, not byte-for-byte equality, so they're good for caching
|
46
|
+
# HTML pages in browser caches. They can't be used for responses that
|
47
|
+
# must be byte-identical, like serving Range requests within a PDF file.
|
48
|
+
# * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
|
49
|
+
# Requests that set If-None-Match header may return a 304 Not Modified
|
50
|
+
# response if it matches the ETag exactly. A strong ETag implies exact
|
51
|
+
# equality: the response must match byte for byte. This is necessary for
|
52
|
+
# doing Range requests within a large video or PDF file, for example, or
|
53
|
+
# for compatibility with some CDNs that don't support weak ETags.
|
54
|
+
# * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
|
55
|
+
# response. Subsequent requests that set If-Modified-Since may return a
|
56
|
+
# 304 Not Modified response if last_modified <= If-Modified-Since.
|
57
|
+
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
|
58
|
+
# +true+ if you want your application to be cacheable by other devices (proxy caches).
|
59
|
+
# * <tt>:template</tt> By default, the template digest for the current
|
60
|
+
# controller/action is included in ETags. If the action renders a
|
61
|
+
# different template, you can include its digest instead. If the action
|
62
|
+
# doesn't render a template at all, you can pass <tt>template: false</tt>
|
63
|
+
# to skip any attempt to check for a template digest.
|
64
|
+
#
|
65
|
+
# === Example:
|
66
|
+
#
|
67
|
+
# def show
|
68
|
+
# @article = Article.find(params[:id])
|
69
|
+
# fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# This will render the show template if the request isn't sending a matching ETag or
|
73
|
+
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
|
74
|
+
#
|
75
|
+
# You can also just pass a record. In this case +last_modified+ will be set
|
76
|
+
# by calling +updated_at+ and +etag+ by passing the object itself.
|
77
|
+
#
|
78
|
+
# def show
|
79
|
+
# @article = Article.find(params[:id])
|
80
|
+
# fresh_when(@article)
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# You can also pass an object that responds to +maximum+, such as a
|
84
|
+
# collection of active records. In this case +last_modified+ will be set by
|
85
|
+
# calling <tt>maximum(:updated_at)</tt> on the collection (the timestamp of the
|
86
|
+
# most recently updated record) and the +etag+ by passing the object itself.
|
87
|
+
#
|
88
|
+
# def index
|
89
|
+
# @articles = Article.all
|
90
|
+
# fresh_when(@articles)
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# When passing a record or a collection, you can still set the public header:
|
94
|
+
#
|
95
|
+
# def show
|
96
|
+
# @article = Article.find(params[:id])
|
97
|
+
# fresh_when(@article, public: true)
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# When rendering a different template than the default controller/action
|
101
|
+
# style, you can indicate which digest to include in the ETag:
|
102
|
+
#
|
103
|
+
# before_action { fresh_when @article, template: 'widgets/show' }
|
104
|
+
#
|
105
|
+
def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil)
|
106
|
+
weak_etag ||= etag || object unless strong_etag
|
107
|
+
last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)
|
108
|
+
|
109
|
+
if strong_etag
|
110
|
+
response.strong_etag = combine_etags strong_etag,
|
111
|
+
last_modified: last_modified, public: public, template: template
|
112
|
+
elsif weak_etag || template
|
113
|
+
response.weak_etag = combine_etags weak_etag,
|
114
|
+
last_modified: last_modified, public: public, template: template
|
115
|
+
end
|
116
|
+
|
117
|
+
response.last_modified = last_modified if last_modified
|
118
|
+
response.cache_control[:public] = true if public
|
119
|
+
|
120
|
+
head :not_modified if request.fresh?(response)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Sets the +etag+ and/or +last_modified+ on the response and checks it against
|
124
|
+
# the client request. If the request doesn't match the options provided, the
|
125
|
+
# request is considered stale and should be generated from scratch. Otherwise,
|
126
|
+
# it's fresh and we don't need to generate anything and a reply of <tt>304 Not Modified</tt> is sent.
|
127
|
+
#
|
128
|
+
# === Parameters:
|
129
|
+
#
|
130
|
+
# * <tt>:etag</tt> Sets a "weak" ETag validator on the response. See the
|
131
|
+
# +:weak_etag+ option.
|
132
|
+
# * <tt>:weak_etag</tt> Sets a "weak" ETag validator on the response.
|
133
|
+
# Requests that set If-None-Match header may return a 304 Not Modified
|
134
|
+
# response if it matches the ETag exactly. A weak ETag indicates semantic
|
135
|
+
# equivalence, not byte-for-byte equality, so they're good for caching
|
136
|
+
# HTML pages in browser caches. They can't be used for responses that
|
137
|
+
# must be byte-identical, like serving Range requests within a PDF file.
|
138
|
+
# * <tt>:strong_etag</tt> Sets a "strong" ETag validator on the response.
|
139
|
+
# Requests that set If-None-Match header may return a 304 Not Modified
|
140
|
+
# response if it matches the ETag exactly. A strong ETag implies exact
|
141
|
+
# equality: the response must match byte for byte. This is necessary for
|
142
|
+
# doing Range requests within a large video or PDF file, for example, or
|
143
|
+
# for compatibility with some CDNs that don't support weak ETags.
|
144
|
+
# * <tt>:last_modified</tt> Sets a "weak" last-update validator on the
|
145
|
+
# response. Subsequent requests that set If-Modified-Since may return a
|
146
|
+
# 304 Not Modified response if last_modified <= If-Modified-Since.
|
147
|
+
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
|
148
|
+
# +true+ if you want your application to be cacheable by other devices (proxy caches).
|
149
|
+
# * <tt>:template</tt> By default, the template digest for the current
|
150
|
+
# controller/action is included in ETags. If the action renders a
|
151
|
+
# different template, you can include its digest instead. If the action
|
152
|
+
# doesn't render a template at all, you can pass <tt>template: false</tt>
|
153
|
+
# to skip any attempt to check for a template digest.
|
154
|
+
#
|
155
|
+
# === Example:
|
156
|
+
#
|
157
|
+
# def show
|
158
|
+
# @article = Article.find(params[:id])
|
159
|
+
#
|
160
|
+
# if stale?(etag: @article, last_modified: @article.updated_at)
|
161
|
+
# @statistics = @article.really_expensive_call
|
162
|
+
# respond_to do |format|
|
163
|
+
# # all the supported formats
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# You can also just pass a record. In this case +last_modified+ will be set
|
169
|
+
# by calling +updated_at+ and +etag+ by passing the object itself.
|
170
|
+
#
|
171
|
+
# def show
|
172
|
+
# @article = Article.find(params[:id])
|
173
|
+
#
|
174
|
+
# if stale?(@article)
|
175
|
+
# @statistics = @article.really_expensive_call
|
176
|
+
# respond_to do |format|
|
177
|
+
# # all the supported formats
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# You can also pass an object that responds to +maximum+, such as a
|
183
|
+
# collection of active records. In this case +last_modified+ will be set by
|
184
|
+
# calling +maximum(:updated_at)+ on the collection (the timestamp of the
|
185
|
+
# most recently updated record) and the +etag+ by passing the object itself.
|
186
|
+
#
|
187
|
+
# def index
|
188
|
+
# @articles = Article.all
|
189
|
+
#
|
190
|
+
# if stale?(@articles)
|
191
|
+
# @statistics = @articles.really_expensive_call
|
192
|
+
# respond_to do |format|
|
193
|
+
# # all the supported formats
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# When passing a record or a collection, you can still set the public header:
|
199
|
+
#
|
200
|
+
# def show
|
201
|
+
# @article = Article.find(params[:id])
|
202
|
+
#
|
203
|
+
# if stale?(@article, public: true)
|
204
|
+
# @statistics = @article.really_expensive_call
|
205
|
+
# respond_to do |format|
|
206
|
+
# # all the supported formats
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
#
|
211
|
+
# When rendering a different template than the default controller/action
|
212
|
+
# style, you can indicate which digest to include in the ETag:
|
213
|
+
#
|
214
|
+
# def show
|
215
|
+
# super if stale? @article, template: 'widgets/show'
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
def stale?(object = nil, **freshness_kwargs)
|
219
|
+
fresh_when(object, **freshness_kwargs)
|
220
|
+
!request.fresh?(response)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+
|
224
|
+
# instruction, so that intermediate caches must not cache the response.
|
225
|
+
#
|
226
|
+
# expires_in 20.minutes
|
227
|
+
# expires_in 3.hours, public: true
|
228
|
+
# expires_in 3.hours, public: true, must_revalidate: true
|
229
|
+
#
|
230
|
+
# This method will overwrite an existing Cache-Control header.
|
231
|
+
# See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
|
232
|
+
#
|
233
|
+
# The method will also ensure an HTTP Date header for client compatibility.
|
234
|
+
def expires_in(seconds, options = {})
|
235
|
+
response.cache_control.merge!(
|
236
|
+
max_age: seconds,
|
237
|
+
public: options.delete(:public),
|
238
|
+
must_revalidate: options.delete(:must_revalidate)
|
239
|
+
)
|
240
|
+
options.delete(:private)
|
241
|
+
|
242
|
+
response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" }
|
243
|
+
response.date = Time.now unless response.date?
|
244
|
+
end
|
245
|
+
|
246
|
+
# Sets an HTTP 1.1 Cache-Control header of <tt>no-cache</tt>. This means the
|
247
|
+
# resource will be marked as stale, so clients must always revalidate.
|
248
|
+
# Intermediate/browser caches may still store the asset.
|
249
|
+
def expires_now
|
250
|
+
response.cache_control.replace(no_cache: true)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Cache or yield the block. The cache is supposed to never expire.
|
254
|
+
#
|
255
|
+
# You can use this method when you have an HTTP response that never changes,
|
256
|
+
# and the browser and proxies should cache it indefinitely.
|
257
|
+
#
|
258
|
+
# * +public+: By default, HTTP responses are private, cached only on the
|
259
|
+
# user's web browser. To allow proxies to cache the response, set +true+ to
|
260
|
+
# indicate that they can serve the cached response to all users.
|
261
|
+
def http_cache_forever(public: false)
|
262
|
+
expires_in 100.years, public: public
|
263
|
+
|
264
|
+
yield if stale?(etag: request.fullpath,
|
265
|
+
last_modified: Time.new(2011, 1, 1).utc,
|
266
|
+
public: public)
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
def combine_etags(validator, options)
|
271
|
+
[validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|