serve 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +0,0 @@
1
- module Serve #:nodoc:
2
- class MarkdownHandler < FileTypeHandler #:nodoc:
3
- extension 'markdown'
4
-
5
- def parse(string)
6
- require 'bluecloth'
7
- "<html><body>#{ BlueCloth.new(string).to_html }</body></html>"
8
- end
9
- end
10
- end
@@ -1,22 +0,0 @@
1
- module Serve #:nodoc:
2
- class StaticHandler < FileTypeHandler #:nodoc:
3
- extension 'txt', 'text', 'xml', 'atom', 'rss', 'rdf', 'htm', 'html'
4
-
5
- def content_type
6
- case File.extname(@script_filename)
7
- when '.txt', '.text'
8
- 'text/plain'
9
- when '.xml'
10
- 'text/xml'
11
- when '.atom'
12
- 'application/atom+xml'
13
- when '.rss'
14
- 'application/rss+xml'
15
- when '.rdf'
16
- 'application/rdf+xml'
17
- else
18
- 'text/html'
19
- end
20
- end
21
- end
22
- end
@@ -1,10 +0,0 @@
1
- module Serve #:nodoc:
2
- class TextileHandler < FileTypeHandler #:nodoc:
3
- extension 'textile'
4
-
5
- def parse(string)
6
- require 'redcloth'
7
- "<html><body>#{ RedCloth.new(string).to_html }</body></html>"
8
- end
9
- end
10
- end
data/lib/serve/rails.rb DELETED
@@ -1,4 +0,0 @@
1
- require 'serve/rails/configuration'
2
- require 'serve/rails/mount'
3
- require 'serve/rails/serve_controller'
4
- require 'serve/rails/routing'
@@ -1,69 +0,0 @@
1
- module Serve # :nodoc:
2
- module Rails
3
- class Configuration # :nodoc:
4
- attr_reader :mounts, :view_helpers
5
-
6
- def initialize
7
- Serve::ResponseCache.defaults.update(
8
- :logger => ActionController::Base.logger,
9
- :perform_caching => ActionController::Base.perform_caching
10
- )
11
-
12
- @mounts = []
13
-
14
- define_find_cache do |request|
15
- @default_cache ||= Serve::ResponseCache.new(
16
- :directory => File.join(::Rails.root, 'tmp', 'serve_cache')
17
- )
18
- end
19
- end
20
-
21
- def define_find_cache(&block)
22
- singleton_class.module_eval do
23
- define_method :find_cache, &block
24
- end
25
- end
26
-
27
- def mount(route, root_path)
28
- m = Mount.new(route, root_path)
29
- @mounts << m
30
- yield m if block_given?
31
- m
32
- end
33
- end
34
-
35
- # Answer the cache for the given request. Delegates to the 'find_cache'
36
- # block, which defaults to a single cache.
37
- #
38
- def self.cache(request)
39
- configuration.find_cache(request)
40
- end
41
-
42
- def self.configuration
43
- @configuration ||= Configuration.new
44
- end
45
-
46
- # The most powerful way to configure Serve::Rails, the configuration is
47
- # yielded to the provided block. Multiple calls are cumulative - there is
48
- # only one configuration instance.
49
- #
50
- def self.configure
51
- yield configuration
52
- end
53
-
54
- # Define the strategy for resolving the cache for a request. This allows
55
- # applications to provide logic like cache-per-browser. The default is a
56
- # single cache.
57
- #
58
- def self.find_cache(&block)
59
- configuration.define_find_cache(&block)
60
- end
61
-
62
- # Mount a route on a directory. This allows an application to have
63
- # multiple served directories, each connected to different routes.
64
- #
65
- def self.mount(route, root_path, &block)
66
- configuration.mount(route, root_path, &block)
67
- end
68
- end
69
- end
@@ -1,38 +0,0 @@
1
- module Serve
2
- module Rails
3
- class Mount # :nodoc:
4
- attr_reader :root_path, :route
5
-
6
- def initialize(route, root)
7
- @route, @root_path = route, root
8
- @view_helper_module_names = []
9
- end
10
-
11
- # Appends to the collection of view helpers that will be made availabe
12
- # to the DynamicHandler. These should be module names. They will be
13
- # constantized in order to allow for re-loading in development mode.
14
- #
15
- def append_view_helper(module_name)
16
- @view_helper_module_names << module_name
17
- end
18
-
19
- def connection
20
- @route == '/' ? '*path' : "#{@route}/*path"
21
- end
22
-
23
- def resolve_path(path)
24
- Serve.resolve_file(@root_path, path)
25
- end
26
-
27
- def view_helpers
28
- helpers = Module.new
29
- @view_helper_module_names.each do |module_name|
30
- helpers.module_eval do
31
- include module_name.constantize
32
- end
33
- end
34
- helpers
35
- end
36
- end
37
- end
38
- end
@@ -1,25 +0,0 @@
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
@@ -1,52 +0,0 @@
1
- module Serve
2
- module Rails
3
-
4
- module ServeController
5
- def show
6
- response.headers.delete('Cache-Control')
7
- cache = Serve::Rails.cache(request)
8
- if cache.response_cached?(request.path)
9
- cache.update_response(request.path, response, request)
10
- else
11
- mount = Serve::Rails.configuration.mounts.detect {|m| m.route == params[:serve_route]}
12
- if path = mount.resolve_path(params[:path] || '/')
13
- handler_class = Serve::FileTypeHandler.find(path)
14
- handler = handler_class.new(mount.root_path, path)
15
- install_view_helpers(handler, mount.view_helpers) if handler_class == Serve::DynamicHandler
16
- handler.process(request, response)
17
- cache.cache_response(request.path, response)
18
- else
19
- render_not_found
20
- end
21
- end
22
- @performed_render = true
23
- end
24
-
25
- private
26
- def render_not_found
27
- render :text => 'not found', :status => 404
28
- end
29
-
30
- # This is a quick solution: We need to install the view helpers defined by
31
- # the Rails application after those of the Serve'd app's view helpers, so
32
- # that those of the Rails application override them.
33
- #
34
- # Ideally, we'll work toward moving some of this Rails stuff further up
35
- # into Serve, so that a simple Serve'd directory uses almost all of the
36
- # same code, like the Configuration.
37
- #
38
- def install_view_helpers(handler, view_helpers)
39
- controller = self
40
- handler.singleton_class.module_eval do
41
- define_method :install_view_helpers do |context|
42
- super(context)
43
- # Make available to view helpers
44
- context.instance_variable_set('@controller', controller)
45
- context.extend view_helpers
46
- end
47
- end
48
- end
49
- end
50
-
51
- end
52
- end
@@ -1,170 +0,0 @@
1
- # This code and it's tests are adapted from the Radiant CMS project.
2
-
3
- require 'uri'
4
-
5
- module Serve
6
- class ResponseCache
7
- @@defaults = {
8
- :expire_time => 5.minutes,
9
- :default_extension => '.yml',
10
- :perform_caching => true,
11
- :use_x_sendfile => false
12
- }
13
- cattr_accessor :defaults
14
-
15
- attr_accessor :directory, :expire_time, :default_extension, :perform_caching, :logger, :use_x_sendfile
16
- alias :page_cache_directory :directory
17
- alias :page_cache_extension :default_extension
18
- private :page_cache_directory, :page_cache_extension
19
-
20
- # Creates a ResponseCache object with the specified options.
21
- #
22
- # Options are as follows:
23
- # :directory :: the path to the temporary cache directory
24
- # :expire_time :: the number of seconds a cached response is considered valid (defaults to 5 min)
25
- # :default_extension :: the extension cached files should use (defaults to '.yml')
26
- # :perform_caching :: boolean value that turns caching on or off (defaults to true)
27
- # :logger :: the application logging object
28
- # :use_x_sendfile :: use X-Sendfile headers to speed up transfer of cached pages (not available on all web servers)
29
- #
30
- def initialize(options = {})
31
- options = options.symbolize_keys.reverse_merge(defaults)
32
- self.directory = options[:directory]
33
- self.expire_time = options[:expire_time]
34
- self.default_extension = options[:default_extension]
35
- self.perform_caching = options[:perform_caching]
36
- self.logger = options[:logger]
37
- self.use_x_sendfile = options[:use_x_sendfile]
38
- end
39
-
40
- # Caches a response object for path to disk.
41
- def cache_response(path, response)
42
- if perform_caching
43
- path = clean(path)
44
- write_response(path, response)
45
- end
46
- response
47
- end
48
-
49
- # If perform_caching is set to true, updates a response object so that it mirrors the
50
- # cached version. The request object is required to perform Last-Modified/If-Modified-Since
51
- # checks--it is left optional to allow for backwards compatability.
52
- def update_response(path, response, request=nil)
53
- if perform_caching
54
- path = clean(path)
55
- read_response(path, response, request)
56
- end
57
- response
58
- end
59
-
60
- # Returns metadata for path.
61
- def read_metadata(path)
62
- path = clean(path)
63
- name = "#{page_cache_path(path)}.yml"
64
- if File.exists?(name) and not File.directory?(name)
65
- content = File.open(name, "rb") { |f| f.read }
66
- metadata = YAML::load(content)
67
- metadata if metadata['expires'] >= Time.now
68
- end
69
- rescue
70
- nil
71
- end
72
-
73
- # Returns true if a response is cached at the specified path.
74
- def response_cached?(path)
75
- perform_caching && !!read_metadata(path)
76
- end
77
-
78
- # Expires the cached response for the specified path.
79
- def expire_response(path)
80
- path = clean(path)
81
- expire_page(path)
82
- end
83
-
84
- # Expires the entire cache.
85
- def clear
86
- Dir["#{directory}/*"].each do |f|
87
- FileUtils.rm_rf f
88
- end
89
- end
90
-
91
- private
92
- # Ensures that path begins with a slash and remove extra slashes.
93
- def clean(path)
94
- path = path.gsub(%r{/+}, '/')
95
- %r{^/?(.*?)/?$}.match(path)
96
- "/#{$1}"
97
- end
98
-
99
- # Reads a cached response from disk and updates a response object.
100
- def read_response(path, response, request)
101
- file_path = page_cache_path(path)
102
- if metadata = read_metadata(path)
103
- response.headers.merge!(metadata['headers'] || {})
104
- if client_has_cache?(metadata, request)
105
- response.headers.merge!('Status' => '304 Not Modified')
106
- elsif use_x_sendfile
107
- response.headers.merge!('X-Sendfile' => "#{file_path}.data")
108
- else
109
- response.body = File.open("#{file_path}.data", "rb") {|f| f.read}
110
- end
111
- end
112
- response
113
- end
114
-
115
- def client_has_cache?(metadata, request)
116
- return false unless request
117
- request_time = Time.httpdate(request.env["HTTP_IF_MODIFIED_SINCE"]) rescue nil
118
- response_time = Time.httpdate(metadata['headers']['Last-Modified']) rescue nil
119
- return request_time && response_time && response_time <= request_time
120
- end
121
-
122
- # Writes a response to disk.
123
- def write_response(path, response)
124
- if response.cache_timeout
125
- if Time === response.cache_timeout
126
- expires = response.cache_timeout
127
- else
128
- expires = Time.now + response.cache_timeout
129
- end
130
- else
131
- expires = Time.now + self.expire_time
132
- end
133
- response.headers['Last-Modified'] ||= Time.now.httpdate
134
- metadata = {
135
- 'headers' => response.headers,
136
- 'expires' => expires
137
- }.to_yaml
138
- cache_page(metadata, response.body, path)
139
- end
140
-
141
- def page_cache_path(path)
142
- path = (path.empty? || path == "/") ? "/_site-root" : URI.unescape(path)
143
- root_dir = File.expand_path(page_cache_directory)
144
- cache_path = File.expand_path(File.join(root_dir, path), root_dir)
145
- cache_path if cache_path.index(root_dir) == 0
146
- end
147
-
148
- def expire_page(path)
149
- return unless perform_caching
150
-
151
- if path = page_cache_path(path)
152
- File.delete("#{path}.yml") if File.exists?("#{path}.yml")
153
- File.delete("#{path}.data") if File.exists?("#{path}.data")
154
- end
155
- end
156
-
157
- def cache_page(metadata, content, path)
158
- return unless perform_caching
159
-
160
- if path = page_cache_path(path)
161
- FileUtils.makedirs(File.dirname(path))
162
- #dont want yml without data
163
- File.open("#{path}.data", "wb+") { |f| f.write(content) }
164
- File.open("#{path}.yml", "wb+") { |f| f.write(metadata) }
165
- end
166
- end
167
- end
168
-
169
- NULL_CACHE = ResponseCache.new(:perform_caching => false)
170
- end
@@ -1,248 +0,0 @@
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 match("expires: 2007-02-08 17:37:09 Z")
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