serverside 0.2.0 → 0.2.5
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 +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
|