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.
Files changed (57) hide show
  1. data/{History.txt → CHANGELOG.rdoc} +9 -0
  2. data/{License.txt → LICENSE} +1 -1
  3. data/{Quickstart.textile → QUICKSTART.rdoc} +61 -80
  4. data/{README.txt → README.rdoc} +10 -13
  5. data/Rakefile +11 -13
  6. data/VERSION +1 -0
  7. data/bin/serve +6 -0
  8. data/lib/serve.rb +2 -8
  9. data/lib/serve/application.rb +13 -6
  10. data/lib/serve/file_resolver.rb +48 -0
  11. data/lib/serve/handlers/dynamic_handler.rb +31 -21
  12. data/lib/serve/handlers/file_type_handler.rb +23 -32
  13. data/lib/serve/handlers/markdown_handler.rb +1 -1
  14. data/lib/serve/handlers/redirect_handler.rb +6 -5
  15. data/lib/serve/handlers/sass_handler.rb +4 -3
  16. data/lib/serve/rack.rb +57 -0
  17. data/lib/serve/rails.rb +4 -0
  18. data/lib/serve/rails/configuration.rb +69 -0
  19. data/lib/serve/rails/mount.rb +38 -0
  20. data/lib/serve/rails/routing.rb +25 -0
  21. data/lib/serve/rails/serve_controller.rb +52 -0
  22. data/lib/serve/response_cache.rb +170 -0
  23. data/lib/serve/version.rb +4 -9
  24. data/lib/serve/webrick/extensions.rb +80 -15
  25. data/lib/serve/webrick/server.rb +17 -0
  26. data/lib/serve/webrick/servlet.rb +19 -0
  27. data/rails/init.rb +6 -0
  28. data/spec/{serve_application_spec.rb → application_spec.rb} +5 -4
  29. data/spec/response_cache_spec.rb +248 -0
  30. data/spec/serve_spec.rb +1 -0
  31. data/spec/spec_helper.rb +10 -9
  32. metadata +51 -69
  33. data.tar.gz.sig +0 -0
  34. data/Manifest.txt +0 -44
  35. data/config/hoe.rb +0 -70
  36. data/config/requirements.rb +0 -17
  37. data/log/debug.log +0 -0
  38. data/script/destroy +0 -14
  39. data/script/generate +0 -14
  40. data/script/txt2html +0 -74
  41. data/setup.rb +0 -1585
  42. data/tasks/deployment.rake +0 -27
  43. data/tasks/environment.rake +0 -7
  44. data/tasks/rspec.rake +0 -21
  45. data/tasks/undefine.rake +0 -5
  46. data/tasks/website.rake +0 -17
  47. data/test_project/_layout.haml +0 -12
  48. data/test_project/erb/_footer.html.erb +0 -2
  49. data/test_project/erb/_layout.html.erb +0 -28
  50. data/test_project/erb/index.html.erb +0 -10
  51. data/test_project/haml/_footer.haml +0 -2
  52. data/test_project/haml/_layout.haml +0 -20
  53. data/test_project/haml/index.haml +0 -9
  54. data/test_project/test.haml +0 -3
  55. data/test_project/test.html.erb +0 -3
  56. data/test_project/view_helpers.rb +0 -5
  57. metadata.gz.sig +0 -0
@@ -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
- VERSION::STRING
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
- if result = search_file_without_auto_appending(req, res, basename)
15
- return result
16
- end
17
- extensions = @config[:AppendExtensions]
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
@@ -0,0 +1,6 @@
1
+ require 'serve/rails'
2
+
3
+ ActionController::Routing::RouteSet::Mapper.send :include, Serve::Rails::Routing::MapperExtensions
4
+ ActionController::AbstractResponse.module_eval do
5
+ attr_accessor :cache_timeout
6
+ end
@@ -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 == "2000"
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=>"2000")
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=>"2000")
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=>"2000")
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