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.
Files changed (40) hide show
  1. data/CHANGELOG +26 -0
  2. data/README +39 -22
  3. data/Rakefile +18 -2
  4. data/bin/serverside +21 -5
  5. data/doc/rdoc/classes/ServerSide/Application.html +12 -19
  6. data/doc/rdoc/classes/ServerSide/Connection/Base.html +203 -116
  7. data/doc/rdoc/classes/ServerSide/Connection/Const.html +35 -5
  8. data/doc/rdoc/classes/ServerSide/Connection/Router.html +107 -78
  9. data/doc/rdoc/classes/ServerSide/Server.html +1 -1
  10. data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +10 -0
  11. data/doc/rdoc/classes/ServerSide/StaticFiles.html +73 -43
  12. data/doc/rdoc/classes/ServerSide/Template.html +200 -0
  13. data/doc/rdoc/classes/ServerSide.html +13 -8
  14. data/doc/rdoc/created.rid +1 -1
  15. data/doc/rdoc/files/CHANGELOG.html +44 -1
  16. data/doc/rdoc/files/README.html +63 -35
  17. data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
  18. data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
  19. data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
  20. data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
  21. data/doc/rdoc/files/lib/serverside/static_rb.html +1 -1
  22. data/doc/rdoc/files/lib/serverside/template_rb.html +108 -0
  23. data/doc/rdoc/fr_class_index.html +1 -2
  24. data/doc/rdoc/fr_file_index.html +1 -0
  25. data/doc/rdoc/fr_method_index.html +18 -16
  26. data/lib/serverside/application.rb +0 -28
  27. data/lib/serverside/connection.rb +38 -12
  28. data/lib/serverside/routing.rb +23 -2
  29. data/lib/serverside/server.rb +1 -1
  30. data/lib/serverside/static.rb +12 -0
  31. data/lib/serverside/template.rb +36 -0
  32. data/test/functional/primitive_static_server_test.rb +26 -2
  33. data/test/functional/static_rfuzz.rb +3 -39
  34. data/test/spec/core_ext_spec.rb +68 -0
  35. data/test/unit/connection_test.rb +37 -1
  36. data/test/unit/template_test.rb +78 -0
  37. metadata +8 -6
  38. data/doc/rdoc/classes/ServerSide/Application/Base.html +0 -196
  39. data/doc/rdoc/classes/ServerSide/Application/Static.html +0 -156
  40. 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#M000027">config= (ServerSide::Application)</a><br />
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/Static.html#M000032">daemonize (ServerSide::Application::Static)</a><br />
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/Application/Base.html#M000031">new (ServerSide::Application::Base)</a><br />
42
- <a href="classes/ServerSide/Connection/Base.html#M000033">new (ServerSide::Connection::Base)</a><br />
43
- <a href="classes/ServerSide/Connection/Base.html#M000036">parse_parameters (ServerSide::Connection::Base)</a><br />
44
- <a href="classes/ServerSide/Connection/Base.html#M000035">parse_request (ServerSide::Connection::Base)</a><br />
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#M000034">process (ServerSide::Connection::Base)</a><br />
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#M000038">redirect (ServerSide::Connection::Base)</a><br />
51
- <a href="classes/ServerSide/Connection/Router.html#M000041">route (ServerSide::Connection::Router)</a><br />
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#M000037">send_response (ServerSide::Connection::Base)</a><br />
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#M000026">serve_static (ServerSide::StaticFiles)</a><br />
60
- <a href="classes/Daemon/Cluster.html#M000017">start (Daemon::Cluster)</a><br />
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#M000039">stream (ServerSide::Connection::Base)</a><br />
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
- Empty = ''.freeze
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)} : Const::Empty
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
@@ -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 containing keys corresponding to patterns.
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, in any array member is checked as a pattern.
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)
@@ -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 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)
@@ -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
- t.exit
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 Object
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