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.
- data/CHANGELOG +16 -0
- data/Rakefile +16 -1
- data/lib/serverside/caching.rb +56 -0
- data/lib/serverside/request.rb +8 -4
- data/lib/serverside/static.rb +29 -51
- data/test/functional/static_server.rb +7 -0
- data/test/spec/caching_spec.rb +101 -0
- data/test/spec/connection_spec.rb +0 -2
- data/test/unit/request_test.rb +31 -1
- data/test/unit/static_test.rb +21 -25
- metadata +30 -75
- data/doc/rdoc/classes/Daemon/Base.html +0 -148
- data/doc/rdoc/classes/Daemon/Cluster/PidFile.html +0 -230
- data/doc/rdoc/classes/Daemon/Cluster.html +0 -308
- data/doc/rdoc/classes/Daemon/PidFile.html +0 -178
- data/doc/rdoc/classes/Daemon.html +0 -253
- data/doc/rdoc/classes/Object.html +0 -151
- data/doc/rdoc/classes/Proc.html +0 -151
- data/doc/rdoc/classes/ServerSide/Application.html +0 -172
- data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +0 -202
- data/doc/rdoc/classes/ServerSide/HTTP/Request.html +0 -768
- data/doc/rdoc/classes/ServerSide/HTTP/Server.html +0 -164
- data/doc/rdoc/classes/ServerSide/HTTP.html +0 -121
- data/doc/rdoc/classes/ServerSide/Router.html +0 -496
- data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +0 -204
- data/doc/rdoc/classes/ServerSide/StaticFiles.html +0 -282
- data/doc/rdoc/classes/ServerSide/Template.html +0 -200
- data/doc/rdoc/classes/ServerSide.html +0 -174
- data/doc/rdoc/classes/String.html +0 -212
- data/doc/rdoc/classes/Symbol.html +0 -158
- data/doc/rdoc/created.rid +0 -1
- data/doc/rdoc/files/CHANGELOG.html +0 -356
- data/doc/rdoc/files/COPYING.html +0 -129
- data/doc/rdoc/files/README.html +0 -199
- data/doc/rdoc/files/lib/serverside/application_rb.html +0 -109
- data/doc/rdoc/files/lib/serverside/cluster_rb.html +0 -101
- data/doc/rdoc/files/lib/serverside/connection_rb.html +0 -101
- data/doc/rdoc/files/lib/serverside/core_ext_rb.html +0 -107
- data/doc/rdoc/files/lib/serverside/daemon_rb.html +0 -108
- data/doc/rdoc/files/lib/serverside/request_rb.html +0 -108
- data/doc/rdoc/files/lib/serverside/routing_rb.html +0 -101
- data/doc/rdoc/files/lib/serverside/server_rb.html +0 -108
- data/doc/rdoc/files/lib/serverside/static_rb.html +0 -108
- data/doc/rdoc/files/lib/serverside/template_rb.html +0 -108
- data/doc/rdoc/files/lib/serverside_rb.html +0 -131
- data/doc/rdoc/fr_class_index.html +0 -45
- data/doc/rdoc/fr_file_index.html +0 -40
- data/doc/rdoc/fr_method_index.html +0 -79
- data/doc/rdoc/index.html +0 -24
- 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.
|
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
|
data/lib/serverside/request.rb
CHANGED
@@ -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
|
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
|
-
|
150
|
-
|
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
|
|
data/lib/serverside/static.rb
CHANGED
@@ -1,32 +1,23 @@
|
|
1
|
-
require '
|
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
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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 = (
|
50
|
-
|
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
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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 =~
|
90
|
-
send_response(200,
|
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',
|
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,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
|
data/test/unit/request_test.rb
CHANGED
@@ -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
|
data/test/unit/static_test.rb
CHANGED
@@ -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::
|
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::
|
62
|
+
etag = (ServerSide::Static::ETAG_FORMAT %
|
62
63
|
[stat.mtime.to_i, stat.size, stat.ino])
|
63
|
-
|
64
|
-
|
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::
|
82
|
+
etag = (ServerSide::Static::ETAG_FORMAT %
|
83
83
|
[stat.mtime.to_i, stat.size, stat.ino])
|
84
|
-
|
85
|
-
|
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::
|
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::
|
99
|
+
etag = (ServerSide::Static::ETAG_FORMAT %
|
101
100
|
[stat.mtime.to_i, stat.size, stat.ino])
|
102
|
-
|
103
|
-
|
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::
|
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::
|
115
|
+
etag = (ServerSide::Static::ETAG_FORMAT %
|
118
116
|
[stat.mtime.to_i, stat.size, stat.ino])
|
119
|
-
|
120
|
-
|
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::
|
164
|
+
etag = (ServerSide::Static::ETAG_FORMAT %
|
168
165
|
[stat.mtime.to_i, stat.size, stat.ino])
|
169
|
-
|
170
|
-
|
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
|