serverside 0.1.59

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG +97 -0
  2. data/COPYING +18 -0
  3. data/README +51 -0
  4. data/Rakefile +92 -0
  5. data/bin/serverside +74 -0
  6. data/doc/rdoc/classes/Daemon.html +253 -0
  7. data/doc/rdoc/classes/Daemon/Base.html +146 -0
  8. data/doc/rdoc/classes/Daemon/Cluster.html +308 -0
  9. data/doc/rdoc/classes/Daemon/Cluster/PidFile.html +228 -0
  10. data/doc/rdoc/classes/Daemon/PidFile.html +178 -0
  11. data/doc/rdoc/classes/ServerSide.html +160 -0
  12. data/doc/rdoc/classes/ServerSide/Application.html +147 -0
  13. data/doc/rdoc/classes/ServerSide/Application/Base.html +196 -0
  14. data/doc/rdoc/classes/ServerSide/Application/Static.html +154 -0
  15. data/doc/rdoc/classes/ServerSide/Connection.html +128 -0
  16. data/doc/rdoc/classes/ServerSide/Connection/Base.html +343 -0
  17. data/doc/rdoc/classes/ServerSide/Connection/Const.html +229 -0
  18. data/doc/rdoc/classes/ServerSide/Connection/Static.html +172 -0
  19. data/doc/rdoc/classes/ServerSide/Server.html +162 -0
  20. data/doc/rdoc/classes/ServerSide/StaticFiles.html +208 -0
  21. data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +179 -0
  22. data/doc/rdoc/classes/String.html +210 -0
  23. data/doc/rdoc/classes/Symbol.html +156 -0
  24. data/doc/rdoc/created.rid +1 -0
  25. data/doc/rdoc/files/CHANGELOG.html +260 -0
  26. data/doc/rdoc/files/COPYING.html +129 -0
  27. data/doc/rdoc/files/README.html +171 -0
  28. data/doc/rdoc/files/lib/serverside/application_rb.html +109 -0
  29. data/doc/rdoc/files/lib/serverside/cluster_rb.html +101 -0
  30. data/doc/rdoc/files/lib/serverside/connection_rb.html +101 -0
  31. data/doc/rdoc/files/lib/serverside/core_ext_rb.html +107 -0
  32. data/doc/rdoc/files/lib/serverside/daemon_rb.html +108 -0
  33. data/doc/rdoc/files/lib/serverside/server_rb.html +108 -0
  34. data/doc/rdoc/files/lib/serverside/static_rb.html +101 -0
  35. data/doc/rdoc/files/lib/serverside_rb.html +131 -0
  36. data/doc/rdoc/fr_class_index.html +44 -0
  37. data/doc/rdoc/fr_file_index.html +37 -0
  38. data/doc/rdoc/fr_method_index.html +60 -0
  39. data/doc/rdoc/index.html +24 -0
  40. data/doc/rdoc/rdoc-style.css +208 -0
  41. data/lib/serverside.rb +13 -0
  42. data/lib/serverside/application.rb +40 -0
  43. data/lib/serverside/cluster.rb +72 -0
  44. data/lib/serverside/connection.rb +115 -0
  45. data/lib/serverside/core_ext.rb +27 -0
  46. data/lib/serverside/daemon.rb +67 -0
  47. data/lib/serverside/server.rb +18 -0
  48. data/lib/serverside/static.rb +96 -0
  49. data/test/functional/primitive_static_server_test.rb +37 -0
  50. data/test/functional/static_profile.rb +17 -0
  51. data/test/functional/static_rfuzz.rb +67 -0
  52. data/test/functional/static_server_test.rb +25 -0
  53. data/test/test_helper.rb +2 -0
  54. data/test/unit/application_test.rb +16 -0
  55. data/test/unit/cluster_test.rb +129 -0
  56. data/test/unit/connection_test.rb +193 -0
  57. data/test/unit/core_ext_test.rb +32 -0
  58. data/test/unit/daemon_test.rb +75 -0
  59. data/test/unit/server_test.rb +26 -0
  60. data/test/unit/static_test.rb +143 -0
  61. metadata +140 -0
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'net/http'
3
+
4
+ class StaticServerTest < Test::Unit::TestCase
5
+
6
+ def test_basic
7
+ t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654, ServerSide::Connection::Static)}
8
+ sleep 0.1
9
+
10
+ h = Net::HTTP.new('localhost', 17654)
11
+ resp, data = h.get('/qqq.zzz', nil)
12
+ assert_equal 404, resp.code.to_i
13
+ assert_equal "Couldn't open file /qqq.zzz.", data
14
+
15
+ h = Net::HTTP.new('localhost', 17654)
16
+ resp, data = h.get("/#{__FILE__}", nil)
17
+ assert_equal 200, resp.code.to_i
18
+ assert_equal IO.read(__FILE__), data
19
+ assert_equal 'text/plain', resp['Content-Type']
20
+ # Net::HTTP includes this header in the request, so our server returns
21
+ # likewise.
22
+ assert_equal 'close', resp['Connection']
23
+ t.exit
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/serverside')
2
+ require 'test/unit'
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class ApplicationTest < Test::Unit::TestCase
4
+ class App1 < ServerSide::Application::Base
5
+ configure(:port => 8001)
6
+ end
7
+
8
+ class App2 < ServerSide::Application::Base
9
+ configure(:port => 8002)
10
+ end
11
+
12
+ def test_config
13
+ assert_equal 8001, App1.configuration[:port]
14
+ assert_equal 8002, App2.configuration[:port]
15
+ end
16
+ end
@@ -0,0 +1,129 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'fileutils'
3
+
4
+ class ClusterTest < Test::Unit::TestCase
5
+ FN = Daemon::Cluster::PidFile::FN
6
+
7
+ def test_pid_fn
8
+ assert_equal 'serverside_cluster.pid', Daemon::Cluster::PidFile::FN
9
+ end
10
+
11
+ def test_pid_delete
12
+ FileUtils.touch(FN)
13
+ assert_equal true, File.file?(FN)
14
+ Daemon::Cluster::PidFile.delete
15
+ assert_equal false, File.file?(FN)
16
+ end
17
+
18
+ def test_pid_store_pid
19
+ FileUtils.rm(FN) if File.file?(FN)
20
+ Daemon::Cluster::PidFile.store_pid(1111)
21
+ assert_equal "1111\n", IO.read(FN)
22
+ Daemon::Cluster::PidFile.store_pid(2222)
23
+ assert_equal "1111\n2222\n", IO.read(FN)
24
+ end
25
+
26
+ def test_pid_recall_pids
27
+ FileUtils.rm(FN) if File.file?(FN)
28
+ assert_raise(Errno::ENOENT) {Daemon::Cluster::PidFile.recall_pids}
29
+ File.open(FN, 'w') {|f| f.puts 3333; f.puts 4444}
30
+ assert_equal [3333, 4444], Daemon::Cluster::PidFile.recall_pids
31
+
32
+ FileUtils.rm(FN)
33
+ Daemon::Cluster::PidFile.store_pid(6666)
34
+ Daemon::Cluster::PidFile.store_pid(7777)
35
+ assert_equal [6666, 7777], Daemon::Cluster::PidFile.recall_pids
36
+ end
37
+
38
+ class DummyCluster < Daemon::Cluster
39
+ FN = 'result'
40
+
41
+ def self.server_loop(port)
42
+ at_exit {File.open(FN, 'a') {|f| f.puts port}}
43
+ loop {sleep 60}
44
+ end
45
+
46
+ def self.ports
47
+ 5555..5556
48
+ end
49
+ end
50
+
51
+ def test_fork_server
52
+ FileUtils.rm(DummyCluster::FN) if File.file?(DummyCluster::FN)
53
+ pid = DummyCluster.fork_server(1111)
54
+ sleep 1
55
+ Process.kill('TERM', pid)
56
+ sleep 0.1
57
+ assert_equal true, File.file?(DummyCluster::FN)
58
+ File.open(DummyCluster::FN, 'r') do |f|
59
+ assert_equal 1111, f.gets.to_i
60
+ assert_equal true, f.eof?
61
+ end
62
+ FileUtils.rm(DummyCluster::FN) if File.file?(DummyCluster::FN)
63
+ end
64
+
65
+ def test_start_servers
66
+ FileUtils.rm(DummyCluster::FN) if File.file?(DummyCluster::FN)
67
+ DummyCluster.start_servers
68
+ sleep 0.5
69
+ pids = Daemon::Cluster::PidFile.recall_pids
70
+ assert_equal 2, pids.length
71
+ pids.each {|pid| Process.kill('TERM', pid)}
72
+ sleep 0.5
73
+ File.open(DummyCluster::FN, 'r') do |f|
74
+ p1, p2 = f.gets.to_i, f.gets.to_i
75
+ assert_equal true, DummyCluster.ports.include?(p1)
76
+ assert_equal true, DummyCluster.ports.include?(p2)
77
+ assert p1 != p2
78
+ assert_equal true, f.eof?
79
+ end
80
+ FileUtils.rm(DummyCluster::FN) if File.file?(DummyCluster::FN)
81
+ end
82
+
83
+ def test_stop_servers
84
+ DummyCluster.start_servers
85
+ sleep 0.5
86
+ pids = Daemon::Cluster::PidFile.recall_pids
87
+ DummyCluster.stop_servers
88
+ sleep 0.5
89
+ assert_equal false, File.file?(FN)
90
+ File.open(DummyCluster::FN, 'r') do |f|
91
+ p1, p2 = f.gets.to_i, f.gets.to_i
92
+ assert_equal true, DummyCluster.ports.include?(p1)
93
+ assert_equal true, DummyCluster.ports.include?(p2)
94
+ assert p1 != p2
95
+ assert_equal true, f.eof?
96
+ end
97
+ FileUtils.rm(DummyCluster::FN) if File.file?(DummyCluster::FN)
98
+ end
99
+
100
+ class DummyCluster2 < Daemon::Cluster
101
+ def self.daemon_loop
102
+ @@a = true
103
+ end
104
+
105
+ def self.start_servers
106
+ @@b = true
107
+ end
108
+
109
+ def self.stop_servers
110
+ @@c = true
111
+ end
112
+
113
+ def self.a; @@a; end
114
+ def self.b; @@b; end
115
+ def self.c; @@c; end
116
+ end
117
+
118
+ def test_start
119
+ DummyCluster2.start
120
+ assert_equal true, DummyCluster2.a
121
+ assert_equal true, DummyCluster2.b
122
+
123
+ DummyCluster2.stop
124
+ assert_equal true, DummyCluster2.c
125
+ end
126
+
127
+ def test_stop
128
+ end
129
+ end
@@ -0,0 +1,193 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'stringio'
3
+
4
+ class ConnectionTest < Test::Unit::TestCase
5
+ class DummyConnection1 < ServerSide::Connection::Base
6
+ attr_reader :count, :conn
7
+
8
+ def process
9
+ @count ||= 0
10
+ @count += 1
11
+ end
12
+ end
13
+
14
+ def test_new
15
+ r = DummyConnection1.new('hello')
16
+ assert_equal 'hello', r.conn
17
+ assert_equal 1, r.count
18
+ end
19
+
20
+ class DummyConnection
21
+ attr_reader :opened
22
+
23
+ def initialize
24
+ @opened = true
25
+ end
26
+
27
+ def close
28
+ @opened = false
29
+ end
30
+ end
31
+
32
+ class DummyConnection2 < ServerSide::Connection::Base
33
+ attr_accessor :count, :persistent
34
+
35
+ def parse_request
36
+ @count ||= 0
37
+ @count += 1
38
+ @persistent = @count < 1000
39
+ end
40
+
41
+ def respond
42
+ end
43
+ end
44
+
45
+ def test_process
46
+ c = DummyConnection.new
47
+ r = DummyConnection2.new(c)
48
+ sleep 0.2
49
+ assert_equal false, c.opened
50
+ assert_equal 1000, r.count
51
+ end
52
+
53
+ class ServerSide::Connection::Base
54
+ attr_accessor :conn, :method, :query, :version, :path, :parameters,
55
+ :headers, :persistent
56
+ end
57
+
58
+ class DummyConnection3 < ServerSide::Connection::Base
59
+ def initialize
60
+ end
61
+ end
62
+
63
+ def test_parse_request_invalid
64
+ r = DummyConnection3.new
65
+ r.conn = StringIO.new('POST /test')
66
+ assert_nil r.parse_request
67
+ r.conn = StringIO.new('invalid string')
68
+ assert_nil r.parse_request
69
+ r.conn = StringIO.new('GET HTTP/1.1')
70
+ assert_nil r.parse_request
71
+ r.conn = StringIO.new('GET /test http')
72
+ assert_nil r.parse_request
73
+ r.conn = StringIO.new('GET /test HTTP')
74
+ assert_nil r.parse_request
75
+ r.conn = StringIO.new('GET /test HTTP/')
76
+ assert_nil r.parse_request
77
+ r.conn = StringIO.new('POST /test HTTP/1.1')
78
+ assert_nil r.parse_request
79
+ end
80
+
81
+ def test_parse_request
82
+ r = DummyConnection3.new
83
+ r.conn = StringIO.new("POST /test HTTP/1.1\r\n\r\n")
84
+ assert_kind_of Hash, r.parse_request
85
+ assert_equal :post, r.method
86
+ assert_equal '/test', r.path
87
+ assert_nil r.query
88
+ assert_equal '1.1', r.version
89
+ assert_equal({}, r.parameters)
90
+ assert_equal({}, r.headers)
91
+ assert_equal true, r.persistent
92
+
93
+ # trailing slash in path
94
+ r = DummyConnection3.new
95
+ r.conn = StringIO.new("POST /test/asdf/qw/?time=24%20hours HTTP/1.1\r\n\r\n")
96
+ assert_kind_of Hash, r.parse_request
97
+ assert_equal :post, r.method
98
+ assert_equal '/test/asdf/qw', r.path
99
+ assert_equal 'time=24%20hours', r.query
100
+ assert_equal '1.1', r.version
101
+ assert_equal({:time => '24 hours'}, r.parameters)
102
+ assert_equal({}, r.headers)
103
+ assert_equal true, r.persistent
104
+
105
+ r.conn = StringIO.new("GET /cex?q=node_state HTTP/1.1\r\n\r\n")
106
+ assert_kind_of Hash, r.parse_request
107
+ assert_equal :get, r.method
108
+ assert_equal '/cex', r.path
109
+ assert_equal 'q=node_state', r.query
110
+ assert_equal({:q => 'node_state'}, r.parameters)
111
+
112
+ r.conn = StringIO.new("GET / HTTP/1.0\r\n\r\n")
113
+ assert_kind_of Hash, r.parse_request
114
+ assert_equal false, r.persistent
115
+
116
+ r.conn = StringIO.new("GET / HTTP/invalid\r\n\r\n")
117
+ assert_kind_of Hash, r.parse_request
118
+ assert_equal 'invalid', r.version
119
+ assert_equal false, r.persistent
120
+
121
+ r.conn = StringIO.new("GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
122
+ assert_kind_of Hash, r.parse_request
123
+ assert_equal '1.1', r.version
124
+ assert_equal 'close', r.headers['Connection']
125
+ assert_equal false, r.persistent
126
+ end
127
+
128
+ def test_send_response
129
+ r = DummyConnection3.new
130
+ # simple case
131
+ r.conn = StringIO.new
132
+ r.persistent = true
133
+ r.send_response(200, 'text', 'Hello there!')
134
+ r.conn.rewind
135
+ assert_equal "HTTP/1.1 200\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!",
136
+ r.conn.read
137
+
138
+ # include content-length
139
+ r.conn = StringIO.new
140
+ r.persistent = true
141
+ r.send_response(404, 'text/html', '<h1>404!</h1>', 10) # incorrect length
142
+ r.conn.rewind
143
+ assert_equal "HTTP/1.1 404\r\nContent-Type: text/html\r\nContent-Length: 10\r\n\r\n<h1>404!</h1>",
144
+ r.conn.read
145
+
146
+ # headers
147
+ r.conn = StringIO.new
148
+ r.persistent = true
149
+ r.send_response(404, 'text/html', '<h1>404!</h1>', nil, {'ETag' => 'xxyyzz'})
150
+ r.conn.rewind
151
+ assert_equal "HTTP/1.1 404\r\nContent-Type: text/html\r\nETag: xxyyzz\r\nContent-Length: 13\r\n\r\n<h1>404!</h1>",
152
+ r.conn.read
153
+
154
+ # no body
155
+ r.conn = StringIO.new
156
+ r.persistent = true
157
+ r.send_response(404, 'text/html', nil, nil, {'Set-Cookie' => 'abc=123'})
158
+ r.conn.rewind
159
+ assert_equal "HTTP/1.1 404\r\nConnection: close\r\nContent-Type: text/html\r\nSet-Cookie: abc=123\r\n\r\n",
160
+ r.conn.read
161
+ assert_equal false, r.persistent
162
+
163
+ # not persistent
164
+ r.conn = StringIO.new
165
+ r.persistent = false
166
+ r.send_response(200, 'text', 'Hello there!')
167
+ r.conn.rewind
168
+ assert_equal "HTTP/1.1 200\r\nConnection: close\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!",
169
+ r.conn.read
170
+
171
+ # socket error
172
+ r.conn = nil
173
+ r.persistent = true
174
+ assert_nothing_raised {r.send_response(200, 'text', 'Hello there!')}
175
+ assert_equal false, r.persistent
176
+ end
177
+
178
+ def test_stream
179
+ r = DummyConnection3.new
180
+ r.conn = StringIO.new
181
+ r.stream 'hey there'
182
+ r.conn.rewind
183
+ assert_equal 'hey there', r.conn.read
184
+
185
+ r.conn = StringIO.new
186
+ r.persistent = true
187
+ r.send_response(404, 'text/html', nil, nil, {'Set-Cookie' => 'abc=123'})
188
+ r.stream('hey there')
189
+ r.conn.rewind
190
+ assert_equal "HTTP/1.1 404\r\nConnection: close\r\nContent-Type: text/html\r\nSet-Cookie: abc=123\r\n\r\nhey there",
191
+ r.conn.read
192
+ end
193
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class StringTest < Test::Unit::TestCase
4
+ def test_uri_escape
5
+ assert_equal 'hello', 'hello'.uri_escape
6
+ assert_equal 'hello+world', 'hello world'.uri_escape
7
+ assert_equal 'Hello%2C+my+name+is+Inigo+Montoya%21',
8
+ 'Hello, my name is Inigo Montoya!'.uri_escape
9
+ end
10
+
11
+ def test_uri_unescape
12
+ assert_equal 'hello', 'hello'.uri_unescape
13
+ assert_equal 'hello world', 'hello+world'.uri_unescape
14
+ assert_equal 'Hello, my name is Inigo Montoya!',
15
+ 'Hello%2C+my+name+is+Inigo+Montoya%21'.uri_unescape
16
+ assert_equal '%&asdf#231&?fgs!', '%&asdf#231&?fgs!'.uri_escape.uri_unescape
17
+ end
18
+
19
+ def test_slash
20
+ assert_equal 'sharon/eylon', 'sharon'/'/eylon'
21
+ assert_equal 'sharon/122', 'sharon/'/122
22
+ assert_equal 'test/schmest', 'test'/:schmest
23
+ end
24
+ end
25
+
26
+ class SymbolTest < Test::Unit::TestCase
27
+ def test_to_s
28
+ assert_equal 'hello', :hello.to_s
29
+ assert_equal 'yow_za', :yow_za.to_s
30
+ assert_equal :yow_za.to_s.object_id, :yow_za.to_s.object_id
31
+ end
32
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class DaemonTest < Test::Unit::TestCase
4
+ class TestDaemon < Daemon::Base
5
+ def self.start
6
+ @count = 0
7
+ loop {@count += 1; sleep 0.1}
8
+ end
9
+
10
+ def self.result_fn
11
+ File.join(Daemon::WorkingDirectory, 'test.result')
12
+ end
13
+
14
+ def self.stop
15
+ File.open(result_fn, 'w') {|f| f << @count}
16
+ end
17
+ end
18
+
19
+ def teardown
20
+ Daemon.control(TestDaemon, :stop) rescue nil
21
+ end
22
+
23
+ def test_working_directory
24
+ assert_equal FileUtils.pwd, Daemon::WorkingDirectory
25
+ end
26
+
27
+ def test_pid_fn
28
+ assert_equal File.join(Daemon::WorkingDirectory, 'daemon.base.pid'),
29
+ Daemon::Base.pid_fn
30
+
31
+ assert_equal File.join(Daemon::WorkingDirectory, 'daemontest.testdaemon.pid'),
32
+ TestDaemon.pid_fn
33
+ end
34
+
35
+ def test_pid_file_store
36
+ Daemon::PidFile.store(TestDaemon, 1234)
37
+ assert_equal IO.read(TestDaemon.pid_fn).to_i, 1234
38
+ end
39
+
40
+ def test_pid_file_recall
41
+ FileUtils.rm(TestDaemon.pid_fn) rescue nil
42
+ assert_raise(RuntimeError) {Daemon::PidFile.recall(TestDaemon)}
43
+ Daemon::PidFile.store(TestDaemon, 5321)
44
+ assert_equal 5321, Daemon::PidFile.recall(TestDaemon)
45
+ end
46
+
47
+ def test_start_stop
48
+ FileUtils.rm(TestDaemon.result_fn) rescue nil
49
+ Daemon.control(TestDaemon, :start)
50
+ sleep 0.2
51
+ assert_equal true, File.file?(TestDaemon.pid_fn)
52
+ sleep 0.5
53
+ assert_nothing_raised {Daemon::PidFile.recall(TestDaemon)}
54
+ Daemon.control(TestDaemon, :stop)
55
+ sleep 0.5
56
+ assert_equal true, File.file?(TestDaemon.result_fn)
57
+ FileUtils.rm(TestDaemon.result_fn) rescue nil
58
+ end
59
+
60
+ def test_restart
61
+ FileUtils.rm(TestDaemon.result_fn) rescue nil
62
+ Daemon.control(TestDaemon, :start)
63
+ sleep 0.5
64
+ pid1 = Daemon::PidFile.recall(TestDaemon)
65
+ Daemon.control(TestDaemon, :restart)
66
+ sleep 0.5
67
+ pid2 = Daemon::PidFile.recall(TestDaemon)
68
+ assert pid1 != pid2
69
+ Daemon.control(TestDaemon, :stop)
70
+ end
71
+
72
+ def test_invalid_control_cmd
73
+ assert_raise(RuntimeError) {Daemon.control(TestDaemon, :invalid)}
74
+ end
75
+ end