serverside 0.2.7 → 0.2.8

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 (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