serve 0.9.10 → 0.10.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/{History.txt → CHANGELOG.rdoc} +9 -0
- data/{License.txt → LICENSE} +1 -1
- data/{Quickstart.textile → QUICKSTART.rdoc} +61 -80
- data/{README.txt → README.rdoc} +10 -13
- data/Rakefile +11 -13
- data/VERSION +1 -0
- data/bin/serve +6 -0
- data/lib/serve.rb +2 -8
- data/lib/serve/application.rb +13 -6
- data/lib/serve/file_resolver.rb +48 -0
- data/lib/serve/handlers/dynamic_handler.rb +31 -21
- data/lib/serve/handlers/file_type_handler.rb +23 -32
- data/lib/serve/handlers/markdown_handler.rb +1 -1
- data/lib/serve/handlers/redirect_handler.rb +6 -5
- data/lib/serve/handlers/sass_handler.rb +4 -3
- data/lib/serve/rack.rb +57 -0
- data/lib/serve/rails.rb +4 -0
- data/lib/serve/rails/configuration.rb +69 -0
- data/lib/serve/rails/mount.rb +38 -0
- data/lib/serve/rails/routing.rb +25 -0
- data/lib/serve/rails/serve_controller.rb +52 -0
- data/lib/serve/response_cache.rb +170 -0
- data/lib/serve/version.rb +4 -9
- data/lib/serve/webrick/extensions.rb +80 -15
- data/lib/serve/webrick/server.rb +17 -0
- data/lib/serve/webrick/servlet.rb +19 -0
- data/rails/init.rb +6 -0
- data/spec/{serve_application_spec.rb → application_spec.rb} +5 -4
- data/spec/response_cache_spec.rb +248 -0
- data/spec/serve_spec.rb +1 -0
- data/spec/spec_helper.rb +10 -9
- metadata +51 -69
- data.tar.gz.sig +0 -0
- data/Manifest.txt +0 -44
- data/config/hoe.rb +0 -70
- data/config/requirements.rb +0 -17
- data/log/debug.log +0 -0
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/script/txt2html +0 -74
- data/setup.rb +0 -1585
- data/tasks/deployment.rake +0 -27
- data/tasks/environment.rake +0 -7
- data/tasks/rspec.rake +0 -21
- data/tasks/undefine.rake +0 -5
- data/tasks/website.rake +0 -17
- data/test_project/_layout.haml +0 -12
- data/test_project/erb/_footer.html.erb +0 -2
- data/test_project/erb/_layout.html.erb +0 -28
- data/test_project/erb/index.html.erb +0 -10
- data/test_project/haml/_footer.haml +0 -2
- data/test_project/haml/_layout.haml +0 -20
- data/test_project/haml/index.haml +0 -9
- data/test_project/test.haml +0 -3
- data/test_project/test.html.erb +0 -3
- data/test_project/view_helpers.rb +0 -5
- metadata.gz.sig +0 -0
data/lib/serve/version.rb
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
module Serve #:nodoc:
|
2
|
-
module VERSION #:nodoc:
|
3
|
-
MAJOR = 0
|
4
|
-
MINOR = 9
|
5
|
-
TINY = 10
|
6
|
-
|
7
|
-
STRING = [MAJOR, MINOR, TINY].join('.')
|
8
|
-
end
|
9
|
-
|
10
2
|
def self.version
|
11
|
-
|
3
|
+
@version ||= begin
|
4
|
+
filename = File.join(File.dirname(__FILE__), '..', '..', 'VERSION')
|
5
|
+
IO.read(filename).strip
|
6
|
+
end
|
12
7
|
end
|
13
8
|
end
|
@@ -1,7 +1,10 @@
|
|
1
|
+
# Portions from Rails, Copyright (c) 2004-2008 David Heinemeier Hansson
|
2
|
+
require 'active_support/memoizable'
|
3
|
+
|
1
4
|
module Serve #:nodoc:
|
2
5
|
module WEBrick #:nodoc:
|
6
|
+
|
3
7
|
module FileHandlerExtensions #:nodoc:
|
4
|
-
|
5
8
|
def self.included(base)
|
6
9
|
base.extend(self)
|
7
10
|
base.class_eval do
|
@@ -11,22 +14,84 @@ module Serve #:nodoc:
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def search_file_with_auto_appending(req, res, basename)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
basename = $1 if basename =~ %r{^(.*?)/$}
|
19
|
-
if extensions
|
20
|
-
extensions.each do |ext|
|
21
|
-
if result = search_file_without_auto_appending(req, res, "#{basename}.#{ext}")
|
22
|
-
return result
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
return nil
|
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)
|
27
21
|
end
|
28
|
-
|
29
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)
|
30
95
|
end
|
31
96
|
end
|
32
97
|
|
@@ -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
|
data/rails/init.rb
ADDED
@@ -1,4 +1,5 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
require 'serve/application'
|
2
3
|
|
3
4
|
describe Serve::Application do
|
4
5
|
|
@@ -21,19 +22,19 @@ describe Serve::Application do
|
|
21
22
|
end
|
22
23
|
|
23
24
|
it "should parse with only the port" do
|
24
|
-
@app.parse(["2000"])[:port].should ==
|
25
|
+
@app.parse(["2000"])[:port].should == 2000
|
25
26
|
end
|
26
27
|
|
27
28
|
it "should parse with the port and address" do
|
28
29
|
@app.parse(["1.1.1.1", "2000"]).should ==
|
29
|
-
@defopts.update(:address => "1.1.1.1", :port=>
|
30
|
+
@defopts.update(:address => "1.1.1.1", :port=>2000)
|
30
31
|
@app.parse(["1.1.1.1:2000"]).should ==
|
31
|
-
@defopts.update(:address => "1.1.1.1", :port=>
|
32
|
+
@defopts.update(:address => "1.1.1.1", :port=>2000)
|
32
33
|
end
|
33
34
|
|
34
35
|
it "should parse with the port, address, and protocol" do
|
35
36
|
@app.parse(["http://1.1.1.1:2000"]).should ==
|
36
|
-
@defopts.update(:address => "1.1.1.1", :port=>
|
37
|
+
@defopts.update(:address => "1.1.1.1", :port=>2000)
|
37
38
|
end
|
38
39
|
|
39
40
|
it "should parse help" do
|
@@ -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
|