serverside 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +16 -0
  2. data/Rakefile +16 -1
  3. data/lib/serverside/caching.rb +56 -0
  4. data/lib/serverside/request.rb +8 -4
  5. data/lib/serverside/static.rb +29 -51
  6. data/test/functional/static_server.rb +7 -0
  7. data/test/spec/caching_spec.rb +101 -0
  8. data/test/spec/connection_spec.rb +0 -2
  9. data/test/unit/request_test.rb +31 -1
  10. data/test/unit/static_test.rb +21 -25
  11. metadata +30 -75
  12. data/doc/rdoc/classes/Daemon/Base.html +0 -148
  13. data/doc/rdoc/classes/Daemon/Cluster/PidFile.html +0 -230
  14. data/doc/rdoc/classes/Daemon/Cluster.html +0 -308
  15. data/doc/rdoc/classes/Daemon/PidFile.html +0 -178
  16. data/doc/rdoc/classes/Daemon.html +0 -253
  17. data/doc/rdoc/classes/Object.html +0 -151
  18. data/doc/rdoc/classes/Proc.html +0 -151
  19. data/doc/rdoc/classes/ServerSide/Application.html +0 -172
  20. data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +0 -202
  21. data/doc/rdoc/classes/ServerSide/HTTP/Request.html +0 -768
  22. data/doc/rdoc/classes/ServerSide/HTTP/Server.html +0 -164
  23. data/doc/rdoc/classes/ServerSide/HTTP.html +0 -121
  24. data/doc/rdoc/classes/ServerSide/Router.html +0 -496
  25. data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +0 -204
  26. data/doc/rdoc/classes/ServerSide/StaticFiles.html +0 -282
  27. data/doc/rdoc/classes/ServerSide/Template.html +0 -200
  28. data/doc/rdoc/classes/ServerSide.html +0 -174
  29. data/doc/rdoc/classes/String.html +0 -212
  30. data/doc/rdoc/classes/Symbol.html +0 -158
  31. data/doc/rdoc/created.rid +0 -1
  32. data/doc/rdoc/files/CHANGELOG.html +0 -356
  33. data/doc/rdoc/files/COPYING.html +0 -129
  34. data/doc/rdoc/files/README.html +0 -199
  35. data/doc/rdoc/files/lib/serverside/application_rb.html +0 -109
  36. data/doc/rdoc/files/lib/serverside/cluster_rb.html +0 -101
  37. data/doc/rdoc/files/lib/serverside/connection_rb.html +0 -101
  38. data/doc/rdoc/files/lib/serverside/core_ext_rb.html +0 -107
  39. data/doc/rdoc/files/lib/serverside/daemon_rb.html +0 -108
  40. data/doc/rdoc/files/lib/serverside/request_rb.html +0 -108
  41. data/doc/rdoc/files/lib/serverside/routing_rb.html +0 -101
  42. data/doc/rdoc/files/lib/serverside/server_rb.html +0 -108
  43. data/doc/rdoc/files/lib/serverside/static_rb.html +0 -108
  44. data/doc/rdoc/files/lib/serverside/template_rb.html +0 -108
  45. data/doc/rdoc/files/lib/serverside_rb.html +0 -131
  46. data/doc/rdoc/fr_class_index.html +0 -45
  47. data/doc/rdoc/fr_file_index.html +0 -40
  48. data/doc/rdoc/fr_method_index.html +0 -79
  49. data/doc/rdoc/index.html +0 -24
  50. data/doc/rdoc/rdoc-style.css +0 -208
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ ==0.2.8
2
+
3
+ * Refactored ServerSide::Static to use HTTP::Caching code.
4
+
5
+ * Added spec coverage rake task.
6
+
7
+ * Added HTTP::Caching module.
8
+
9
+ * Added response_headers attribute to HTTP::Request.
10
+
11
+ * Refactored ServerSide::Static constants.
12
+
13
+ * Renamed ServerSide::StaticFiles to ServerSide::Static.
14
+
15
+ * Added static_server.rb script.
16
+
1
17
  ==0.2.7
2
18
 
3
19
  * Wrote spec for HTTP::Connection.
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ require 'fileutils'
7
7
  include FileUtils
8
8
 
9
9
  NAME = "serverside"
