serve 1.0.0 → 1.1.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.
@@ -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