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