fivepointssolutions-serve 0.9.11
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/History.txt +52 -0
- data/License.txt +20 -0
- data/Quickstart.textile +148 -0
- data/README.txt +105 -0
- data/VERSION.yml +4 -0
- data/bin/serve +12 -0
- data/lib/serve.rb +10 -0
- data/lib/serve/application.rb +150 -0
- data/lib/serve/file_resolver.rb +46 -0
- data/lib/serve/handlers/dynamic_handler.rb +210 -0
- data/lib/serve/handlers/email_handler.rb +23 -0
- data/lib/serve/handlers/file_type_handler.rb +37 -0
- data/lib/serve/handlers/markdown_handler.rb +10 -0
- data/lib/serve/handlers/redirect_handler.rb +13 -0
- data/lib/serve/handlers/sass_handler.rb +19 -0
- data/lib/serve/handlers/textile_handler.rb +10 -0
- data/lib/serve/rails.rb +3 -0
- data/lib/serve/rails/configuration.rb +69 -0
- data/lib/serve/rails/controllers/serve_controller.rb +43 -0
- data/lib/serve/rails/mount.rb +29 -0
- data/lib/serve/rails/routing.rb +25 -0
- data/lib/serve/response_cache.rb +172 -0
- data/lib/serve/version.rb +13 -0
- data/lib/serve/webrick/extensions.rb +98 -0
- data/lib/serve/webrick/server.rb +17 -0
- data/lib/serve/webrick/servlet.rb +19 -0
- data/spec/response_cache_spec.rb +248 -0
- data/spec/serve_application_spec.rb +75 -0
- data/spec/serve_spec.rb +13 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +12 -0
- metadata +89 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
class ServeController < ActionController::Base
|
2
|
+
def show
|
3
|
+
response.headers.delete('Cache-Control')
|
4
|
+
cache = Serve::Rails.cache(request)
|
5
|
+
if cache.response_cached?(request.path)
|
6
|
+
cache.update_response(request.path, response, request)
|
7
|
+
else
|
8
|
+
mount = Serve::Rails.configuration.mounts.detect {|m| m.route == params[:serve_route]}
|
9
|
+
if path = mount.resolve_path(params[:path] || '/')
|
10
|
+
handler_class = Serve::FileTypeHandler.find(path)
|
11
|
+
handler = handler_class.new(mount.root_path, path)
|
12
|
+
install_view_helpers(handler, mount.view_helpers) if handler_class == Serve::DynamicHandler
|
13
|
+
handler.process(request, response)
|
14
|
+
cache.cache_response(request.path, response)
|
15
|
+
else
|
16
|
+
render :text => 'not found', :status => 404
|
17
|
+
end
|
18
|
+
end
|
19
|
+
@performed_render = true
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# This is a quick solution: We need to install the view helpers defined by
|
25
|
+
# the Rails application after those of the Serve'd app's view helpers, so
|
26
|
+
# that those of the Rails application override them.
|
27
|
+
#
|
28
|
+
# Ideally, we'll work toward moving some of this Rails stuff further up
|
29
|
+
# into Serve, so that a simple Serve'd directory uses almost all of the
|
30
|
+
# same code, like the Configuration.
|
31
|
+
#
|
32
|
+
def install_view_helpers(handler, view_helpers)
|
33
|
+
controller = self
|
34
|
+
handler.metaclass.module_eval do
|
35
|
+
define_method :install_view_helpers do |context|
|
36
|
+
super(context)
|
37
|
+
# Make available to view helpers
|
38
|
+
context.instance_variable_set('@controller', controller)
|
39
|
+
context.extend view_helpers
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Serve
|
2
|
+
module Rails
|
3
|
+
class Mount # :nodoc:
|
4
|
+
attr_reader :root_path, :route, :view_helpers
|
5
|
+
|
6
|
+
def initialize(route, root)
|
7
|
+
@route, @root_path = route, root
|
8
|
+
@view_helpers = Module.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# Appends to the collection of view helpers that will be made availabe
|
12
|
+
# to the DynamicHandler.
|
13
|
+
#
|
14
|
+
def append_view_helper(m)
|
15
|
+
@view_helpers.module_eval do
|
16
|
+
include m
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def connection
|
21
|
+
@route == '/' ? '*path' : "#{@route}/*path"
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve_path(path)
|
25
|
+
Serve.resolve_file(@root_path, path)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Serve
|
2
|
+
module Rails
|
3
|
+
module Routing
|
4
|
+
|
5
|
+
module MapperExtensions
|
6
|
+
def serve
|
7
|
+
serve_mounts = Serve::Rails.configuration.mounts
|
8
|
+
default_site_path = File.join(::Rails.root, 'site')
|
9
|
+
|
10
|
+
if File.directory?(default_site_path) && !serve_mounts.detect {|m| m.route == '/'}
|
11
|
+
mount('/', default_site_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
serve_mounts.each do |mount|
|
15
|
+
@set.add_route(mount.connection, {
|
16
|
+
:controller => 'serve', :action => 'show',
|
17
|
+
:serve_route => mount.route
|
18
|
+
})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# This code and it's tests are adapted from the Radiant CMS project.
|
2
|
+
#
|
3
|
+
# Copyright (c) 2006-2007, John W. Long.
|
4
|
+
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Serve
|
8
|
+
class ResponseCache
|
9
|
+
@@defaults = {
|
10
|
+
:expire_time => 5.minutes,
|
11
|
+
:default_extension => '.yml',
|
12
|
+
:perform_caching => true,
|
13
|
+
:use_x_sendfile => false
|
14
|
+
}
|
15
|
+
cattr_accessor :defaults
|
16
|
+
|
17
|
+
attr_accessor :directory, :expire_time, :default_extension, :perform_caching, :logger, :use_x_sendfile
|
18
|
+
alias :page_cache_directory :directory
|
19
|
+
alias :page_cache_extension :default_extension
|
20
|
+
private :page_cache_directory, :page_cache_extension
|
21
|
+
|
22
|
+
# Creates a ResponseCache object with the specified options.
|
23
|
+
#
|
24
|
+
# Options are as follows:
|
25
|
+
# :directory :: the path to the temporary cache directory
|
26
|
+
# :expire_time :: the number of seconds a cached response is considered valid (defaults to 5 min)
|
27
|
+
# :default_extension :: the extension cached files should use (defaults to '.yml')
|
28
|
+
# :perform_caching :: boolean value that turns caching on or off (defaults to true)
|
29
|
+
# :logger :: the application logging object
|
30
|
+
# :use_x_sendfile :: use X-Sendfile headers to speed up transfer of cached pages (not available on all web servers)
|
31
|
+
#
|
32
|
+
def initialize(options = {})
|
33
|
+
options = options.symbolize_keys.reverse_merge(defaults)
|
34
|
+
self.directory = options[:directory]
|
35
|
+
self.expire_time = options[:expire_time]
|
36
|
+
self.default_extension = options[:default_extension]
|
37
|
+
self.perform_caching = options[:perform_caching]
|
38
|
+
self.logger = options[:logger]
|
39
|
+
self.use_x_sendfile = options[:use_x_sendfile]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Caches a response object for path to disk.
|
43
|
+
def cache_response(path, response)
|
44
|
+
if perform_caching
|
45
|
+
path = clean(path)
|
46
|
+
write_response(path, response)
|
47
|
+
end
|
48
|
+
response
|
49
|
+
end
|
50
|
+
|
51
|
+
# If perform_caching is set to true, updates a response object so that it mirrors the
|
52
|
+
# cached version. The request object is required to perform Last-Modified/If-Modified-Since
|
53
|
+
# checks--it is left optional to allow for backwards compatability.
|
54
|
+
def update_response(path, response, request=nil)
|
55
|
+
if perform_caching
|
56
|
+
path = clean(path)
|
57
|
+
read_response(path, response, request)
|
58
|
+
end
|
59
|
+
response
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns metadata for path.
|
63
|
+
def read_metadata(path)
|
64
|
+
path = clean(path)
|
65
|
+
name = "#{page_cache_path(path)}.yml"
|
66
|
+
if File.exists?(name) and not File.directory?(name)
|
67
|
+
content = File.open(name, "rb") { |f| f.read }
|
68
|
+
metadata = YAML::load(content)
|
69
|
+
metadata if metadata['expires'] >= Time.now
|
70
|
+
end
|
71
|
+
rescue
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns true if a response is cached at the specified path.
|
76
|
+
def response_cached?(path)
|
77
|
+
perform_caching && !!read_metadata(path)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Expires the cached response for the specified path.
|
81
|
+
def expire_response(path)
|
82
|
+
path = clean(path)
|
83
|
+
expire_page(path)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Expires the entire cache.
|
87
|
+
def clear
|
88
|
+
Dir["#{directory}/*"].each do |f|
|
89
|
+
FileUtils.rm_rf f
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
# Ensures that path begins with a slash and remove extra slashes.
|
95
|
+
def clean(path)
|
96
|
+
path = path.gsub(%r{/+}, '/')
|
97
|
+
%r{^/?(.*?)/?$}.match(path)
|
98
|
+
"/#{$1}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Reads a cached response from disk and updates a response object.
|
102
|
+
def read_response(path, response, request)
|
103
|
+
file_path = page_cache_path(path)
|
104
|
+
if metadata = read_metadata(path)
|
105
|
+
response.headers.merge!(metadata['headers'] || {})
|
106
|
+
if client_has_cache?(metadata, request)
|
107
|
+
response.headers.merge!('Status' => '304 Not Modified')
|
108
|
+
elsif use_x_sendfile
|
109
|
+
response.headers.merge!('X-Sendfile' => "#{file_path}.data")
|
110
|
+
else
|
111
|
+
response.body = File.open("#{file_path}.data", "rb") {|f| f.read}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
response
|
115
|
+
end
|
116
|
+
|
117
|
+
def client_has_cache?(metadata, request)
|
118
|
+
return false unless request
|
119
|
+
request_time = Time.httpdate(request.env["HTTP_IF_MODIFIED_SINCE"]) rescue nil
|
120
|
+
response_time = Time.httpdate(metadata['headers']['Last-Modified']) rescue nil
|
121
|
+
return request_time && response_time && response_time <= request_time
|
122
|
+
end
|
123
|
+
|
124
|
+
# Writes a response to disk.
|
125
|
+
def write_response(path, response)
|
126
|
+
if response.cache_timeout
|
127
|
+
if Time === response.cache_timeout
|
128
|
+
expires = response.cache_timeout
|
129
|
+
else
|
130
|
+
expires = Time.now + response.cache_timeout
|
131
|
+
end
|
132
|
+
else
|
133
|
+
expires = Time.now + self.expire_time
|
134
|
+
end
|
135
|
+
response.headers['Last-Modified'] ||= Time.now.httpdate
|
136
|
+
metadata = {
|
137
|
+
'headers' => response.headers,
|
138
|
+
'expires' => expires
|
139
|
+
}.to_yaml
|
140
|
+
cache_page(metadata, response.body, path)
|
141
|
+
end
|
142
|
+
|
143
|
+
def page_cache_path(path)
|
144
|
+
path = (path.empty? || path == "/") ? "/_site-root" : URI.unescape(path)
|
145
|
+
root_dir = File.expand_path(page_cache_directory)
|
146
|
+
cache_path = File.expand_path(File.join(root_dir, path), root_dir)
|
147
|
+
cache_path if cache_path.index(root_dir) == 0
|
148
|
+
end
|
149
|
+
|
150
|
+
def expire_page(path)
|
151
|
+
return unless perform_caching
|
152
|
+
|
153
|
+
if path = page_cache_path(path)
|
154
|
+
File.delete("#{path}.yml") if File.exists?("#{path}.yml")
|
155
|
+
File.delete("#{path}.data") if File.exists?("#{path}.data")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def cache_page(metadata, content, path)
|
160
|
+
return unless perform_caching
|
161
|
+
|
162
|
+
if path = page_cache_path(path)
|
163
|
+
FileUtils.makedirs(File.dirname(path))
|
164
|
+
#dont want yml without data
|
165
|
+
File.open("#{path}.data", "wb+") { |f| f.write(content) }
|
166
|
+
File.open("#{path}.yml", "wb+") { |f| f.write(metadata) }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
NULL_CACHE = ResponseCache.new(:perform_caching => false)
|
172
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Portions from Rails, Copyright (c) 2004-2008 David Heinemeier Hansson
|
2
|
+
require 'active_support/memoizable'
|
3
|
+
|
4
|
+
module Serve #:nodoc:
|
5
|
+
module WEBrick #:nodoc:
|
6
|
+
|
7
|
+
module FileHandlerExtensions #:nodoc:
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(self)
|
10
|
+
base.class_eval do
|
11
|
+
alias :search_file_without_auto_appending :search_file
|
12
|
+
alias :search_file :search_file_with_auto_appending
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def search_file_with_auto_appending(req, res, basename)
|
17
|
+
full_path = File.join(res.filename, basename)
|
18
|
+
return basename if File.file?(full_path)
|
19
|
+
return nil if File.directory?(full_path)
|
20
|
+
Serve.resolve_file(Dir.pwd, req.path)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
WEBrick::HTTPRequest.module_eval do
|
28
|
+
extend ActiveSupport::Memoizable
|
29
|
+
|
30
|
+
alias headers header
|
31
|
+
|
32
|
+
# Returns the \host for this request, such as "example.com".
|
33
|
+
def raw_host_with_port
|
34
|
+
@host + ':' + @port.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
|
38
|
+
def protocol
|
39
|
+
ssl? ? 'https://' : 'http://'
|
40
|
+
end
|
41
|
+
memoize :protocol
|
42
|
+
|
43
|
+
# Is this an SSL request?
|
44
|
+
def ssl?
|
45
|
+
meta_vars['HTTPS'] == 'on'
|
46
|
+
end
|
47
|
+
|
48
|
+
def params
|
49
|
+
query.inject({}) {|m, (k,v)| m[k.to_s.to_sym] = v; m}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the host for this request, such as example.com.
|
53
|
+
def host
|
54
|
+
raw_host_with_port.sub(/:\d+$/, '')
|
55
|
+
end
|
56
|
+
memoize :host
|
57
|
+
|
58
|
+
# Returns a \host:\port string for this request, such as "example.com" or
|
59
|
+
# "example.com:8080".
|
60
|
+
def host_with_port
|
61
|
+
"#{host}#{port_string}"
|
62
|
+
end
|
63
|
+
memoize :host_with_port
|
64
|
+
|
65
|
+
# Returns the port number of this request as an integer.
|
66
|
+
def port
|
67
|
+
if raw_host_with_port =~ /:(\d+)$/
|
68
|
+
$1.to_i
|
69
|
+
else
|
70
|
+
standard_port
|
71
|
+
end
|
72
|
+
end
|
73
|
+
memoize :port
|
74
|
+
|
75
|
+
# Returns the standard \port number for this request's protocol.
|
76
|
+
def standard_port
|
77
|
+
case protocol
|
78
|
+
when 'https://' then 443
|
79
|
+
else 80
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a \port suffix like ":8080" if the \port number of this request
|
84
|
+
# is not the default HTTP \port 80 or HTTPS \port 443.
|
85
|
+
def port_string
|
86
|
+
port == standard_port ? '' : ":#{port}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
WEBrick::HTTPResponse.module_eval do
|
91
|
+
alias headers header
|
92
|
+
|
93
|
+
def redirect(url, status)
|
94
|
+
set_redirect(::WEBrick::HTTPStatus[status.to_i], url)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
WEBrick::HTTPServlet::FileHandler.class_eval { include Serve::WEBrick::FileHandlerExtensions }
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Serve #:nodoc:
|
2
|
+
module WEBrick
|
3
|
+
class Server < ::WEBrick::HTTPServer #:nodoc:
|
4
|
+
def self.register_handlers
|
5
|
+
extensions = []
|
6
|
+
Serve::FileTypeHandler.handlers.each do |ext, handler|
|
7
|
+
extensions << ext
|
8
|
+
handler_servlet = Class.new(Serve::WEBrick::Servlet) do
|
9
|
+
define_method(:handler) { handler }
|
10
|
+
end
|
11
|
+
::WEBrick::HTTPServlet::FileHandler.add_handler(ext, handler_servlet)
|
12
|
+
end
|
13
|
+
extensions
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Serve #:nodoc:
|
2
|
+
module WEBrick
|
3
|
+
class Servlet < ::WEBrick::HTTPServlet::AbstractServlet #:nodoc:
|
4
|
+
def do_GET(req, res)
|
5
|
+
begin
|
6
|
+
path = Serve.resolve_file(Dir.pwd, req.path)
|
7
|
+
handler.new(Dir.pwd, path).process(req, res)
|
8
|
+
rescue StandardError => ex
|
9
|
+
raise
|
10
|
+
rescue Exception => ex
|
11
|
+
@logger.error(ex)
|
12
|
+
raise ::WEBrick::HTTPStatus::InternalServerError, ex.message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
alias do_POST do_GET
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Serve::ResponseCache do
|
4
|
+
class SilentLogger
|
5
|
+
def method_missing(*args); end
|
6
|
+
end
|
7
|
+
|
8
|
+
class TestRequest < Struct.new(:body, :env)
|
9
|
+
end
|
10
|
+
|
11
|
+
class TestResponse < Struct.new(:body, :headers, :cache_timeout)
|
12
|
+
def initialize(body = '', headers = {})
|
13
|
+
self.body = body
|
14
|
+
self.headers = headers
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
before :all do
|
19
|
+
@dir = File.expand_path("#{File.expand_path(File.dirname(__FILE__))}/tmp/cache")
|
20
|
+
@baddir = File.expand_path("#{File.expand_path(File.dirname(__FILE__))}/tmp/badcache")
|
21
|
+
end
|
22
|
+
|
23
|
+
before :each do
|
24
|
+
FileUtils.rm_rf @baddir
|
25
|
+
@cache = Serve::ResponseCache.new(
|
26
|
+
:directory => @dir,
|
27
|
+
:perform_caching => true
|
28
|
+
)
|
29
|
+
@cache.clear
|
30
|
+
end
|
31
|
+
|
32
|
+
after :each do
|
33
|
+
FileUtils.rm_rf @dir if File.exists? @dir
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should initialize with defaults' do
|
37
|
+
@cache = Serve::ResponseCache.new
|
38
|
+
@cache.expire_time.should == 5.minutes
|
39
|
+
@cache.default_extension.should == '.yml'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should initialize with options' do
|
43
|
+
@cache = Serve::ResponseCache.new(
|
44
|
+
:directory => "test",
|
45
|
+
:expire_time => 5,
|
46
|
+
:default_extension => ".xhtml",
|
47
|
+
:perform_caching => false,
|
48
|
+
:logger => SilentLogger.new
|
49
|
+
)
|
50
|
+
@cache.directory.should == "test"
|
51
|
+
@cache.expire_time.should == 5
|
52
|
+
@cache.default_extension.should == ".xhtml"
|
53
|
+
@cache.perform_caching.should == false
|
54
|
+
@cache.logger.should be_kind_of(SilentLogger)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should cache response' do
|
58
|
+
['test/me', '/test/me', 'test/me/', '/test/me/', 'test//me'].each do |url|
|
59
|
+
@cache.clear
|
60
|
+
response = response('content', 'Last-Modified' => 'Tue, 27 Feb 2007 06:13:43 GMT')
|
61
|
+
response.cache_timeout = Time.gm(2007, 2, 8, 17, 37, 9)
|
62
|
+
@cache.cache_response(url, response)
|
63
|
+
name = "#{@dir}/test/me.yml"
|
64
|
+
File.exists?(name).should == true
|
65
|
+
file(name).should == "--- \nexpires: 2007-02-08 17:37:09 Z\nheaders: \n Last-Modified: Tue, 27 Feb 2007 06:13:43 GMT\n"
|
66
|
+
data_name = "#{@dir}/test/me.data"
|
67
|
+
file(data_name).should == "content"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'cache response with extension' do
|
72
|
+
@cache.cache_response("styles.css", response('content'))
|
73
|
+
File.exists?("#{@dir}/styles.css.yml").should == true
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'cache response without caching' do
|
77
|
+
@cache.perform_caching = false
|
78
|
+
@cache.cache_response('test', response('content'))
|
79
|
+
File.exists?("#{@dir}/test.yml").should == false
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'update response' do
|
83
|
+
@cache.cache_response('/test/me', response('content'))
|
84
|
+
['test/me', '/test/me', 'test/me/', '/test/me/', 'test//me'].each do |url|
|
85
|
+
@cache.update_response(url, response, TestRequest).body.should == 'content'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'update response nonexistant' do
|
90
|
+
@cache.update_response('nothing/here', response, TestRequest).body.should == ''
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'update response without caching' do
|
94
|
+
@cache.cache_response('/test/me', response('content'))
|
95
|
+
@cache.perform_caching = false
|
96
|
+
@cache.update_response('/test/me', response, TestRequest).body.should == ''
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'cache' do
|
100
|
+
result = @cache.cache_response('test', response('content', 'Content-Type' => 'text/plain'))
|
101
|
+
cached = @cache.update_response('test', response, TestRequest)
|
102
|
+
cached.body.should == 'content'
|
103
|
+
cached.headers['Content-Type'].should == 'text/plain'
|
104
|
+
result.should be_kind_of(TestResponse)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'expire response' do
|
108
|
+
@cache.cache_response('test', response('content'))
|
109
|
+
@cache.expire_response('test')
|
110
|
+
@cache.update_response('test', response, TestRequest).body.should == ''
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'clear' do
|
114
|
+
@cache.cache_response('test1', response('content'))
|
115
|
+
@cache.cache_response('test2', response('content'))
|
116
|
+
Dir["#{@dir}/*"].size.should == 4
|
117
|
+
|
118
|
+
@cache.clear
|
119
|
+
Dir["#{@dir}/*"].size.should == 0
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'response_cached?' do
|
123
|
+
@cache.response_cached?('test').should == false
|
124
|
+
result = @cache.cache_response('test', response('content'))
|
125
|
+
@cache.response_cached?('test').should == true
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'response_cached? should not answer true when response is cached but preform_caching option is false' do
|
129
|
+
@cache.cache_response('test', response('content'))
|
130
|
+
@cache.perform_caching = false
|
131
|
+
@cache.response_cached?('test').should == false
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'response_cached? with timeout' do
|
135
|
+
@cache.expire_time = 1
|
136
|
+
result = @cache.cache_response('test', response('content'))
|
137
|
+
sleep 1.5
|
138
|
+
@cache.response_cached?('test').should == false
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'response_cached? timeout with response setting' do
|
142
|
+
@cache.expire_time = 1
|
143
|
+
response = response('content')
|
144
|
+
response.cache_timeout = 3.seconds
|
145
|
+
result = @cache.cache_response('test', response)
|
146
|
+
sleep 1.5
|
147
|
+
@cache.response_cached?('test').should == true
|
148
|
+
sleep 2
|
149
|
+
@cache.response_cached?('test').should == false
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'send using x_sendfile header' do
|
153
|
+
@cache.use_x_sendfile = true
|
154
|
+
result = @cache.cache_response('test', response('content', 'Content-Type' => 'text/plain'))
|
155
|
+
cached = @cache.update_response('test', response, TestRequest)
|
156
|
+
cached.body.should == ''
|
157
|
+
cached.headers['X-Sendfile'].should == "#{@dir}/test.data"
|
158
|
+
cached.headers['Content-Type'].should == 'text/plain'
|
159
|
+
result.should be_kind_of(TestResponse)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'send cached page with last modified' do
|
163
|
+
last_modified = Time.now.httpdate
|
164
|
+
result = @cache.cache_response('test', response('content', 'Last-Modified' => last_modified))
|
165
|
+
request = TestRequest.new
|
166
|
+
request.env = { 'HTTP_IF_MODIFIED_SINCE' => last_modified }
|
167
|
+
second_call = @cache.update_response('test', response, request)
|
168
|
+
second_call.headers['Status'].should match(/^304/)
|
169
|
+
second_call.body.should == ''
|
170
|
+
result.should be_kind_of(TestResponse)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'send cached page with old last modified' do
|
174
|
+
last_modified = Time.now.httpdate
|
175
|
+
result = @cache.cache_response('test', response('content', 'Last-Modified' => last_modified))
|
176
|
+
request = TestRequest.new
|
177
|
+
request.env = { 'HTTP_IF_MODIFIED_SINCE' => 5.minutes.ago.httpdate }
|
178
|
+
second_call = @cache.update_response('test', response, request)
|
179
|
+
second_call.body.should == 'content'
|
180
|
+
result.should be_kind_of(TestResponse)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'not cached if metadata empty' do
|
184
|
+
FileUtils.makedirs(@dir)
|
185
|
+
File.open("#{@dir}/test_me.yml", 'w') { }
|
186
|
+
@cache.response_cached?('/test_me').should == false
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'not cached if metadata broken' do
|
190
|
+
FileUtils.makedirs(@dir)
|
191
|
+
File.open("#{@dir}/test_me.yml", 'w') {|f| f.puts '::: bad yaml file:::' }
|
192
|
+
@cache.response_cached?('/test_me').should == false
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'not cached if metadata not hash' do
|
196
|
+
FileUtils.makedirs(@dir)
|
197
|
+
File.open("#{@dir}/test_me.yml", 'w') {|f| f.puts ':symbol' }
|
198
|
+
@cache.response_cached?('/test_me').should == false
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'not cached if metadata has no expire' do
|
202
|
+
FileUtils.makedirs(@dir)
|
203
|
+
File.open("#{@dir}/test_me.yml", 'w') { |f| f.puts "--- \nheaders: \n Last-Modified: Tue, 27 Feb 2007 06:13:43 GMT\n" }
|
204
|
+
@cache.response_cached?('/test_me').should == false
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'cache cant write outside dir' do
|
208
|
+
@cache.cache_response('../badcache/cache_cant_write_outside_dir', response('content'))
|
209
|
+
File.exist?("#{@baddir}/cache_cant_write_outside_dir.yml").should == false
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'cache cannot read outside dir' do
|
213
|
+
FileUtils.makedirs(@baddir)
|
214
|
+
@cache.cache_response('/test_me', response('content'))
|
215
|
+
File.rename "#{@dir}/test_me.yml", "#{@baddir}/test_me.yml"
|
216
|
+
File.rename "#{@dir}/test_me.data", "#{@baddir}/test_me.data"
|
217
|
+
@cache.response_cached?('/../badcache/test_me').should == false
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'cache cannot expire outside dir' do
|
221
|
+
FileUtils.makedirs(@baddir)
|
222
|
+
@cache.cache_response('/test_me', response('content'))
|
223
|
+
File.rename "#{@dir}/test_me.yml", "#{@baddir}/test_me.yml"
|
224
|
+
File.rename "#{@dir}/test_me.data", "#{@baddir}/test_me.data"
|
225
|
+
@cache.expire_response('/../badcache/test_me')
|
226
|
+
File.exist?("#{@baddir}/test_me.yml").should == true
|
227
|
+
File.exist?("#{@baddir}/test_me.data").should == true
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should store indexes correctly' do
|
231
|
+
@cache.cache_response('/', response('content'))
|
232
|
+
@cache.response_cached?('_site-root').should == true
|
233
|
+
@cache.response_cached?('/') .should == true
|
234
|
+
File.exist?("#{@dir}/../cache.yml").should == false
|
235
|
+
File.exist?("#{@dir}/../cache.data").should == false
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def file(filename)
|
241
|
+
open(filename) { |f| f.read } rescue ''
|
242
|
+
end
|
243
|
+
|
244
|
+
def response(*args)
|
245
|
+
TestResponse.new(*args)
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|