serverside 0.2.0 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +26 -0
- data/README +39 -22
- data/Rakefile +18 -2
- data/bin/serverside +21 -5
- data/doc/rdoc/classes/ServerSide/Application.html +12 -19
- data/doc/rdoc/classes/ServerSide/Connection/Base.html +203 -116
- data/doc/rdoc/classes/ServerSide/Connection/Const.html +35 -5
- data/doc/rdoc/classes/ServerSide/Connection/Router.html +107 -78
- data/doc/rdoc/classes/ServerSide/Server.html +1 -1
- data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +10 -0
- data/doc/rdoc/classes/ServerSide/StaticFiles.html +73 -43
- data/doc/rdoc/classes/ServerSide/Template.html +200 -0
- data/doc/rdoc/classes/ServerSide.html +13 -8
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/CHANGELOG.html +44 -1
- data/doc/rdoc/files/README.html +63 -35
- data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/static_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/template_rb.html +108 -0
- data/doc/rdoc/fr_class_index.html +1 -2
- data/doc/rdoc/fr_file_index.html +1 -0
- data/doc/rdoc/fr_method_index.html +18 -16
- data/lib/serverside/application.rb +0 -28
- data/lib/serverside/connection.rb +38 -12
- data/lib/serverside/routing.rb +23 -2
- data/lib/serverside/server.rb +1 -1
- data/lib/serverside/static.rb +12 -0
- data/lib/serverside/template.rb +36 -0
- data/test/functional/primitive_static_server_test.rb +26 -2
- data/test/functional/static_rfuzz.rb +3 -39
- data/test/spec/core_ext_spec.rb +68 -0
- data/test/unit/connection_test.rb +37 -1
- data/test/unit/template_test.rb +78 -0
- metadata +8 -6
- data/doc/rdoc/classes/ServerSide/Application/Base.html +0 -196
- data/doc/rdoc/classes/ServerSide/Application/Static.html +0 -156
- data/test/unit/application_test.rb +0 -16
@@ -24,48 +24,50 @@
|
|
24
24
|
<a href="classes/ServerSide/Connection/Router.html#M000046">cache_constant (ServerSide::Connection::Router)</a><br />
|
25
25
|
<a href="classes/ServerSide/Connection/Router.html#M000042">compile_rules (ServerSide::Connection::Router)</a><br />
|
26
26
|
<a href="classes/ServerSide/Connection/Router.html#M000044">condition_part (ServerSide::Connection::Router)</a><br />
|
27
|
-
<a href="classes/ServerSide/Application.html#
|
28
|
-
<a href="classes/ServerSide/Application/Base.html#M000030">configuration (ServerSide::Application::Base)</a><br />
|
29
|
-
<a href="classes/ServerSide/Application/Base.html#M000029">configure (ServerSide::Application::Base)</a><br />
|
27
|
+
<a href="classes/ServerSide/Application.html#M000028">config= (ServerSide::Application)</a><br />
|
30
28
|
<a href="classes/Object.html#M000003">const_tag (Object)</a><br />
|
31
29
|
<a href="classes/Daemon.html#M000007">control (Daemon)</a><br />
|
32
30
|
<a href="classes/Daemon/Cluster.html#M000016">daemon_loop (Daemon::Cluster)</a><br />
|
33
|
-
<a href="classes/ServerSide/Application
|
34
|
-
<a href="classes/ServerSide/Application.html#M000028">daemonize (ServerSide::Application)</a><br />
|
31
|
+
<a href="classes/ServerSide/Application.html#M000029">daemonize (ServerSide::Application)</a><br />
|
35
32
|
<a href="classes/ServerSide/Connection/Router.html#M000049">default_handler (ServerSide::Connection::Router)</a><br />
|
36
33
|
<a href="classes/ServerSide/Connection/Router.html#M000045">define_proc (ServerSide::Connection::Router)</a><br />
|
37
34
|
<a href="classes/Daemon/Cluster/PidFile.html#M000019">delete (Daemon::Cluster::PidFile)</a><br />
|
35
|
+
<a href="classes/ServerSide/Connection/Base.html#M000039">delete_cookie (ServerSide::Connection::Base)</a><br />
|
38
36
|
<a href="classes/Daemon/Cluster.html#M000013">fork_server (Daemon::Cluster)</a><br />
|
39
37
|
<a href="classes/ServerSide/Connection/Router.html#M000040">has_routes? (ServerSide::Connection::Router)</a><br />
|
40
38
|
<a href="classes/ServerSide/Server.html#M000050">new (ServerSide::Server)</a><br />
|
41
|
-
<a href="classes/ServerSide/
|
42
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
43
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
44
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
39
|
+
<a href="classes/ServerSide/Connection/Base.html#M000030">new (ServerSide::Connection::Base)</a><br />
|
40
|
+
<a href="classes/ServerSide/Connection/Base.html#M000034">parse_cookies (ServerSide::Connection::Base)</a><br />
|
41
|
+
<a href="classes/ServerSide/Connection/Base.html#M000033">parse_parameters (ServerSide::Connection::Base)</a><br />
|
42
|
+
<a href="classes/ServerSide/Connection/Base.html#M000032">parse_request (ServerSide::Connection::Base)</a><br />
|
45
43
|
<a href="classes/Daemon/Base.html#M000012">pid_fn (Daemon::Base)</a><br />
|
46
44
|
<a href="classes/Proc.html#M000002">proc_tag (Proc)</a><br />
|
47
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
45
|
+
<a href="classes/ServerSide/Connection/Base.html#M000031">process (ServerSide::Connection::Base)</a><br />
|
48
46
|
<a href="classes/Daemon/PidFile.html#M000011">recall (Daemon::PidFile)</a><br />
|
49
47
|
<a href="classes/Daemon/Cluster/PidFile.html#M000021">recall_pids (Daemon::Cluster::PidFile)</a><br />
|
50
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
51
|
-
<a href="classes/ServerSide/
|
48
|
+
<a href="classes/ServerSide/Connection/Base.html#M000036">redirect (ServerSide::Connection::Base)</a><br />
|
49
|
+
<a href="classes/ServerSide/Template.html#M000052">render (ServerSide::Template)</a><br />
|
52
50
|
<a href="classes/ServerSide.html#M000022">route (ServerSide)</a><br />
|
51
|
+
<a href="classes/ServerSide/Connection/Router.html#M000041">route (ServerSide::Connection::Router)</a><br />
|
53
52
|
<a href="classes/ServerSide.html#M000023">route_default (ServerSide)</a><br />
|
54
53
|
<a href="classes/ServerSide/Connection/Router.html#M000047">route_default (ServerSide::Connection::Router)</a><br />
|
55
54
|
<a href="classes/ServerSide/Connection/Router.html#M000043">rule_to_statement (ServerSide::Connection::Router)</a><br />
|
56
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
55
|
+
<a href="classes/ServerSide/Connection/Base.html#M000035">send_response (ServerSide::Connection::Base)</a><br />
|
57
56
|
<a href="classes/ServerSide/StaticFiles.html#M000025">serve_dir (ServerSide::StaticFiles)</a><br />
|
58
57
|
<a href="classes/ServerSide/StaticFiles.html#M000024">serve_file (ServerSide::StaticFiles)</a><br />
|
59
|
-
<a href="classes/ServerSide/StaticFiles.html#
|
60
|
-
<a href="classes/
|
58
|
+
<a href="classes/ServerSide/StaticFiles.html#M000027">serve_static (ServerSide::StaticFiles)</a><br />
|
59
|
+
<a href="classes/ServerSide/StaticFiles.html#M000026">serve_template (ServerSide::StaticFiles)</a><br />
|
60
|
+
<a href="classes/ServerSide/Template.html#M000051">set (ServerSide::Template)</a><br />
|
61
|
+
<a href="classes/ServerSide/Connection/Base.html#M000038">set_cookie (ServerSide::Connection::Base)</a><br />
|
61
62
|
<a href="classes/Daemon.html#M000008">start (Daemon)</a><br />
|
63
|
+
<a href="classes/Daemon/Cluster.html#M000017">start (Daemon::Cluster)</a><br />
|
62
64
|
<a href="classes/Daemon/Cluster.html#M000014">start_servers (Daemon::Cluster)</a><br />
|
63
65
|
<a href="classes/Daemon/Cluster.html#M000018">stop (Daemon::Cluster)</a><br />
|
64
66
|
<a href="classes/Daemon.html#M000009">stop (Daemon)</a><br />
|
65
67
|
<a href="classes/Daemon/Cluster.html#M000015">stop_servers (Daemon::Cluster)</a><br />
|
66
68
|
<a href="classes/Daemon/PidFile.html#M000010">store (Daemon::PidFile)</a><br />
|
67
69
|
<a href="classes/Daemon/Cluster/PidFile.html#M000020">store_pid (Daemon::Cluster::PidFile)</a><br />
|
68
|
-
<a href="classes/ServerSide/Connection/Base.html#
|
70
|
+
<a href="classes/ServerSide/Connection/Base.html#M000037">stream (ServerSide::Connection::Base)</a><br />
|
69
71
|
<a href="classes/Symbol.html#M000001">to_s (Symbol)</a><br />
|
70
72
|
<a href="classes/ServerSide/Connection/Router.html#M000048">unhandled (ServerSide::Connection::Router)</a><br />
|
71
73
|
<a href="classes/String.html#M000004">uri_escape (String)</a><br />
|
@@ -21,33 +21,5 @@ module ServerSide
|
|
21
21
|
end
|
22
22
|
Daemon.control(daemon_class, cmd)
|
23
23
|
end
|
24
|
-
|
25
|
-
class Base < ServerSide::Connection::Base
|
26
|
-
def self.configure(options)
|
27
|
-
@config = options
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.configuration
|
31
|
-
@config
|
32
|
-
end
|
33
|
-
|
34
|
-
def initialize(host, port)
|
35
|
-
ServerSide::Server.new(host, port, make_request_class)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class Static
|
40
|
-
def self.daemonize(config, cmd)
|
41
|
-
daemon_class = Class.new(Daemon::Cluster) do
|
42
|
-
meta_def(:pid_fn) {Daemon::WorkingDirectory/'serverside.pid'}
|
43
|
-
meta_def(:server_loop) do |port|
|
44
|
-
ServerSide::Server.new(config[:host], port, ServerSide::Connection::Static)
|
45
|
-
end
|
46
|
-
meta_def(:ports) {$cmd_config[:ports]}
|
47
|
-
end
|
48
|
-
|
49
|
-
Daemon.control(daemon_class, cmd)
|
50
|
-
end
|
51
|
-
end
|
52
24
|
end
|
53
25
|
end
|
@@ -25,14 +25,20 @@ module ServerSide
|
|
25
25
|
# Regexp for parsing URI parameters.
|
26
26
|
ParameterRegexp = /(.+)=(.*)/.freeze
|
27
27
|
EqualSign = '='.freeze
|
28
|
-
StatusClose = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%sContent-Length: %d\r\n\r\n".freeze
|
29
|
-
StatusStream = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s\r\n".freeze
|
30
|
-
StatusPersist = "HTTP/1.1 %d\r\nContent-Type: %s\r\n%sContent-Length: %d\r\n\r\n".freeze
|
28
|
+
StatusClose = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
|
29
|
+
StatusStream = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s%s\r\n".freeze
|
30
|
+
StatusPersist = "HTTP/1.1 %d\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
|
31
31
|
StatusRedirect = "HTTP/1.1 %d\r\nConnection: close\r\nLocation: %s\r\n\r\n".freeze
|
32
32
|
Header = "%s: %s\r\n".freeze
|
33
|
-
|
33
|
+
EmptyString = ''.freeze
|
34
|
+
EmptyHash = {}.freeze
|
34
35
|
Slash = '/'.freeze
|
35
|
-
Location = 'Location'
|
36
|
+
Location = 'Location'.freeze
|
37
|
+
Cookie = 'Cookie'
|
38
|
+
SetCookie = "Set-Cookie: %s=%s; path=/; expires=%s\r\n".freeze
|
39
|
+
CookieSplit = /[;,] */n.freeze
|
40
|
+
CookieRegexp = /\s*(.+)=(.*)\s*/.freeze
|
41
|
+
CookieExpiredTime = Time.at(0).freeze
|
36
42
|
end
|
37
43
|
|
38
44
|
# This is the base request class. When a new request is created, it starts
|
@@ -60,10 +66,6 @@ module ServerSide
|
|
60
66
|
break unless @persistent
|
61
67
|
end
|
62
68
|
rescue => e
|
63
|
-
puts '*******************'
|
64
|
-
puts e.message
|
65
|
-
puts e.backtrace.first
|
66
|
-
puts '*******************'
|
67
69
|
# We don't care. Just close the connection.
|
68
70
|
ensure
|
69
71
|
@conn.close
|
@@ -86,6 +88,9 @@ module ServerSide
|
|
86
88
|
end
|
87
89
|
@persistent = (@version == Const::Version_1_1) &&
|
88
90
|
(@headers[Const::Connection] != Const::Close)
|
91
|
+
@cookies = @headers[Const::Cookie] ? parse_cookies : Const::EmptyHash
|
92
|
+
@response_cookies = nil
|
93
|
+
|
89
94
|
@headers
|
90
95
|
end
|
91
96
|
|
@@ -99,20 +104,30 @@ module ServerSide
|
|
99
104
|
m
|
100
105
|
end
|
101
106
|
end
|
107
|
+
|
108
|
+
# Parses cookie values passed in the request
|
109
|
+
def parse_cookies
|
110
|
+
@headers[Const::Cookie].split(Const::CookieSplit).inject({}) do |m, i|
|
111
|
+
if i =~ Const::CookieRegexp
|
112
|
+
m[$1.to_sym] = $2.uri_unescape
|
113
|
+
end
|
114
|
+
m
|
115
|
+
end
|
116
|
+
end
|
102
117
|
|
103
118
|
# Sends an HTTP response.
|
104
119
|
def send_response(status, content_type, body = nil, content_length = nil,
|
105
120
|
headers = nil)
|
106
121
|
h = headers ?
|
107
|
-
headers.inject('') {|m, kv| m << (Const::Header % kv)} :
|
108
|
-
|
122
|
+
headers.inject('') {|m, kv| m << (Const::Header % kv)} : ''
|
123
|
+
|
109
124
|
content_length = body.length if content_length.nil? && body
|
110
125
|
@persistent = false if content_length.nil?
|
111
126
|
|
112
127
|
# Select the right format to use according to circumstances.
|
113
128
|
@conn << ((@persistent ? Const::StatusPersist :
|
114
129
|
(body ? Const::StatusClose : Const::StatusStream)) %
|
115
|
-
[status, content_type, h, content_length])
|
130
|
+
[status, content_type, h, @response_cookies, content_length])
|
116
131
|
@conn << body if body
|
117
132
|
rescue
|
118
133
|
@persistent = false
|
@@ -130,6 +145,17 @@ module ServerSide
|
|
130
145
|
def stream(body)
|
131
146
|
(@conn << body if body) rescue (@persistent = false)
|
132
147
|
end
|
148
|
+
|
149
|
+
# Sets a cookie to be included in the response.
|
150
|
+
def set_cookie(name, value, expires)
|
151
|
+
@response_cookies ||= ""
|
152
|
+
@response_cookies << (Const::SetCookie % [name, value.to_s.uri_escape, expires.rfc2822])
|
153
|
+
end
|
154
|
+
|
155
|
+
# Marks a cookie as deleted. The cookie is given an expires stamp in the past.
|
156
|
+
def delete_cookie(name)
|
157
|
+
set_cookie(name, nil, Const::CookieExpiredTime)
|
158
|
+
end
|
133
159
|
end
|
134
160
|
end
|
135
161
|
end
|
data/lib/serverside/routing.rb
CHANGED
@@ -2,7 +2,24 @@ module ServerSide
|
|
2
2
|
module Connection
|
3
3
|
# The Router class defines a kind of connection that can route requests
|
4
4
|
# to different handlers based on rules that can be specified either by
|
5
|
-
# lambdas or by hashes
|
5
|
+
# lambdas or by hashes that contain variable names corresponding to patterns.
|
6
|
+
#
|
7
|
+
# The simplest form of a routing rule specifies a path pattern:
|
8
|
+
#
|
9
|
+
# ServerSide.route('/static') {serve_static('.'/@path)}
|
10
|
+
#
|
11
|
+
# But you can also check for other attributes of the request:
|
12
|
+
#
|
13
|
+
# ServerSide.route(:path => '/static', :host => '^:subdomain\.mydomain') {
|
14
|
+
# serve_static(@parameters[:subdomain]/@path)
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# It also possible to pass a lambda as a rule:
|
18
|
+
#
|
19
|
+
# ServerSide.route(lambda {@headers['Agent'] =~ /Moz/}) {serve_static('moz'/@path)}
|
20
|
+
#
|
21
|
+
# Routing rules are evaluated in backwards, so the rules should be ordered
|
22
|
+
# from the general to the specific.
|
6
23
|
class Router < Base
|
7
24
|
# Returns true if routes were defined.
|
8
25
|
def self.has_routes?
|
@@ -12,7 +29,11 @@ module ServerSide
|
|
12
29
|
# Adds a routing rule. The normalized rule is a hash containing keys (acting
|
13
30
|
# as instance variable names) with patterns as values. If the rule is not a
|
14
31
|
# hash, it is normalized into a pattern checked against the request path.
|
15
|
-
# Pattern values can also be arrays,
|
32
|
+
# Pattern values can also be arrays, any member of which is checked as a
|
33
|
+
# pattern. The rule can also be a Proc or lambda which is run with the
|
34
|
+
# connection object's binding. A contrived example:
|
35
|
+
#
|
36
|
+
# ServerSide.route(lambda{path = 'mypage'}) {serve_static('mypage.html')}
|
16
37
|
def self.route(rule, &block)
|
17
38
|
@@rules ||= []
|
18
39
|
rule = {:path => rule} unless (Hash === rule) || (Proc === rule)
|
data/lib/serverside/server.rb
CHANGED
@@ -6,7 +6,7 @@ module ServerSide
|
|
6
6
|
# for applications which use Comet techniques.
|
7
7
|
class Server
|
8
8
|
# Creates a new server by opening a listening socket and starting an accept
|
9
|
-
# loop. When a new connection is accepted, a new instance of the
|
9
|
+
# loop. When a new connection is accepted, a new instance of the
|
10
10
|
# supplied connection class is instantiated and passed the connection for
|
11
11
|
# processing.
|
12
12
|
def initialize(host, port, connection_class)
|
data/lib/serverside/static.rb
CHANGED
@@ -12,12 +12,14 @@ module ServerSide
|
|
12
12
|
NotModifiedClose = "HTTP/1.1 304 Not Modified\r\nConnection: close\r\nContent-Length: 0\r\nETag: %s\r\nCache-Control: #{MaxAge}\r\n\r\n".freeze
|
13
13
|
NotModifiedPersist = "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\nETag: %s\r\nCache-Control: #{MaxAge}\r\n\r\n".freeze
|
14
14
|
TextPlain = 'text/plain'.freeze
|
15
|
+
TextHTML = 'text/html'.freeze
|
15
16
|
MaxCacheFileSize = 100000.freeze # 100KB for the moment
|
16
17
|
|
17
18
|
DirListingStart = '<html><head><title>Directory Listing for %s</title></head><body><h2>Directory listing for %s:</h2>'.freeze
|
18
19
|
DirListing = '<a href="%s">%s</a><br/>'.freeze
|
19
20
|
DirListingStop = '</body></html>'.freeze
|
20
21
|
FileNotFound = 'File not found.'.freeze
|
22
|
+
RHTML = /\.rhtml$/.freeze
|
21
23
|
end
|
22
24
|
|
23
25
|
@@mime_types = Hash.new {|h, k| ServerSide::StaticFiles::Const::TextPlain}
|
@@ -68,15 +70,25 @@ module ServerSide
|
|
68
70
|
send_response(200, 'text/html', html)
|
69
71
|
end
|
70
72
|
|
73
|
+
def serve_template(fn, b = nil)
|
74
|
+
if (fn =~ Const::RHTML) || (File.file?(fn = fn + '.rhtml'))
|
75
|
+
send_response(200, Const::TextHTML, Template.render(fn, b || binding))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
71
79
|
# Serves static files and directory listings.
|
72
80
|
def serve_static(path)
|
73
81
|
if File.file?(path)
|
74
82
|
serve_file(path)
|
83
|
+
elsif serve_template(path)
|
84
|
+
return
|
75
85
|
elsif File.directory?(path)
|
76
86
|
serve_dir(path)
|
77
87
|
else
|
78
88
|
send_response(404, 'text', Const::FileNotFound)
|
79
89
|
end
|
90
|
+
rescue => e
|
91
|
+
send_response(500, 'text', e.message)
|
80
92
|
end
|
81
93
|
end
|
82
94
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module ServerSide
|
4
|
+
# The Template module implements an ERB template rendering system. Templates
|
5
|
+
# are cached and automatically reloaded if the file changes.
|
6
|
+
class Template
|
7
|
+
# The @@templates variable is a hash keyed by template name. The values are
|
8
|
+
# arrays containing 2 objects: a file stamp (if the template comes from a
|
9
|
+
# file,) and the template object itself.
|
10
|
+
@@templates = {}
|
11
|
+
|
12
|
+
# Stores a template for later use. The stamp parameter is used only when
|
13
|
+
# the content of a template file is stored.
|
14
|
+
def self.set(name, body, stamp = nil)
|
15
|
+
@@templates[name] = [stamp, ERB.new(body)]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Renders a template. If the template name is not found, attemps to load
|
19
|
+
# the template from file. If the template has a non-nil stamp, the render
|
20
|
+
# method compares it to the file stamp, and reloads the template content
|
21
|
+
# if necessary.
|
22
|
+
def self.render(name, binding)
|
23
|
+
t = @@templates[name]
|
24
|
+
return t[1].result(binding) if t && t[0].nil?
|
25
|
+
|
26
|
+
if File.file?(name)
|
27
|
+
stamp = File.mtime(name)
|
28
|
+
t = set(name, IO.read(name), stamp) if (!t || (stamp != t[0]))
|
29
|
+
t[1].result(binding)
|
30
|
+
else
|
31
|
+
@@templates[name] = nil
|
32
|
+
raise RuntimeError, 'Template not found.'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -5,6 +5,11 @@ class StaticServerTest < Test::Unit::TestCase
|
|
5
5
|
|
6
6
|
class StaticConnection < ServerSide::Connection::Base
|
7
7
|
def respond
|
8
|
+
begin
|
9
|
+
# set_cookie(:hello, 'world', Time.now + 30) if @cookies[:test] == 'hello'
|
10
|
+
rescue => e
|
11
|
+
puts e.message
|
12
|
+
end
|
8
13
|
status = 200
|
9
14
|
body = IO.read('.'/@path)
|
10
15
|
rescue => e
|
@@ -16,7 +21,7 @@ class StaticServerTest < Test::Unit::TestCase
|
|
16
21
|
end
|
17
22
|
|
18
23
|
def test_basic
|
19
|
-
t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654, StaticConnection)}
|
24
|
+
@t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654, StaticConnection)}
|
20
25
|
sleep 0.1
|
21
26
|
|
22
27
|
h = Net::HTTP.new('localhost', 17654)
|
@@ -32,6 +37,25 @@ class StaticServerTest < Test::Unit::TestCase
|
|
32
37
|
# Net::HTTP includes this header in the request, so our server returns
|
33
38
|
# likewise.
|
34
39
|
assert_equal 'close', resp['Connection']
|
35
|
-
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_cookies
|
43
|
+
@t = Thread.new {ServerSide::Server.new('0.0.0.0', 17655, StaticConnection)}
|
44
|
+
sleep 0.1
|
45
|
+
|
46
|
+
h = Net::HTTP.new('localhost', 17655)
|
47
|
+
resp, data = h.get('/qqq', nil)
|
48
|
+
assert_equal 404, resp.code.to_i
|
49
|
+
assert_nil resp['Set-Cookie']
|
50
|
+
|
51
|
+
h = Net::HTTP.new('localhost', 17655)
|
52
|
+
resp, data = h.get('/qqq', {'Cookie' => 'test=hello'})
|
53
|
+
assert_equal 404, resp.code.to_i
|
54
|
+
assert_not_nil resp['Set-Cookie'] =~ /hello=world/
|
55
|
+
|
56
|
+
h = Net::HTTP.new('localhost', 17655)
|
57
|
+
resp, data = h.get('/qqq', nil)
|
58
|
+
assert_equal 404, resp.code.to_i
|
59
|
+
assert_nil resp['Set-Cookie']
|
36
60
|
end
|
37
61
|
end
|
@@ -6,44 +6,6 @@ require 'rfuzz/client'
|
|
6
6
|
require 'rfuzz/stats'
|
7
7
|
include RFuzz
|
8
8
|
|
9
|
-
class StatsTracker
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
@stats = {}
|
13
|
-
@begins = {}
|
14
|
-
@error_count = 0
|
15
|
-
end
|
16
|
-
|
17
|
-
def mark(event)
|
18
|
-
@begins[event] = Time.now
|
19
|
-
end
|
20
|
-
|
21
|
-
def sample(event)
|
22
|
-
@stats[event] ||= Stats.new(event.to_s)
|
23
|
-
@stats[event].sample(Time.now - @begins[event])
|
24
|
-
end
|
25
|
-
|
26
|
-
def method_missing(event, *args)
|
27
|
-
case args[0]
|
28
|
-
when :begins
|
29
|
-
mark(:request) if event == :connect
|
30
|
-
mark(event)
|
31
|
-
when :ends
|
32
|
-
sample(:request) if event == :close
|
33
|
-
sample(event)
|
34
|
-
when :error
|
35
|
-
@error_count += 1
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def to_s
|
40
|
-
request = @stats[:request]
|
41
|
-
@stats.delete :request
|
42
|
-
"#{request}\n----\n#{@stats.values.join("\n")}\nErrors: #@error_count"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
9
|
if ARGV.length != 4
|
48
10
|
STDERR.puts "usage: ruby perftest.rb host port uri count"
|
49
11
|
exit 1
|
@@ -59,7 +21,9 @@ count.times do
|
|
59
21
|
code = resp.http_status.to_i
|
60
22
|
codes[code] ||= 0
|
61
23
|
codes[code] += 1
|
62
|
-
rescue
|
24
|
+
rescue => e
|
25
|
+
puts e.message
|
26
|
+
puts e.backtrace.first
|
63
27
|
end
|
64
28
|
end
|
65
29
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../../lib/serverside')
|
2
|
+
|
3
|
+
# String extensions
|
4
|
+
|
5
|
+
context "String" do
|
6
|
+
specify "should have URI escaping functionality" do
|
7
|
+
'a b c'.uri_escape.should_equal 'a+b+c'
|
8
|
+
'a/b#1@6%8K'.uri_escape.should_equal 'a%2Fb%231%406%258K'
|
9
|
+
end
|
10
|
+
|
11
|
+
specify "should have URI unescaping functionality" do
|
12
|
+
'a%20b%20c'.uri_unescape.should_equal 'a b c'
|
13
|
+
'a%2Fb%231%406%258K'.uri_unescape.should_equal 'a/b#1@6%8K'
|
14
|
+
s = 'b!-=&*%aAåabéfak{}":,m"\'Mbac( 12313t awerqwe)'
|
15
|
+
s.uri_escape.uri_unescape.should_equal s
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "should have a / operator for joining paths." do
|
19
|
+
('abc'/'def').should_equal 'abc/def'
|
20
|
+
('/hello/'/'there').should_equal '/hello/there'
|
21
|
+
('touch'/'/me/'/'hold'/'/me').should_equal 'touch/me/hold/me'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Symbol extensions
|
26
|
+
|
27
|
+
class Symbol
|
28
|
+
attr_reader :_to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Symbol.to_s" do
|
32
|
+
specify "should convert the symbol to a string" do
|
33
|
+
:abc_def.to_s.should_equal 'abc_def'
|
34
|
+
:def_ghi.to_s.should_be_instance_of String
|
35
|
+
:ghi_jkl.to_s.should_equal :ghi_jkl.id2name
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "should cache the id2name value" do
|
39
|
+
:kwantz_mit_krantz._to_s.should_be_nil
|
40
|
+
:kwantz_mit_krantz.to_s
|
41
|
+
:kwantz_mit_krantz._to_s.should_equal :kwantz_mit_krantz.id2name
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "should always return the same cached value" do
|
45
|
+
:item.to_s.should_be :item.to_s
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "Proc.proc_tag" do
|
50
|
+
setup do
|
51
|
+
@l1 = lambda {1 + 1}
|
52
|
+
@l2 = lambda {1 + 1}
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "should return a unique tag for the proc object" do
|
56
|
+
@l1.proc_tag.should_not_equal @l2.proc_tag
|
57
|
+
end
|
58
|
+
|
59
|
+
specify "should return the same tag always" do
|
60
|
+
@l1.proc_tag.should_equal @l1.proc_tag
|
61
|
+
end
|
62
|
+
|
63
|
+
specify "should return the object_hash in base 36 prefixed with 'proc_'" do
|
64
|
+
@l1.proc_tag.should_equal 'proc_' + @l1.object_id.to_s(36).sub('-', '_')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
@@ -52,7 +52,7 @@ class ConnectionTest < Test::Unit::TestCase
|
|
52
52
|
|
53
53
|
class ServerSide::Connection::Base
|
54
54
|
attr_accessor :conn, :method, :query, :version, :path, :parameters,
|
55
|
-
:headers, :persistent
|
55
|
+
:headers, :persistent, :cookies, :response_cookies
|
56
56
|
end
|
57
57
|
|
58
58
|
class DummyConnection3 < ServerSide::Connection::Base
|
@@ -88,6 +88,8 @@ class ConnectionTest < Test::Unit::TestCase
|
|
88
88
|
assert_equal '1.1', r.version
|
89
89
|
assert_equal({}, r.parameters)
|
90
90
|
assert_equal({}, r.headers)
|
91
|
+
assert_equal({}, r.cookies)
|
92
|
+
assert_nil r.response_cookies
|
91
93
|
assert_equal true, r.persistent
|
92
94
|
|
93
95
|
# trailing slash in path
|
@@ -100,6 +102,8 @@ class ConnectionTest < Test::Unit::TestCase
|
|
100
102
|
assert_equal '1.1', r.version
|
101
103
|
assert_equal({:time => '24 hours'}, r.parameters)
|
102
104
|
assert_equal({}, r.headers)
|
105
|
+
assert_equal({}, r.cookies)
|
106
|
+
assert_nil r.response_cookies
|
103
107
|
assert_equal true, r.persistent
|
104
108
|
|
105
109
|
r.conn = StringIO.new("GET /cex?q=node_state HTTP/1.1\r\n\r\n")
|
@@ -108,10 +112,14 @@ class ConnectionTest < Test::Unit::TestCase
|
|
108
112
|
assert_equal '/cex', r.path
|
109
113
|
assert_equal 'q=node_state', r.query
|
110
114
|
assert_equal({:q => 'node_state'}, r.parameters)
|
115
|
+
assert_equal({}, r.cookies)
|
116
|
+
assert_nil r.response_cookies
|
111
117
|
|
112
118
|
r.conn = StringIO.new("GET / HTTP/1.0\r\n\r\n")
|
113
119
|
assert_kind_of Hash, r.parse_request
|
114
120
|
assert_equal false, r.persistent
|
121
|
+
assert_equal({}, r.cookies)
|
122
|
+
assert_nil r.response_cookies
|
115
123
|
|
116
124
|
r.conn = StringIO.new("GET / HTTP/invalid\r\n\r\n")
|
117
125
|
assert_kind_of Hash, r.parse_request
|
@@ -123,6 +131,13 @@ class ConnectionTest < Test::Unit::TestCase
|
|
123
131
|
assert_equal '1.1', r.version
|
124
132
|
assert_equal 'close', r.headers['Connection']
|
125
133
|
assert_equal false, r.persistent
|
134
|
+
|
135
|
+
# cookies
|
136
|
+
r.conn = StringIO.new("GET / HTTP/1.1\r\nConnection: close\r\nCookie: abc=1342; def=7%2f4\r\n\r\n")
|
137
|
+
assert_kind_of Hash, r.parse_request
|
138
|
+
assert_equal 'abc=1342; def=7%2f4', r.headers['Cookie']
|
139
|
+
assert_equal '1342', r.cookies[:abc]
|
140
|
+
assert_equal '7/4', r.cookies[:def]
|
126
141
|
end
|
127
142
|
|
128
143
|
def test_send_response
|
@@ -203,4 +218,25 @@ class ConnectionTest < Test::Unit::TestCase
|
|
203
218
|
r.conn.rewind
|
204
219
|
assert_equal "HTTP/1.1 301\r\nConnection: close\r\nLocation: http://www.google.com/search?q=meaning%20of%20life\r\n\r\n", r.conn.read
|
205
220
|
end
|
221
|
+
|
222
|
+
def test_set_cookie
|
223
|
+
r = DummyConnection3.new
|
224
|
+
r.conn = StringIO.new
|
225
|
+
t = Time.now + 360
|
226
|
+
r.set_cookie(:session, "ABCDEFG", t)
|
227
|
+
assert_equal "Set-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\n", r.response_cookies
|
228
|
+
r.send_response(200, 'text', 'Hi!')
|
229
|
+
r.conn.rewind
|
230
|
+
assert_equal "HTTP/1.1 200\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\nContent-Length: 3\r\n\r\nHi!", r.conn.read
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_delete_cookie
|
234
|
+
r = DummyConnection3.new
|
235
|
+
r.conn = StringIO.new
|
236
|
+
r.delete_cookie(:session)
|
237
|
+
assert_equal "Set-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\n", r.response_cookies
|
238
|
+
r.send_response(200, 'text', 'Hi!')
|
239
|
+
r.conn.rewind
|
240
|
+
assert_equal "HTTP/1.1 200\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.conn.read
|
241
|
+
end
|
206
242
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'stringio'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class ServerSide::Template
|
6
|
+
def self.reset
|
7
|
+
@@templates = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.templates
|
11
|
+
@@templates
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TemplateTest < Test::Unit::TestCase
|
16
|
+
def test_set
|
17
|
+
ServerSide::Template.reset
|
18
|
+
assert_equal 0, ServerSide::Template.templates.size
|
19
|
+
ServerSide::Template.set('sharon', 'Hello chilren!')
|
20
|
+
assert_equal 1, ServerSide::Template.templates.size
|
21
|
+
assert_equal ['sharon'], ServerSide::Template.templates.keys
|
22
|
+
t = ServerSide::Template.templates['sharon']
|
23
|
+
assert_kind_of Array, t
|
24
|
+
assert_equal nil, t[0]
|
25
|
+
assert_kind_of ERB, t[1]
|
26
|
+
assert_equal 'Hello chilren!', t[1].result(binding)
|
27
|
+
|
28
|
+
stamp = Time.now - 100
|
29
|
+
ServerSide::Template.set('zohar', 'FSMV <%= wow %>', stamp)
|
30
|
+
assert_equal 2, ServerSide::Template.templates.size
|
31
|
+
assert_equal true, ServerSide::Template.templates.keys.include?('zohar')
|
32
|
+
t = ServerSide::Template.templates['zohar']
|
33
|
+
assert_kind_of Array, t
|
34
|
+
assert_equal stamp, t[0]
|
35
|
+
assert_kind_of ERB, t[1]
|
36
|
+
wow = 'wow'
|
37
|
+
assert_equal 'FSMV wow', t[1].result(binding)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_render
|
41
|
+
ServerSide::Template.reset
|
42
|
+
assert_raise(RuntimeError) {ServerSide::Template.render('sharon', binding)}
|
43
|
+
|
44
|
+
ServerSide::Template.set('sharon', 'Hello <%= name %>!')
|
45
|
+
name = 'world'
|
46
|
+
assert_equal 'Hello world!', ServerSide::Template.render('sharon', binding)
|
47
|
+
end
|
48
|
+
|
49
|
+
FN = 'test.rhtml'
|
50
|
+
HtmlTemplate = "<% cats.each do |name| %><li><%= name %></li><% end %>"
|
51
|
+
|
52
|
+
def teardown
|
53
|
+
FileUtils.rm(FN) if File.file?(FN)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_render_file
|
57
|
+
FileUtils.rm(FN) if File.file?(FN)
|
58
|
+
assert_raise(RuntimeError) {ServerSide::Template.render(FN, binding)}
|
59
|
+
|
60
|
+
File.open(FN, 'w') {|f| f << HtmlTemplate}
|
61
|
+
assert_raise(NoMethodError) {ServerSide::Template.render(FN, binding)}
|
62
|
+
|
63
|
+
cats = %w{Ciconia Marie Tipa}
|
64
|
+
s = ServerSide::Template.render(FN, binding)
|
65
|
+
assert_equal "<li>Ciconia</li><li>Marie</li><li>Tipa</li>", s
|
66
|
+
|
67
|
+
cats = []
|
68
|
+
s = ServerSide::Template.render(FN, binding)
|
69
|
+
assert_nil s.match('<li>')
|
70
|
+
|
71
|
+
sleep 2
|
72
|
+
|
73
|
+
cats = %w{Ciconia Marie Tipa}
|
74
|
+
File.open(FN, 'w') {|f| f << "<%= cats.join(', ') %>"}
|
75
|
+
|
76
|
+
assert_equal 'Ciconia, Marie, Tipa', ServerSide::Template.render(FN, binding)
|
77
|
+
end
|
78
|
+
end
|