serverside 0.2.0 → 0.2.5

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