blob: 5ee4c044ea4fdbaf3398c02dbf78c5a9f259b038 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Http
module Cache
module Request
HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
def if_modified_since
if since = env[HTTP_IF_MODIFIED_SINCE]
Time.rfc2822(since) rescue nil
end
end
def if_none_match
env[HTTP_IF_NONE_MATCH]
end
def not_modified?(modified_at)
if_modified_since && modified_at && if_modified_since >= modified_at
end
def etag_matches?(etag)
if_none_match && if_none_match == etag
end
# Check response freshness (Last-Modified and ETag) against request
# If-Modified-Since and If-None-Match conditions. If both headers are
# supplied, both must match, or the request is not considered fresh.
def fresh?(response)
last_modified = if_modified_since
etag = if_none_match
return false unless last_modified || etag
success = true
success &&= not_modified?(response.last_modified) if last_modified
success &&= etag_matches?(response.etag) if etag
success
end
end
module Response
attr_reader :cache_control, :etag
alias :etag? :etag
def last_modified
if last = headers[LAST_MODIFIED]
Time.httpdate(last)
end
end
def last_modified?
headers.include?(LAST_MODIFIED)
end
def last_modified=(utc_time)
headers[LAST_MODIFIED] = utc_time.httpdate
end
def date
if date_header = headers['Date']
Time.httpdate(date_header)
end
end
def date?
headers.include?('Date')
end
def date=(utc_time)
headers['Date'] = utc_time.httpdate
end
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
end
private
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
def prepare_cache_control!
@cache_control = {}
@etag = self[ETAG]
if cache_control = self[CACHE_CONTROL]
cache_control.split(/,\s*/).each do |segment|
first, last = segment.split("=")
@cache_control[first.to_sym] = last || true
end
end
end
def handle_conditional_get!
if etag? || last_modified? || !@cache_control.empty?
set_conditional_cache_control!
end
end
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
NO_CACHE = "no-cache".freeze
PUBLIC = "public".freeze
PRIVATE = "private".freeze
MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
return if self[CACHE_CONTROL].present?
control = @cache_control
if control.empty?
headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
headers[CACHE_CONTROL] = NO_CACHE
else
extras = control[:extras]
max_age = control[:max_age]
options = []
options << "max-age=#{max_age.to_i}" if max_age
options << (control[:public] ? PUBLIC : PRIVATE)
options << MUST_REVALIDATE if control[:must_revalidate]
options.concat(extras) if extras
headers[CACHE_CONTROL] = options.join(", ")
end
end
end
end
end
end
|