serverside 0.1.59

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 (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