10
- VERS = "0.2.7"
10
+ VERS = "0.2.8"
11
11
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
12
12
  RDOC_OPTS = ['--quiet', '--title', "ServerSide Documentation",
13
13
  "--opname", "index.html",
@@ -109,6 +109,21 @@ task :aok do
109
109
  sh %{rake spec}
110
110
  end
111
111
 
112
+ require 'spec/rake/spectask'
113
+
114
+ desc "Run specs RCov"
115
+ Spec::Rake::SpecTask.new('specs_with_rcov') do |t|
116
+ t.spec_files = FileList['test/spec/*_spec.rb']
117
+ t.rcov = true
118
+ end
119
+
120
+ require 'spec/rake/rcov_verify'
121
+
122
+ RCov::VerifyTask.new(:rcov_verify => :rcov) do |t|
123
+ t.threshold = 95.4 # Make sure you have rcov 0.7 or higher!
124
+ t.index_html = 'doc/output/coverage/index.html'
125
+ end
126
+
112
127
  desc 'Update docs and upload to rubyforge.org'
113
128
  task :doc_rforge do
114
129
  sh %{rake doc}
@@ -0,0 +1,56 @@
1
+ require 'time'
2
+
3
+ module ServerSide
4
+ module HTTP
5
+ # This module implements HTTP cache negotiation with a client.
6
+ module Caching
7
+ ETAG = 'ETag'.freeze
8
+ CACHE_CONTROL = 'Cache-Control'.freeze
9
+ DEFAULT_MAX_AGE = (86400 * 30).freeze
10
+ IF_NONE_MATCH = 'If-None-Match'.freeze
11
+ ETAG_WILDCARD = '*'.freeze
12
+ IF_MODIFIED_SINCE = 'If-Modified-Since'.freeze
13
+ LAST_MODIFIED = "Last-Modified".freeze
14
+ NOT_MODIFIED_CLOSE = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nConnection: close\r\nLast-Modified: %s\r\nETag: \"%s\"\r\nCache-Control: max-age=%d\r\n\r\n".freeze
15
+ NOT_MODIFIED_PERSIST = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nLast-Modified: %s\r\nETag: \"%s\"\r\nCache-Control: max-age=%d\r\n\r\n".freeze
16
+ MAX_AGE = 'max-age=%d'.freeze
17
+
18
+ def valid_client_cache?(etag, http_stamp)
19
+ none_match = @headers[IF_NONE_MATCH]
20
+ modified_since = @headers[IF_MODIFIED_SINCE]
21
+ (none_match && (none_match =~ /\*|"#{etag}"/)) ||
22
+ (modified_since && (modified_since == http_stamp))
23
+ end
24
+
25
+ def validate_cache(etag, stamp, max_age = DEFAULT_MAX_AGE, &block)
26
+ http_stamp = stamp.httpdate
27
+ if valid_client_cache?(etag, http_stamp)
28
+ send_not_modified(etag, http_stamp, max_age)
29
+ else
30
+ @response_headers[ETAG] = "\"#{etag}\""
31
+ @response_headers[LAST_MODIFIED] = http_stamp
32
+ @response_headers[CACHE_CONTROL] = MAX_AGE % max_age
33
+ block.call
34
+ end
35
+ end
36
+
37
+ def send_not_modified(etag, http_time, max_age = DEFAULT_MAX_AGE)
38
+ @socket << ((@persistent ? NOT_MODIFIED_PERSIST : NOT_MODIFIED_CLOSE) %
39
+ [Time.now.httpdate, http_time, etag, max_age])
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ __END__
46
+
47
+ Reality::Controller.new(:node_history) do
48
+ default_flavor :html
49
+ valid_method :get
50
+
51
+ def html
52
+ validate_cache(etag, http_time) {|cache_headers|
53
+ render_template(cache_headers)
54
+ }
55
+ end
56
+ end
@@ -43,17 +43,18 @@ module ServerSide
43
43
  CONTENT_TYPE = "Content-Type".freeze
44
44
  CONTENT_TYPE_URL_ENCODED = 'application/x-www-form-urlencoded'.freeze
45
45
 
46
- include StaticFiles
46
+ include Static
47
47
 
48
48
  attr_reader :socket, :method, :path, :query, :version, :parameters,
49
49
  :headers, :persistent, :cookies, :response_cookies, :body,
50
- :content_length, :content_type
50
+ :content_length, :content_type, :response_headers
51
51
 
52
52
  # Initializes the request instance. Any descendants of HTTP::Request
53
53
  # which override the initialize method must receive socket as the
54
54
  # single argument, and copy it to @socket.
55
55
  def initialize(socket)
56
56
  @socket = socket
57
+ @response_headers = {}
57
58
  end
58
59
 
59
60
  # Processes the request by parsing it and then responding.
@@ -146,9 +147,12 @@ module ServerSide
146
147
  # Sends an HTTP response.
147
148
  def send_response(status, content_type, body = nil, content_length = nil,
148
149
  headers = nil)
149
- h = headers ?
150
- headers.inject('') {|m, kv| m << (HEADER % kv)} : ''
150
+ @response_headers.merge!(headers) if headers
151
+ h = @response_headers.inject('') {|m, kv| m << (HEADER % kv)}
151
152
 
153
+ # calculate content_length if needed. if we dont have the content_length,
154
+ # we consider the response as a streaming response, and so the connection
155
+ # will not be persistent.
152
156
  content_length = body.length if content_length.nil? && body
153
157
  @persistent = false if content_length.nil?
154
158
 
@@ -1,32 +1,23 @@
1
- require 'time'
1
+ require File.join(File.dirname(__FILE__), 'caching')
2
2
 
3
3
  module ServerSide
4
4
  # This module provides functionality for serving files and directory listings
5
5
  # over HTTP.
6
- module StaticFiles
7
- # Frozen constants to be used by the module.
8
- module Const
9
- ETag = 'ETag'.freeze
10
- ETagFormat = '%x:%x:%x'.inspect.freeze
11
- CacheControl = 'Cache-Control'.freeze
12
- MaxAge = "max-age=#{86400 * 30}".freeze
13
- IfNoneMatch = 'If-None-Match'.freeze
14
- IfModifiedSince = 'If-Modified-Since'.freeze
15
- LastModified = "Last-Modified".freeze
16
- NotModifiedClose = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nConnection: close\r\nLast-Modified: %s\r\nETag: %s\r\nCache-Control: #{MaxAge}\r\n\r\n".freeze
17
- NotModifiedPersist = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nLast-Modified: %s\r\nETag: %s\r\nCache-Control: #{MaxAge}\r\n\r\n".freeze
18
- TextPlain = 'text/plain'.freeze
19
- TextHTML = 'text/html'.freeze
20
- MaxCacheFileSize = 100000.freeze # 100KB for the moment
21
-
22
- DirListingStart = '<html><head><title>Directory Listing for %s</title></head><body><h2>Directory listing for %s:</h2>'.freeze
23
- DirListing = '<a href="%s">%s</a><br/>'.freeze
24
- DirListingStop = '</body></html>'.freeze
25
- FileNotFound = 'File not found.'.freeze
26
- RHTML = /\.rhtml$/.freeze
27
- end
6
+ module Static
7
+ include HTTP::Caching
8
+
9
+ ETAG_FORMAT = '%x:%x:%x'.inspect.freeze
10
+ TEXT_PLAIN = 'text/plain'.freeze
11
+ TEXT_HTML = 'text/html'.freeze
12
+ MAX_CACHE_FILE_SIZE = 100000.freeze # 100KB for the moment
28
13
 
29
- @@mime_types = Hash.new {|h, k| ServerSide::StaticFiles::Const::TextPlain}
14
+ DIR_LISTING_START = '<html><head><title>Directory Listing for %s</title></head><body><h2>Directory listing for %s:</h2>'.freeze
15
+ DIR_LISTING = '<a href="%s">%s</a><br/>'.freeze
16
+ DIR_LISTING_STOP = '</body></html>'.freeze
17
+ FILE_NOT_FOUND = 'File not found.'.freeze
18
+ RHTML = /\.rhtml$/.freeze
19
+
20
+ @@mime_types = Hash.new {|h, k| TEXT_PLAIN}
30
21
  @@mime_types.merge!({
31
22
  '.html'.freeze => 'text/html'.freeze,
32
23
  '.css'.freeze => 'text/css'.freeze,
@@ -46,48 +37,35 @@ module ServerSide
46
37
  # rendered.
47
38
  def serve_file(fn)
48
39
  stat = File.stat(fn)
49
- etag = (Const::ETagFormat % [stat.mtime.to_i, stat.size, stat.ino]).freeze
50
- date = stat.mtime.httpdate
51
-
52
- etag_match = @headers[Const::IfNoneMatch]
53
- last_date = @headers[Const::IfModifiedSince]
54
-
55
- modified = (!etag_match && !last_date) ||
56
- (etag_match && (etag != etag_match)) || (last_date && (last_date != date))
57
-
58
- if modified
40
+ etag = (ETAG_FORMAT % [stat.mtime.to_i, stat.size, stat.ino]).freeze
41
+ validate_cache(etag, stat.mtime) do
59
42
  if @@static_files[fn] && (@@static_files[fn][0] == etag)
60
43
  content = @@static_files[fn][1]
61
44
  else
62
45
  content = IO.read(fn).freeze
63
46
  @@static_files[fn] = [etag.freeze, content]
64
47
  end
65
-
66
- send_response(200, @@mime_types[File.extname(fn)], content, stat.size, {
67
- Const::ETag => etag,
68
- Const::LastModified => date,
69
- Const::CacheControl => Const::MaxAge
70
- })
71
- else
72
- @socket << ((@persistent ? Const::NotModifiedPersist :
73
- Const::NotModifiedClose) % [Time.now.httpdate, date, etag, stat.size])
48
+ send_response(200, @@mime_types[File.extname(fn)], content, stat.size)
74
49
  end
75
50
  rescue => e
76
- send_response(404, Const::TextPlain, 'Error reading file.')
51
+ puts e.message
52
+ send_response(404, TEXT_PLAIN, 'Error reading file.')
77
53
  end
78
54
 
79
55
  # Serves a directory listing over HTTP in the form of an HTML page.
80
56
  def serve_dir(dir)
81
- html = (Const::DirListingStart % [@path, @path]) +
82
- Dir.entries(dir).inject('') {|m, fn|
83
- (fn == '.') ? m : m << Const::DirListing % [@path/fn, fn]
84
- } + Const::DirListingStop
57
+ entries = Dir.entries(dir)
58
+ entries.reject! {|fn| fn =~ /^\./}
59
+ entries.unshift('..') if dir != './'
60
+ html = (DIR_LISTING_START % [@path, @path]) +
61
+ entries.inject('') {|m, fn| m << DIR_LISTING % [@path/fn, fn]} +
62
+ DIR_LISTING_STOP
85
63
  send_response(200, 'text/html', html)
86
64
  end
87
65
 
88
66
  def serve_template(fn, b = nil)
89
- if (fn =~ Const::RHTML) || (File.file?(fn = fn + '.rhtml'))
90
- send_response(200, Const::TextHTML, Template.render(fn, b || binding))
67
+ if (fn =~ RHTML) || (File.file?(fn = fn + '.rhtml'))
68
+ send_response(200, TEXT_HTML, Template.render(fn, b || binding))
91
69
  end
92
70
  end
93
71
 
@@ -100,7 +78,7 @@ module ServerSide
100
78
  elsif File.directory?(path)
101
79
  serve_dir(path)
102
80
  else
103
- send_response(404, 'text', Const::FileNotFound)
81
+ send_response(404, 'text', FILE_NOT_FOUND)
104
82
  end
105
83
  rescue => e
106
84
  send_response(500, 'text', e.message)
@@ -0,0 +1,7 @@
1
+ require 'lib/serverside'
2
+
3
+ trap('INT') {exit}
4
+ include ServerSide
5
+ puts "Serving on port 8000..."
6
+ Router.route_default {serve_static('.'/@path)}
7
+ HTTP::Server.new('0.0.0.0', 8000, Router)
@@ -0,0 +1,101 @@
1
+ require File.join(File.dirname(__FILE__), '../../lib/serverside')
2
+ require 'stringio'
3
+ include ServerSide::HTTP
4
+
5
+ class DummyRequest < Request
6
+ attr_accessor :socket
7
+ include Caching
8
+
9
+ def initialize
10
+ super(StringIO.new)
11
+ @headers = {}
12
+ end
13
+ end
14
+
15
+ context "Caching::validate_cache" do
16
+ specify "should set etag and last-modified response headers" do
17
+ r = DummyRequest.new
18
+ t = Time.now
19
+ r.validate_cache('aaaa', t) {
20
+ r.send_response(200, 'text/plain', 'hi', nil, {'F' => 1})
21
+ }
22
+ r.socket.rewind
23
+ resp = r.socket.read
24
+
25
+ resp.should_match /ETag:\s"aaaa"\r\n/
26
+ resp.should_match /Last-Modified:\s#{t.httpdate}\r\n/
27
+ resp.should_match /F:\s1\r\n/
28
+ end
29
+
30
+ specify "should send not modified response if client includes a suitable validator" do
31
+ r = DummyRequest.new
32
+ t = Time.now
33
+ r.headers['If-None-Match'] = '"bbbb"'
34
+ r.validate_cache('bbbb', t) {raise "This should not be called"}
35
+ r.socket.rewind
36
+ resp = r.socket.read
37
+
38
+ resp.should_match /^HTTP\/1.1\s304 Not Modified\r\n/
39
+ resp.should_match /ETag:\s"bbbb"\r\n/
40
+ resp.should_match /Last-Modified:\s#{t.httpdate}\r\n/
41
+
42
+ r = DummyRequest.new
43
+ t = Time.now
44
+ r.headers['If-Modified-Since'] = t.httpdate
45
+ r.validate_cache('cccc', t) {raise "This should not be called"}
46
+ r.socket.rewind
47
+ resp = r.socket.read
48
+
49
+ resp.should_match /^HTTP\/1.1\s304 Not Modified\r\n/
50
+ resp.should_match /ETag:\s"cccc"\r\n/
51
+ resp.should_match /Last-Modified:\s#{t.httpdate}\r\n/
52
+ end
53
+ end
54
+
55
+ context "Caching::send_not_modified" do
56
+ specify "should send back a valid 304 response with headers" do
57
+ r = DummyRequest.new
58
+ t = Time.now
59
+ r.send_not_modified('dddd', t.httpdate, 240)
60
+ r.socket.rewind
61
+ resp = r.socket.read
62
+
63
+ resp.should_match /^HTTP\/1.1\s304 Not Modified\r\n/
64
+ resp.should_match /ETag:\s"dddd"\r\n/
65
+ resp.should_match /Last-Modified:\s#{t.httpdate}\r\n/
66
+ resp.should_match /Cache-Control:\smax-age=240\r\n/
67
+ end
68
+ end
69
+
70
+ context "Caching::valid_client_cache?" do
71
+ specify "should check if-none-match validator for etag" do
72
+ r = DummyRequest.new
73
+ t = Time.now
74
+ r.valid_client_cache?('eeee', t).should_be_nil
75
+ r.headers['If-None-Match'] = '"abc"'
76
+ r.valid_client_cache?('eeee', t).should_be_nil
77
+ r.headers['If-None-Match'] = '"eeee"'
78
+ r.valid_client_cache?('eeee', t).should_not_be_nil
79
+ r.headers['If-None-Match'] = '"aaa", "bbb", "ccc"'
80
+ r.valid_client_cache?('eeee', t).should_be_nil
81
+ r.headers['If-None-Match'] = '"aaa", "eeee", "ccc"'
82
+ r.valid_client_cache?('eeee', t).should_not_be_nil
83
+ end
84
+
85
+ specify "should check if-none-match validator for wildcard" do
86
+ r = DummyRequest.new
87
+ r.headers['If-None-Match'] = '*'
88
+ r.valid_client_cache?('eeee', nil).should_not_be_nil
89
+ r.headers['If-None-Match'] = '*, "aaaa"'
90
+ r.valid_client_cache?('eeee', nil).should_not_be_nil
91
+ end
92
+
93
+ specify "should check if-modified-since validator for etag" do
94
+ r = DummyRequest.new
95
+ t = Time.now
96
+ r.headers['If-Modified-Since'] = (t-1).httpdate
97
+ r.valid_client_cache?('eeee', t.httpdate).should_be false
98
+ r.headers['If-Modified-Since'] = t.httpdate
99
+ r.valid_client_cache?('eeee', t.httpdate).should_not_be false
100
+ end
101
+ end
@@ -1,7 +1,5 @@
1
1
  require File.join(File.dirname(__FILE__), '../../lib/serverside')
2
2
 
3
- # String extensions
4
-
5
3
  class ServerSide::HTTP::Connection
6
4
  attr_reader :socket, :request_class, :thread
7
5
  end
@@ -42,7 +42,7 @@ class RequestTest < Test::Unit::TestCase
42
42
  end
43
43
 
44
44
  class ServerSide::HTTP::Request
45
- attr_writer :socket, :persistent
45
+ attr_writer :socket, :persistent, :response_headers
46
46
  end
47
47
 
48
48
  def test_process_result
@@ -149,6 +149,7 @@ class RequestTest < Test::Unit::TestCase
149
149
  def test_send_response
150
150
  r = ServerSide::HTTP::Request.new(nil)
151
151
  # simple case
152
+ r.response_headers = {}
152
153
  r.socket = StringIO.new
153
154
  r.persistent = true
154
155
  r.send_response(200, 'text', 'Hello there!')
@@ -157,6 +158,7 @@ class RequestTest < Test::Unit::TestCase
157
158
  r.socket.read
158
159
 
159
160
  # include content-length
161
+ r.response_headers = {}
160
162
  r.socket = StringIO.new
161
163
  r.persistent = true
162
164
  r.send_response(404, 'text/html', '<h1>404!</h1>', 10) # incorrect length
@@ -165,6 +167,7 @@ class RequestTest < Test::Unit::TestCase
165
167
  r.socket.read
166
168
 
167
169
  # headers
170
+ r.response_headers = {}
168
171
  r.socket = StringIO.new
169
172
  r.persistent = true
170
173
  r.send_response(404, 'text/html', '<h1>404!</h1>', nil, {'ETag' => 'xxyyzz'})
@@ -173,6 +176,7 @@ class RequestTest < Test::Unit::TestCase
173
176
  r.socket.read
174
177
 
175
178
  # no body
179
+ r.response_headers = {}
176
180
  r.socket = StringIO.new
177
181
  r.persistent = true
178
182
  r.send_response(404, 'text/html', nil, nil, {'Set-Cookie' => 'abc=123'})
@@ -182,6 +186,7 @@ class RequestTest < Test::Unit::TestCase
182
186
  assert_equal false, r.persistent
183
187
 
184
188
  # not persistent
189
+ r.response_headers = {}
185
190
  r.socket = StringIO.new
186
191
  r.persistent = false
187
192
  r.send_response(200, 'text', 'Hello there!')
@@ -190,6 +195,7 @@ class RequestTest < Test::Unit::TestCase
190
195
  r.socket.read
191
196
 
192
197
  # socket error
198
+ r.response_headers = {}
193
199
  r.socket = nil
194
200
  r.persistent = true
195
201
  assert_nothing_raised {r.send_response(200, 'text', 'Hello there!')}
@@ -219,6 +225,7 @@ class RequestTest < Test::Unit::TestCase
219
225
  r.socket.rewind
220
226
  assert_equal "HTTP/1.1 302\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n", r.socket.read
221
227
 
228
+ r.response_headers = {}
222
229
  r.socket = StringIO.new
223
230
  r.redirect('http://www.google.com/search?q=meaning%20of%20life', true)
224
231
  r.socket.rewind
@@ -245,4 +252,27 @@ class RequestTest < Test::Unit::TestCase
245
252
  r.socket.rewind
246
253
  assert_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\nContent-Length: 3\r\n\r\nHi!", r.socket.read
247
254
  end
255
+
256
+ def test_response_headers
257
+ r = ServerSide::HTTP::Request.new(nil)
258
+ r.socket = StringIO.new
259
+ assert_equal({}, r.response_headers)
260
+ r.response_headers = {'A' => '1', 'B' => 2}
261
+ r.send_response(200, 'text', 'Hi!')
262
+ r.socket.rewind
263
+ resp = r.socket.read
264
+ assert_not_nil resp.match("A: 1\r\n")
265
+ assert_not_nil resp.match("B: 2\r\n")
266
+
267
+ r.response_headers = {}
268
+ r.socket = StringIO.new
269
+ assert_equal({}, r.response_headers)
270
+ r.response_headers = {'D' => '1', 'E' => 2}
271
+ r.send_response(200, 'text', 'Hi!', nil, {'F' => 3})
272
+ r.socket.rewind
273
+ resp = r.socket.read
274
+ assert_not_nil resp.match("D: 1\r\n")
275
+ assert_not_nil resp.match("E: 2\r\n")
276
+ assert_not_nil resp.match("F: 3\r\n")
277
+ end
248
278
  end
@@ -15,6 +15,7 @@ class StaticTest < Test::Unit::TestCase
15
15
  attr_accessor :path, :socket, :headers
16
16
 
17
17
  def initialize
18
+ super(nil)
18
19
  @headers = {}
19
20
  end
20
21
  end
@@ -29,7 +30,7 @@ class StaticTest < Test::Unit::TestCase
29
30
  cache = Dummy.static_files[__FILE__]
30
31
  assert_not_nil cache
31
32
  stat = File.stat(__FILE__)
32
- etag = (ServerSide::StaticFiles::Const::ETagFormat %
33
+ etag = (ServerSide::Static::ETAG_FORMAT %
33
34
  [stat.mtime.to_i, stat.size, stat.ino])
34
35
  assert_equal etag, cache[0]
35
36
  assert_equal IO.read(__FILE__), cache[1]
@@ -58,11 +59,10 @@ class StaticTest < Test::Unit::TestCase
58
59
  assert_equal '200', /HTTP\/1.1\s(.*)\r\n/.match(resp)[1]
59
60
  fc = IO.read(__FILE__)
60
61
  stat = File.stat(__FILE__)
61
- etag = (ServerSide::StaticFiles::Const::ETagFormat %
62
+ etag = (ServerSide::Static::ETAG_FORMAT %
62
63
  [stat.mtime.to_i, stat.size, stat.ino])
63
- assert_equal etag, /ETag:\s(.*)\r\n/.match(resp)[1]
64
- assert_equal ServerSide::StaticFiles::Const::MaxAge,
65
- /Cache-Control:\s(.*)\r\n/.match(resp)[1]
64
+ assert_not_nil resp =~ /ETag:\s"#{etag}"\r\n/
65
+ assert_not_nil resp =~ /Cache-Control:\smax-age=#{ServerSide::HTTP::Caching::DEFAULT_MAX_AGE}\r\n/
66
66
  assert_equal stat.size.to_s,
67
67
  /Content-Length:\s(.*)\r\n/.match(resp)[1]
68
68
  end
@@ -79,16 +79,15 @@ class StaticTest < Test::Unit::TestCase
79
79
  assert_equal '200', /HTTP\/1.1\s(.*)\r\n/.match(resp)[1]
80
80
  fc = IO.read(__FILE__)
81
81
  stat = File.stat(__FILE__)
82
- etag = (ServerSide::StaticFiles::Const::ETagFormat %
82
+ etag = (ServerSide::Static::ETAG_FORMAT %
83
83
  [stat.mtime.to_i, stat.size, stat.ino])
84
- assert_equal etag, /ETag:\s(.*)\r\n/.match(resp)[1]
85
- assert_equal ServerSide::StaticFiles::Const::MaxAge,
86
- /Cache-Control:\s(.*)\r\n/.match(resp)[1]
84
+ assert_not_nil resp =~ /ETag:\s"#{etag}"\r\n/
85
+ assert_not_nil resp =~ /Cache-Control:\smax-age=#{ServerSide::HTTP::Caching::DEFAULT_MAX_AGE}\r\n/
87
86
  assert_equal stat.size.to_s,
88
87
  /Content-Length:\s(.*)\r\n/.match(resp)[1]
89
88
 
90
89
  c.socket = StringIO.new
91
- c.headers[ServerSide::StaticFiles::Const::IfNoneMatch] = etag
90
+ c.headers[ServerSide::Static::IF_NONE_MATCH] = "\"#{etag}\""
92
91
  c.serve_file(__FILE__)
93
92
  c.socket.rewind
94
93
  resp = c.socket.read
@@ -97,15 +96,14 @@ class StaticTest < Test::Unit::TestCase
97
96
  assert_equal '304 Not Modified', /HTTP\/1.1\s(.*)\r\n/.match(resp)[1]
98
97
  fc = IO.read(__FILE__)
99
98
  stat = File.stat(__FILE__)
100
- etag = (ServerSide::StaticFiles::Const::ETagFormat %
99
+ etag = (ServerSide::Static::ETAG_FORMAT %
101
100
  [stat.mtime.to_i, stat.size, stat.ino])
102
- assert_equal etag, /ETag:\s(.*)\r\n/.match(resp)[1]
103
- assert_equal ServerSide::StaticFiles::Const::MaxAge,
104
- /Cache-Control:\s(.*)\r\n/.match(resp)[1]
101
+ assert_not_nil resp =~ /ETag:\s"#{etag}"\r\n/
102
+ assert_not_nil resp =~ /Cache-Control:\smax-age=#{ServerSide::HTTP::Caching::DEFAULT_MAX_AGE}\r\n/
105
103
 
106
104
  FileUtils.touch(__FILE__)
107
105
  c.socket = StringIO.new
108
- c.headers[ServerSide::StaticFiles::Const::IfNoneMatch] = etag
106
+ c.headers[ServerSide::Static::IF_NONE_MATCH] = etag
109
107
  c.serve_file(__FILE__)
110
108
  c.socket.rewind
111
109
  resp = c.socket.read
@@ -114,11 +112,10 @@ class StaticTest < Test::Unit::TestCase
114
112
  assert_equal '200', /HTTP\/1.1\s(.*)\r\n/.match(resp)[1]
115
113
  fc = IO.read(__FILE__)
116
114
  stat = File.stat(__FILE__)
117
- etag = (ServerSide::StaticFiles::Const::ETagFormat %
115
+ etag = (ServerSide::Static::ETAG_FORMAT %
118
116
  [stat.mtime.to_i, stat.size, stat.ino])
119
- assert_equal etag, /ETag:\s(.*)\r\n/.match(resp)[1]
120
- assert_equal ServerSide::StaticFiles::Const::MaxAge,
121
- /Cache-Control:\s(.*)\r\n/.match(resp)[1]
117
+ assert_not_nil resp =~ /ETag:\s"#{etag}"\r\n/
118
+ assert_not_nil resp =~ /Cache-Control:\smax-age=#{ServerSide::HTTP::Caching::DEFAULT_MAX_AGE}\r\n/
122
119
  assert_equal stat.size.to_s,
123
120
  /Content-Length:\s(.*)\r\n/.match(resp)[1]
124
121
  end
@@ -135,7 +132,7 @@ class StaticTest < Test::Unit::TestCase
135
132
  resp = c.socket.read
136
133
 
137
134
  Dir.entries(dir).each do |fn|
138
- next if fn == '.'
135
+ next if fn =~ /^\./
139
136
  assert_not_nil resp =~ /<a href="#{dir/fn}">(#{fn})<\/a>/
140
137
  end
141
138
  end
@@ -151,7 +148,7 @@ class StaticTest < Test::Unit::TestCase
151
148
  resp = c.socket.read
152
149
 
153
150
  Dir.entries(dir).each do |fn|
154
- next if fn == '.'
151
+ next if fn =~ /^\./
155
152
  assert_not_nil resp =~ /<a href="#{dir/fn}">(#{fn})<\/a>/
156
153
  end
157
154
 
@@ -164,11 +161,10 @@ class StaticTest < Test::Unit::TestCase
164
161
  assert_equal '200', /HTTP\/1.1\s(.*)\r\n/.match(resp)[1]
165
162
  fc = IO.read(__FILE__)
166
163
  stat = File.stat(__FILE__)
167
- etag = (ServerSide::StaticFiles::Const::ETagFormat %
164
+ etag = (ServerSide::Static::ETAG_FORMAT %
168
165
  [stat.mtime.to_i, stat.size, stat.ino])
169
- assert_equal etag, /ETag:\s(.*)\r\n/.match(resp)[1]
170
- assert_equal ServerSide::StaticFiles::Const::MaxAge,
171
- /Cache-Control:\s(.*)\r\n/.match(resp)[1]
166
+ assert_not_nil resp =~ /ETag:\s"#{etag}"\r\n/
167
+ assert_not_nil resp =~ /Cache-Control:\smax-age=#{ServerSide::HTTP::Caching::DEFAULT_MAX_AGE}\r\n/
172
168
  assert_equal stat.size.to_s,
173
169
  /Content-Length:\s(.*)\r\n/.match(resp)[1]
174
170
  end