josevalim-easy_http_cache 2.0
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.
- data/MIT-LICENSE +21 -0
- data/README +174 -0
- data/Rakefile +12 -0
- data/init.rb +1 -0
- data/lib/easy_http_cache.rb +140 -0
- data/test/easy_http_cache_test.rb +239 -0
- metadata +59 -0
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2006 Coda Hale
|
|
2
|
+
Copyright (c) 2008 José Valim (jose.valim at gmail dot com)
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
a copy of this software and associated documentation files (the
|
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Copyright (c) 2008 José Valim (jose.valim at gmail dot com)
|
|
2
|
+
Site: http://www.pagestacker.com/
|
|
3
|
+
Blog: http://josevalim.blogspot.com/
|
|
4
|
+
License: MIT
|
|
5
|
+
Version: 2.0
|
|
6
|
+
|
|
7
|
+
You can also read this README in pretty html at the GitHub project Wiki page
|
|
8
|
+
|
|
9
|
+
http://github.com/josevalim/easy_http_cache/wikis/home
|
|
10
|
+
|
|
11
|
+
Warning
|
|
12
|
+
-------
|
|
13
|
+
|
|
14
|
+
Since version 2.0, this plugin/gem has drastically changed to fit Rails 2.2
|
|
15
|
+
http cache goodness. :expires_in, :expires_at and :control options were
|
|
16
|
+
removed, so if you want to use previous versions, see "Previous versions"
|
|
17
|
+
below.
|
|
18
|
+
|
|
19
|
+
Description
|
|
20
|
+
-----------
|
|
21
|
+
|
|
22
|
+
Allows Rails applications to do conditional cache easily and in a DRY way
|
|
23
|
+
(without messing up your actions):
|
|
24
|
+
|
|
25
|
+
class ListsController < ApplicationController
|
|
26
|
+
http_cache :index, :show
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
It uses :last_modified and :etag keys, that besides Time, String or resources
|
|
30
|
+
accepts Proc, Method and Symbols that are evaluated within the current controller.
|
|
31
|
+
Read more about each option below:
|
|
32
|
+
|
|
33
|
+
:last_modified
|
|
34
|
+
Used to manipulate Last-Modified header. You can pass any object that responds
|
|
35
|
+
to :to_time. If you pass a Proc or Method or Symbols, they will be evaluated
|
|
36
|
+
within the current controller and :to_time will be called.
|
|
37
|
+
|
|
38
|
+
You can also pass resources and :updated_at or :updated_on will be called on
|
|
39
|
+
it. If you want to call a different method on your resource, you can pass it as
|
|
40
|
+
a symbol using the :method option.
|
|
41
|
+
|
|
42
|
+
All times will be converted to UTC. Finally, if you pass an array, it will get
|
|
43
|
+
the most recent time to be used.
|
|
44
|
+
|
|
45
|
+
:etag
|
|
46
|
+
Used to manipulate Etag header. If you pass a Proc or Method or Symbols, they
|
|
47
|
+
will be evaluated within the current controller.
|
|
48
|
+
|
|
49
|
+
You can also pass an array and each element will be also evaluated with needed.
|
|
50
|
+
|
|
51
|
+
:if
|
|
52
|
+
Only perform http cache if it returns true.
|
|
53
|
+
|
|
54
|
+
:unless
|
|
55
|
+
Only perform http cache if it returns false.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Install
|
|
59
|
+
-------
|
|
60
|
+
|
|
61
|
+
Install Easy HTTP Cache is very easy. It is stored in GitHub, so if you
|
|
62
|
+
have never installed a gem via GitHub run the following:
|
|
63
|
+
|
|
64
|
+
gem sources -a http://gems.github.com
|
|
65
|
+
|
|
66
|
+
Then install the gem:
|
|
67
|
+
|
|
68
|
+
sudo gem install josevalim-easy_http_cache
|
|
69
|
+
|
|
70
|
+
In RAILS_ROOT/config/environment.rb:
|
|
71
|
+
|
|
72
|
+
config.gem "josevalim-easy_http_cache", :lib => "easy_http_cache", :source => "http://gems.github.com"
|
|
73
|
+
|
|
74
|
+
If you want it as plugin, just do:
|
|
75
|
+
|
|
76
|
+
cd myapp
|
|
77
|
+
git clone git://github.com/josevalim/easy_http_cache.git
|
|
78
|
+
rm -rf vendor/plugins/easy_http_cache/.git
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
Previous versions
|
|
82
|
+
-----------------
|
|
83
|
+
|
|
84
|
+
If you are running on Rails 2.1.x, you should use v1.2.3:
|
|
85
|
+
|
|
86
|
+
cd myapp
|
|
87
|
+
git clone git://github.com/josevalim/easy_http_cache.git
|
|
88
|
+
cd vendor/plugins/easy_http_cache
|
|
89
|
+
git checkout v1.2.3
|
|
90
|
+
rm -rf ./.git
|
|
91
|
+
|
|
92
|
+
If you are using a previous version, please updagrade your app. =)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
Variables
|
|
96
|
+
---------
|
|
97
|
+
|
|
98
|
+
You can set ENV['RAILS_CACHE_ID'] or ENV['RAILS_APP_VERSION'] to change
|
|
99
|
+
the ETag that will be generated, expiring all previous caches. Those variables
|
|
100
|
+
are also used by other cache stores (memcached, file, ...).
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
--------
|
|
105
|
+
|
|
106
|
+
Just as above:
|
|
107
|
+
|
|
108
|
+
class ListsController < ApplicationController
|
|
109
|
+
http_cache :index, :show
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
If you do not want to cache when you are showing a flash message (and you
|
|
113
|
+
usually want that), you can simply do:
|
|
114
|
+
|
|
115
|
+
class ListsController < ApplicationController
|
|
116
|
+
http_cache :index, :show, :if => Proc.new { |c| c.__send__(:flash).empty? }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
And if you do not want JSON requests:
|
|
120
|
+
|
|
121
|
+
class ListsController < ApplicationController
|
|
122
|
+
http_cache :index, :show, :unless => Proc.new { |c| c.request.format.json? }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
Or if you want to expire all http cache before 2008, just do:
|
|
126
|
+
|
|
127
|
+
class ListsController < ApplicationController
|
|
128
|
+
http_cache :index, :show, :last_modified => Time.utc(2008)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
If You want to cache a list and automatically expire the cache when
|
|
132
|
+
it changes, just do:
|
|
133
|
+
|
|
134
|
+
class ListsController < ApplicationController
|
|
135
|
+
http_cache :index, :show, :last_modified => :list
|
|
136
|
+
|
|
137
|
+
protected
|
|
138
|
+
def list
|
|
139
|
+
@list ||= List.find(params[:id])
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
You can also set :etag header:
|
|
144
|
+
|
|
145
|
+
class ListsController < ApplicationController
|
|
146
|
+
http_cache :index, :show, :etag => :list
|
|
147
|
+
|
|
148
|
+
protected
|
|
149
|
+
def list
|
|
150
|
+
@list ||= List.find(params[:id])
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
If you are using a resource that doesn't respond to updated_at or updated_on,
|
|
155
|
+
you can pass a method as parameter and it will be called in your resources:
|
|
156
|
+
|
|
157
|
+
class ListsController < ApplicationController
|
|
158
|
+
http_cache :index, :show, :last_modified => :list, :method => :cached_at
|
|
159
|
+
|
|
160
|
+
protected
|
|
161
|
+
def list
|
|
162
|
+
@list ||= List.find(params[:id])
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
Finally, you can also pass an array at :last_modified as below:
|
|
167
|
+
|
|
168
|
+
class ListsController < ApplicationController
|
|
169
|
+
http_cache :index, :show,
|
|
170
|
+
:last_modified => [ :list, Time.utc(2007,12,27) ]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
This will check which one is the most recent to compare with the
|
|
174
|
+
"Last-Modified" field sent by the client.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rake/rdoctask'
|
|
4
|
+
|
|
5
|
+
desc 'Generate documentation for Footnotes plugin.'
|
|
6
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
7
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
8
|
+
rdoc.title = 'Easy HTTP Cache'
|
|
9
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
10
|
+
rdoc.rdoc_files.include('README')
|
|
11
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
12
|
+
end
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'easy_http_cache'
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
module ActionController #:nodoc:
|
|
2
|
+
module Caching
|
|
3
|
+
module HttpCache
|
|
4
|
+
def self.included(base) #:nodoc:
|
|
5
|
+
base.extend(ClassMethods)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# Declares that +actions+ should be cached.
|
|
10
|
+
#
|
|
11
|
+
def http_cache(*actions)
|
|
12
|
+
return unless perform_caching
|
|
13
|
+
options = actions.extract_options!
|
|
14
|
+
|
|
15
|
+
options.assert_valid_keys(
|
|
16
|
+
:last_modified, :method, :etag, :if, :unless
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
http_cache_filter = HttpCacheFilter.new(
|
|
20
|
+
:method => options.delete(:method),
|
|
21
|
+
:last_modified => [options.delete(:last_modified)].flatten.compact,
|
|
22
|
+
:etag => options.delete(:etag)
|
|
23
|
+
)
|
|
24
|
+
filter_options = {:only => actions}.merge(options)
|
|
25
|
+
|
|
26
|
+
before_filter(http_cache_filter, filter_options)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class HttpCacheFilter #:nodoc:
|
|
31
|
+
def initialize(options = {})
|
|
32
|
+
@options = options
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def filter(controller)
|
|
36
|
+
# We don't go ahead if we are rendering a component
|
|
37
|
+
#
|
|
38
|
+
return if component_request?(controller)
|
|
39
|
+
|
|
40
|
+
last_modified = get_last_modified(controller)
|
|
41
|
+
controller.response.last_modified = last_modified if last_modified
|
|
42
|
+
|
|
43
|
+
processed_etags = get_processed_etags(controller)
|
|
44
|
+
controller.response.etag = processed_etags if processed_etags
|
|
45
|
+
|
|
46
|
+
if controller.request.fresh?(controller.response)
|
|
47
|
+
controller.__send__(:head, :not_modified)
|
|
48
|
+
return false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
protected
|
|
53
|
+
# If :etag is an array, it processes all Methods, Procs and Symbols
|
|
54
|
+
# and return them as array. If it's an object, we only evaluate it.
|
|
55
|
+
#
|
|
56
|
+
# Finally, if :etag is not sent but RAILS_CACHE_ID or RAILS_APP_VERSION
|
|
57
|
+
# are set, we return an empty string allowing etag to be performed
|
|
58
|
+
# because those variables, when modified, are a valid way to expire
|
|
59
|
+
# all previous caches.
|
|
60
|
+
#
|
|
61
|
+
def get_processed_etags(controller)
|
|
62
|
+
if @options[:etag].is_a?(Array)
|
|
63
|
+
@options[:etag].collect do |item|
|
|
64
|
+
evaluate_method(item, controller)
|
|
65
|
+
end
|
|
66
|
+
elsif @options[:etag]
|
|
67
|
+
evaluate_method(@options[:etag], controller)
|
|
68
|
+
elsif ENV['RAILS_CACHE_ID'] || ENV['RAILS_APP_VERSION']
|
|
69
|
+
''
|
|
70
|
+
else
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# We perform Last-Modified HTTP Cache when the option :last_modified is sent
|
|
76
|
+
# or no other cache mechanism is set (then we set a very old timestamp).
|
|
77
|
+
#
|
|
78
|
+
def get_last_modified(controller)
|
|
79
|
+
# Then, if @options[:last_modified] is not empty, we run through the array
|
|
80
|
+
# processing all objects (if needed) and return the latest one to be used.
|
|
81
|
+
#
|
|
82
|
+
if !@options[:last_modified].empty?
|
|
83
|
+
@options[:last_modified].collect do |item|
|
|
84
|
+
evaluate_time(item, controller)
|
|
85
|
+
end.compact.sort.last
|
|
86
|
+
elsif @options[:etag].blank?
|
|
87
|
+
Time.utc(0)
|
|
88
|
+
else
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def evaluate_method(method, controller)
|
|
94
|
+
case method
|
|
95
|
+
when Symbol
|
|
96
|
+
controller.__send__(method)
|
|
97
|
+
when Proc, Method
|
|
98
|
+
method.call(controller)
|
|
99
|
+
else
|
|
100
|
+
method
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Evaluate the objects sent and return time objects
|
|
105
|
+
#
|
|
106
|
+
# It process Symbols, String, Proc and Methods, get its results and then
|
|
107
|
+
# call :to_time, :updated_at, :updated_on on it.
|
|
108
|
+
#
|
|
109
|
+
# If the parameter :method is sent, it will try to call it on the object before
|
|
110
|
+
# calling :to_time, :updated_at, :updated_on.
|
|
111
|
+
#
|
|
112
|
+
def evaluate_time(method, controller)
|
|
113
|
+
return nil unless method
|
|
114
|
+
time = evaluate_method(method, controller)
|
|
115
|
+
|
|
116
|
+
time = time.__send__(@options[:method]) if @options[:method].is_a?(Symbol) && time.respond_to?(@options[:method])
|
|
117
|
+
|
|
118
|
+
if time.respond_to?(:to_time)
|
|
119
|
+
time.to_time.utc
|
|
120
|
+
elsif time.respond_to?(:updated_at)
|
|
121
|
+
time.updated_at.utc
|
|
122
|
+
elsif time.respond_to?(:updated_on)
|
|
123
|
+
time.updated_on.utc
|
|
124
|
+
else
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# We should not do http cache when we are using components
|
|
130
|
+
#
|
|
131
|
+
def component_request?(controller)
|
|
132
|
+
controller.instance_variable_get('@parent_controller')
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
ActionController::Base.__send__ :include, ActionController::Caching::HttpCache
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Those lines are plugin test settings
|
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
|
3
|
+
require 'ostruct'
|
|
4
|
+
require File.dirname(__FILE__) + '/../../../../config/environment'
|
|
5
|
+
require File.dirname(__FILE__) + '/../lib/easy_http_cache.rb'
|
|
6
|
+
require 'test_help'
|
|
7
|
+
|
|
8
|
+
ActionController::Base.perform_caching = true
|
|
9
|
+
ActionController::Routing::Routes.draw do |map|
|
|
10
|
+
map.connect ':controller/:action/:id'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class HttpCacheTestController < ActionController::Base
|
|
14
|
+
http_cache :index
|
|
15
|
+
http_cache :show, :last_modified => 2.hours.ago, :if => Proc.new { |c| !c.request.format.json? }
|
|
16
|
+
http_cache :edit, :last_modified => Proc.new{ 30.minutes.ago }
|
|
17
|
+
http_cache :destroy, :last_modified => [2.hours.ago, Proc.new{|c| 30.minutes.ago }]
|
|
18
|
+
http_cache :invalid, :last_modified => [1.hours.ago, false]
|
|
19
|
+
|
|
20
|
+
http_cache :etag, :etag => 'ETAG_CACHE'
|
|
21
|
+
http_cache :etag_array, :etag => [ 'ETAG_CACHE', :resource ]
|
|
22
|
+
http_cache :resources, :last_modified => [:resource, :list, :object]
|
|
23
|
+
http_cache :resources_with_method, :last_modified => [:resource, :list, :object], :method => :cached_at
|
|
24
|
+
|
|
25
|
+
def index
|
|
26
|
+
render :text => '200 OK', :status => 200
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias_method :show, :index
|
|
30
|
+
alias_method :edit, :index
|
|
31
|
+
alias_method :destroy, :index
|
|
32
|
+
alias_method :invalid, :index
|
|
33
|
+
alias_method :etag, :index
|
|
34
|
+
alias_method :etag_array, :index
|
|
35
|
+
alias_method :resources, :index
|
|
36
|
+
alias_method :resources_with_method, :index
|
|
37
|
+
|
|
38
|
+
protected
|
|
39
|
+
|
|
40
|
+
def resource
|
|
41
|
+
resource = OpenStruct.new
|
|
42
|
+
resource.instance_eval do
|
|
43
|
+
def to_param
|
|
44
|
+
12345
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
resource.updated_at = 2.hours.ago
|
|
49
|
+
resource
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def list
|
|
53
|
+
list = OpenStruct.new
|
|
54
|
+
list.updated_on = 30.minutes.ago
|
|
55
|
+
list
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def object
|
|
59
|
+
object = OpenStruct.new
|
|
60
|
+
object.cached_at = 15.minutes.ago
|
|
61
|
+
object
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class HttpCacheTest < Test::Unit::TestCase
|
|
66
|
+
def setup
|
|
67
|
+
reset!
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_last_modified_http_cache
|
|
71
|
+
last_modified_http_cache(:show, 1.hour.ago, 3.hours.ago)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_last_modified_http_cache_with_proc
|
|
75
|
+
last_modified_http_cache(:edit, 15.minutes.ago, 45.minutes.ago)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_last_modified_http_cache_with_array
|
|
79
|
+
last_modified_http_cache(:destroy, 15.minutes.ago, 45.minutes.ago)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_last_modified_http_cache_with_resources
|
|
83
|
+
last_modified_http_cache(:resources, 15.minutes.ago, 45.minutes.ago)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_last_modified_http_cache_with_resources_with_method
|
|
87
|
+
last_modified_http_cache(:resources_with_method, 10.minutes.ago, 20.minutes.ago)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_last_modified_http_cache_discards_invalid_input
|
|
91
|
+
last_modified_http_cache(:invalid, 30.minutes.ago, 90.minutes.ago)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_http_cache_without_input
|
|
95
|
+
get :index
|
|
96
|
+
assert_headers('200 OK', 'private, max-age=0, must-revalidate', 'Last-Modified', Time.utc(0).httpdate)
|
|
97
|
+
reset!
|
|
98
|
+
|
|
99
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = 1.hour.ago.httpdate
|
|
100
|
+
get :index
|
|
101
|
+
assert_headers('304 Not Modified', 'private, max-age=0, must-revalidate', 'Last-Modified', Time.utc(0).httpdate)
|
|
102
|
+
reset!
|
|
103
|
+
|
|
104
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = 3.hours.ago.httpdate
|
|
105
|
+
get :index
|
|
106
|
+
assert_headers('304 Not Modified', 'private, max-age=0, must-revalidate', 'Last-Modified', Time.utc(0).httpdate)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_http_cache_with_conditional_options
|
|
110
|
+
@request.env['HTTP_ACCEPT'] = 'application/json'
|
|
111
|
+
get :show
|
|
112
|
+
assert_nil @response.headers['Last-Modified']
|
|
113
|
+
reset!
|
|
114
|
+
|
|
115
|
+
@request.env['HTTP_ACCEPT'] = 'application/json'
|
|
116
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = 1.hour.ago.httpdate
|
|
117
|
+
get :show
|
|
118
|
+
assert_equal '200 OK', @response.headers['Status']
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_http_cache_without_input_with_env_variable
|
|
122
|
+
ENV['RAILS_APP_VERSION'] = '1.2.3'
|
|
123
|
+
|
|
124
|
+
get :index
|
|
125
|
+
assert_headers('200 OK', 'private, max-age=0, must-revalidate', 'Last-Modified', Time.utc(0).httpdate)
|
|
126
|
+
reset!
|
|
127
|
+
|
|
128
|
+
etag_http_cache(:index, '')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_etag_http_cache
|
|
132
|
+
etag_http_cache(:etag, 'ETAG_CACHE')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_etag_http_cache_with_array
|
|
136
|
+
etag_http_cache(:etag_array, ['ETAG_CACHE', 12345])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_etag_http_cache_with_env_variable
|
|
140
|
+
ENV['RAILS_APP_VERSION'] = '1.2.3'
|
|
141
|
+
etag_http_cache(:etag, 'ETAG_CACHE')
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_should_not_cache_when_rendering_components
|
|
145
|
+
set_parent_controller!
|
|
146
|
+
get :show
|
|
147
|
+
assert_headers('200 OK', 'no-cache')
|
|
148
|
+
|
|
149
|
+
set_parent_controller!
|
|
150
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = 1.hour.ago.httpdate
|
|
151
|
+
get :show
|
|
152
|
+
assert_headers('200 OK', 'no-cache')
|
|
153
|
+
|
|
154
|
+
set_parent_controller!
|
|
155
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = 3.hours.ago.httpdate
|
|
156
|
+
get :show
|
|
157
|
+
assert_headers('200 OK', 'no-cache')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
def reset!
|
|
162
|
+
@request = ActionController::TestRequest.new
|
|
163
|
+
@response = ActionController::TestResponse.new
|
|
164
|
+
@controller = HttpCacheTestController.new
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def set_parent_controller!
|
|
168
|
+
get :index
|
|
169
|
+
old_controller = @controller.dup
|
|
170
|
+
reset!
|
|
171
|
+
|
|
172
|
+
@controller.instance_variable_set('@parent_controller', old_controller)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def etag_for(etag)
|
|
176
|
+
%("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def assert_headers(status, control, cache_header=nil, value=nil)
|
|
180
|
+
assert_equal status, @response.headers['Status']
|
|
181
|
+
assert_equal control, @response.headers['Cache-Control']
|
|
182
|
+
|
|
183
|
+
if cache_header
|
|
184
|
+
if value
|
|
185
|
+
assert_equal value, @response.headers[cache_header]
|
|
186
|
+
else
|
|
187
|
+
assert @response.headers[cache_header]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Goes through a http cache process:
|
|
193
|
+
#
|
|
194
|
+
# 1. Request an action
|
|
195
|
+
# 2. Get a '200 OK' status
|
|
196
|
+
# 3. Request the same action with a not expired HTTP_IF_MODIFIED_SINCE
|
|
197
|
+
# 4. Get a '304 Not Modified' status
|
|
198
|
+
# 5. Request the same action with an expired HTTP_IF_MODIFIED_SINCE
|
|
199
|
+
# 6. Get a '200 OK' status
|
|
200
|
+
#
|
|
201
|
+
def last_modified_http_cache(action, not_expired_time, expired_time)
|
|
202
|
+
get action
|
|
203
|
+
assert_headers('200 OK', 'private, max-age=0, must-revalidate', 'Last-Modified')
|
|
204
|
+
reset!
|
|
205
|
+
|
|
206
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = not_expired_time.httpdate
|
|
207
|
+
get action
|
|
208
|
+
assert_headers('304 Not Modified', 'private, max-age=0, must-revalidate', 'Last-Modified')
|
|
209
|
+
reset!
|
|
210
|
+
|
|
211
|
+
@request.env['HTTP_IF_MODIFIED_SINCE'] = expired_time.httpdate
|
|
212
|
+
get action
|
|
213
|
+
assert_headers('200 OK', 'private, max-age=0, must-revalidate', 'Last-Modified')
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Goes through a http cache process:
|
|
217
|
+
#
|
|
218
|
+
# 1. Request an action
|
|
219
|
+
# 2. Get a '200 OK' status
|
|
220
|
+
# 3. Request the same action with a valid ETAG
|
|
221
|
+
# 4. Get a '304 Not Modified' status
|
|
222
|
+
# 5. Request the same action with an invalid IF_NONE_MATCH
|
|
223
|
+
# 6. Get a '200 OK' status
|
|
224
|
+
#
|
|
225
|
+
def etag_http_cache(action, variable)
|
|
226
|
+
get action
|
|
227
|
+
assert_headers('200 OK', 'private, max-age=0, must-revalidate', 'ETag', etag_for(variable))
|
|
228
|
+
reset!
|
|
229
|
+
|
|
230
|
+
@request.env['HTTP_IF_NONE_MATCH'] = etag_for(variable)
|
|
231
|
+
get action
|
|
232
|
+
assert_headers('304 Not Modified', 'private, max-age=0, must-revalidate', 'ETag', etag_for(variable))
|
|
233
|
+
reset!
|
|
234
|
+
|
|
235
|
+
@request.env['HTTP_IF_NONE_MATCH'] = 'INVALID'
|
|
236
|
+
get action
|
|
237
|
+
assert_headers('200 OK', 'private, max-age=0, must-revalidate', 'ETag', etag_for(variable))
|
|
238
|
+
end
|
|
239
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: josevalim-easy_http_cache
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: "2.0"
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- "Jos\xC3\xA9 Valim"
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2008-11-29 00:00:00 -08:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: Allows Rails applications to use HTTP cache specifications easily.
|
|
17
|
+
email: jose.valim@gmail.com
|
|
18
|
+
executables: []
|
|
19
|
+
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files:
|
|
23
|
+
- README
|
|
24
|
+
files:
|
|
25
|
+
- MIT-LICENSE
|
|
26
|
+
- README
|
|
27
|
+
- Rakefile
|
|
28
|
+
- init.rb
|
|
29
|
+
- lib/easy_http_cache.rb
|
|
30
|
+
- test/easy_http_cache_test.rb
|
|
31
|
+
has_rdoc: true
|
|
32
|
+
homepage: http://github.com/josevalim/easy_http_cache
|
|
33
|
+
post_install_message:
|
|
34
|
+
rdoc_options:
|
|
35
|
+
- --main
|
|
36
|
+
- README
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: "0"
|
|
44
|
+
version:
|
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: "0"
|
|
50
|
+
version:
|
|
51
|
+
requirements: []
|
|
52
|
+
|
|
53
|
+
rubyforge_project:
|
|
54
|
+
rubygems_version: 1.2.0
|
|
55
|
+
signing_key:
|
|
56
|
+
specification_version: 2
|
|
57
|
+
summary: Allows Rails applications to use HTTP cache specifications easily.
|
|
58
|
+
test_files:
|
|
59
|
+
- test/easy_http_cache_test.rb
|