serve 0.9.10 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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