serverside 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/spec/caching_spec.rb DELETED
@@ -1,318 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
- require 'stringio'
3
- include ServerSide::HTTP
4
-
5
- class DummyRequest < Request
6
- attr_accessor :socket, :persistent
7
- include Caching
8
-
9
- def initialize
10
- super(StringIO.new)
11
- @headers = {}
12
- end
13
- end
14
-
15
- context "Caching#disable_caching" do
16
- specify "should set the Cache-Control header to no-cache" do
17
- r = DummyRequest.new
18
- r.response_headers['Cache-Control'].should_be_nil
19
- r.disable_caching
20
- r.response_headers['Cache-Control'].should == 'no-cache'
21
- end
22
-
23
- specify "should remove all other cache-related headers" do
24
- r = DummyRequest.new
25
- r.response_headers['ETag'] = 'something'
26
- r.response_headers['Vary'] = 'something'
27
- r.response_headers['Expires'] = 'something'
28
- r.response_headers['Last-Modified'] = 'something'
29
- r.disable_caching
30
- r.response_headers['ETag'].should_be_nil
31
- r.response_headers['Vary'].should_be_nil
32
- r.response_headers['Expires'].should_be_nil
33
- r.response_headers['Last-Modified'].should_be_nil
34
- end
35
- end
36
-
37
- context "Caching#etag_validators" do
38
- specify "should return an empty array if no validators are present" do
39
- r = DummyRequest.new
40
- r.etag_validators.should == []
41
- end
42
-
43
- specify "should return an array containing all etag validators" do
44
- r = DummyRequest.new
45
- r.headers['If-None-Match'] = '"aaa-bbb"'
46
- r.etag_validators.should == ['aaa-bbb']
47
-
48
- r.headers['If-None-Match'] = '"aaa-bbb", "ccc-ddd"'
49
- r.etag_validators.should == ['aaa-bbb', 'ccc-ddd']
50
- end
51
-
52
- specify "should handle etags with and without quotes" do
53
- r = DummyRequest.new
54
- r.headers['If-None-Match'] = 'aaa-bbb'
55
- r.etag_validators.should == ['aaa-bbb']
56
-
57
- r.headers['If-None-Match'] = 'aaa-bbb, "ccc-ddd"'
58
- r.etag_validators.should == ['aaa-bbb', 'ccc-ddd']
59
- end
60
-
61
- specify "should handle a wildcard validator" do
62
- r = DummyRequest.new
63
- r.headers['If-None-Match'] = '*'
64
- r.etag_validators.should == ['*']
65
- end
66
- end
67
-
68
- context "Caching#valid_etag?" do
69
- specify "should return nil if no validator matches the specified etag" do
70
- r = DummyRequest.new
71
- r.valid_etag?('xxx-yyy').should_be_nil
72
-
73
- r.headers['If-None-Match'] = 'xx-yy, aaa-bbb'
74
- r.valid_etag?('xxx-yyy').should_be_nil
75
- end
76
-
77
- specify "should return true if a validator matches the specifed etag" do
78
- r = DummyRequest.new
79
-
80
- r.headers['If-None-Match'] = 'xxx-yyy'
81
- r.valid_etag?('xxx-yyy').should_be true
82
-
83
- r.headers['If-None-Match'] = '"xxx-yyy"'
84
- r.valid_etag?('xxx-yyy').should_be true
85
-
86
- r.headers['If-None-Match'] = 'aaa-bbb, xxx-yyy'
87
- r.valid_etag?('xxx-yyy').should_be true
88
-
89
- r.headers['If-None-Match'] = 'xxx-yyy, aaa-bbb'
90
- r.valid_etag?('xxx-yyy').should_be true
91
- end
92
-
93
- specify "should return true if a wildcard is included in If-None-Match" do
94
- r = DummyRequest.new
95
-
96
- r.headers['If-None-Match'] = '*'
97
- r.valid_etag?('xxx-yyy').should_be true
98
-
99
- r.headers['If-None-Match'] = 'aaa-bbb, *'
100
- r.valid_etag?('xxx-yyy').should_be true
101
- end
102
- end
103
-
104
- context "Caching#valid_etag? in expiry etag mode (no etag specified)" do
105
- specify "should return nil if no etag validator is included" do
106
- r = DummyRequest.new
107
- r.valid_etag?.should_be_nil
108
- end
109
-
110
- specify "should return true if If-None-Match includes a wildcard" do
111
- r = DummyRequest.new
112
-
113
- r.headers['If-None-Match'] = '*'
114
- r.valid_etag?.should_be true
115
- end
116
-
117
- specify "should ignore validators not formatted as expiry etags" do
118
- r = DummyRequest.new
119
-
120
- r.headers['If-None-Match'] = 'abcd'
121
- r.valid_etag?.should_be_nil
122
-
123
- r.headers['If-None-Match'] = 'xxx-yyy, zzz-zzz'
124
- r.valid_etag?.should_be_nil
125
- end
126
-
127
- specify "should parse expiry etags and check the expiration stamp" do
128
- r = DummyRequest.new
129
- t = Time.now
130
- fmt = Caching::EXPIRY_ETAG_FORMAT
131
-
132
- r.headers['If-None-Match'] = fmt % [t.to_i, (t - 20).to_i]
133
- r.valid_etag?.should_be_nil
134
-
135
- r.headers['If-None-Match'] = fmt % [t.to_i, (t + 20).to_i]
136
- r.valid_etag?.should_be true
137
-
138
- r.headers['If-None-Match'] = "xxx-yyy, #{fmt % [t.to_i, (t + 20).to_i]}, #{fmt % [t.to_i, (t - 20).to_i]}"
139
- r.valid_etag?.should_be true
140
- end
141
- end
142
-
143
- context "Caching#expiry_etag" do
144
- specify "should return an expiry etag with the stamp and expiration time" do
145
- r = DummyRequest.new
146
-
147
- t = Time.now
148
- fmt = Caching::EXPIRY_ETAG_FORMAT
149
- max_age = 54321
150
-
151
- r.expiry_etag(t, max_age).should == (fmt % [t.to_i, (t + max_age).to_i])
152
- end
153
- end
154
-
155
- context "Caching#valid_stamp?" do
156
- specify "should return nil if no If-Modified-Since header is included" do
157
- r = DummyRequest.new
158
- r.valid_stamp?(Time.now).should_be_nil
159
- end
160
-
161
- specify "should return nil if the If-Modified-Since header is different than the specified stamp" do
162
- t = Time.now
163
- r = DummyRequest.new
164
- r.headers['If-Modified-Since'] = t.httpdate
165
- r.valid_stamp?(t + 1).should_be_nil
166
- r.valid_stamp?(t - 1).should_be_nil
167
- end
168
-
169
- specify "should return true if the If-Modified-Since header matches the specified stamp" do
170
- t = Time.now
171
- r = DummyRequest.new
172
- r.headers['If-Modified-Since'] = t.httpdate
173
- r.valid_stamp?(t).should_be true
174
- end
175
- end
176
-
177
- context "Caching#validate_cache" do
178
- specify "should return nil if no validators are present" do
179
- r = DummyRequest.new
180
- r.validate_cache(Time.now, 360).should_be_nil
181
- end
182
-
183
- specify "should check for a stamp validator" do
184
- r = DummyRequest.new
185
- t = Time.now
186
-
187
- r.headers['If-Modified-Since'] = t.httpdate
188
- r.validate_cache(t + 1, 360).should_be_nil
189
- r.validate_cache(t - 1, 360).should_be_nil
190
- r.validate_cache(t, 360).should_be true
191
- end
192
-
193
- specify "should check for an etag validator" do
194
- r = DummyRequest.new
195
- t = Time.now
196
- etag = 'abcdef'
197
-
198
- r.validate_cache(t, 360, etag).should_be_nil
199
- r.headers['If-None-Match'] = 'aaa-bbb'
200
- r.validate_cache(t, 360, etag).should_be_nil
201
- r.headers['If-None-Match'] = "aaa-bbb, #{etag}"
202
- r.validate_cache(t, 360, etag).should_be true
203
- r.headers['If-None-Match'] = '*'
204
- r.validate_cache(t, 360, etag).should_be true
205
- end
206
-
207
- specify "should check for an expiry etag validator if etag is unspecified" do
208
- r = DummyRequest.new
209
- t = Time.now
210
- fmt = Caching::EXPIRY_ETAG_FORMAT
211
-
212
- r.headers['If-None-Match'] = 'aaa-bbb'
213
- r.validate_cache(t, 360).should_be_nil
214
- r.headers['If-None-Match'] = "aaa-bbb, #{fmt % [t.to_i, (t + 20).to_i]}"
215
- r.validate_cache(t, 360).should_be true
216
- r.headers['If-None-Match'] = '*'
217
- r.validate_cache(t, 360).should_be true
218
- end
219
-
220
- specify "should set the response headers with caching info if request did not validate" do
221
- r = DummyRequest.new
222
- t = Time.now
223
- r.validate_cache(t, 360, 'aaa-bbb', :public, 'Cookie')
224
- r.response_headers['ETag'].should == '"aaa-bbb"'
225
- r.response_headers['Last-Modified'].should == t.httpdate
226
- r.response_headers['Expires'].should == ((t + 360).httpdate)
227
- r.response_headers['Cache-Control'].should == :public
228
- r.response_headers['Vary'].should == 'Cookie'
229
- end
230
-
231
- specify "should set an expiry etag if no etag is specified" do
232
- r = DummyRequest.new
233
- t = Time.now
234
- fmt = Caching::EXPIRY_ETAG_FORMAT
235
- r.validate_cache(t, 360)
236
- r.response_headers['ETag'].should == (
237
- "\"#{fmt % [t.to_i, (t + 360).to_i]}\"")
238
- end
239
-
240
- specify "should send a 304 response if the cache validates" do
241
- r = DummyRequest.new
242
- t = Time.now
243
- fmt = Caching::EXPIRY_ETAG_FORMAT
244
-
245
- r.headers['If-None-Match'] = "aaa-bbb, #{fmt % [t.to_i, (t + 20).to_i]}"
246
- r.validate_cache(t, 360).should_be true
247
- r.socket.rewind
248
- resp = r.socket.read
249
- resp.should_match /^HTTP\/1.1 304 Not Modified\r\n/
250
-
251
- r = DummyRequest.new
252
- t = Time.now
253
-
254
- r.headers['If-Modified-Since'] = t.httpdate
255
- r.validate_cache(t, 360).should_be true
256
- r.socket.rewind
257
- resp = r.socket.read
258
- resp.should_match /^HTTP\/1.1 304 Not Modified\r\n/
259
- end
260
-
261
- specify "should not send anything if the cache doesn't validate" do
262
- r = DummyRequest.new
263
- t = Time.now
264
-
265
- r.validate_cache(t, 360).should_be_nil
266
- r.socket.rewind
267
- resp = r.socket.read
268
- resp.should_be_empty
269
- end
270
-
271
- specify "should not execute the given block if the cache validates" do
272
- r = DummyRequest.new
273
- t = Time.now
274
- r.headers['If-Modified-Since'] = t.httpdate
275
- proc {r.validate_cache(t, 360) {raise}}.should_not_raise
276
- end
277
-
278
- specify "should return the result of the given block if the cache doesn't validate" do
279
- x = nil
280
- l = proc {x = :executed}
281
-
282
- r = DummyRequest.new
283
- t = Time.now
284
- r.validate_cache(t, 360, &l).should == :executed
285
- x.should == :executed
286
- end
287
- end
288
-
289
- context "Caching#send_not_modified_response" do
290
- specify "should render a 304 response" do
291
- r = DummyRequest.new
292
- r.send_not_modified_response
293
- r.socket.rewind
294
- resp = r.socket.read
295
- resp.should_match /^HTTP\/1.1 304 Not Modified\r\n/
296
- resp.should_match /Content-Length: 0\r\n/
297
- resp.should_match /\r\n\r\n$/ # empty response body
298
- end
299
-
300
- specify "should exclude Connection header if persistent" do
301
- r = DummyRequest.new
302
- r.persistent = true
303
- r.send_not_modified_response
304
- r.socket.rewind
305
- resp = r.socket.read
306
- resp.should_not_match /Connection: close\r\n/
307
- end
308
-
309
- specify "should include Connection header if persistent" do
310
- r = DummyRequest.new
311
- r.persistent = false
312
- r.send_not_modified_response
313
- r.socket.rewind
314
- resp = r.socket.read
315
- resp.should_match /Connection: close\r\n/
316
- end
317
- end
318
-
data/spec/cluster_spec.rb DELETED
@@ -1,140 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
-
3
- __END__
4
-
5
- context "Daemon::Cluster::PidFile" do
6
- setup do
7
- @fn = Daemon::Cluster::PidFile::FN
8
- end
9
-
10
- specify "::FN should be the cluster's pid file" do
11
- Daemon::Cluster::PidFile::FN.should == 'serverside_cluster.pid'
12
- end
13
-
14
- specify "should delete the cluster's pid file" do
15
- FileUtils.touch(@fn)
16
- File.file?(@fn).should == true
17
- Daemon::Cluster::PidFile.delete
18
- File.file?(@fn).should == false
19
- end
20
-
21
- specify "should store multiple pids" do
22
- Daemon::Cluster::PidFile.delete
23
- Daemon::Cluster::PidFile.store_pid(1111)
24
- IO.read(@fn).should == "1111\n"
25
- Daemon::Cluster::PidFile.store_pid(2222)
26
- IO.read(@fn).should == "1111\n2222\n"
27
- end
28
-
29
- def test_pid_recall_pids
30
- Daemon::Cluster::PidFile.delete
31
- proc {Daemon::Cluster::PidFile.recall_pids}.should_raise Errno::ENOENT
32
- File.open(@fn, 'w') {|f| f.puts 3333; f.puts 4444}
33
- Daemon::Cluster::PidFile.recall_pids.should == [3333, 4444]
34
-
35
- FileUtils.rm(@fn)
36
- Daemon::Cluster::PidFile.store_pid(6666)
37
- Daemon::Cluster::PidFile.store_pid(7777)
38
- Daemon::Cluster::PidFile.recall_pids.should == [6666, 7777]
39
- end
40
- end
41
-
42
- class DummyCluster < Daemon::Cluster
43
- FN = 'result'
44
-
45
- def self.server_loop(port)
46
- at_exit {File.open(FN, 'a') {|f| f.puts port}}
47
- loop {sleep 10}
48
- end
49
-
50
- def self.ports
51
- 5555..5556
52
- end
53
- end
54
-
55
- context "Cluster.fork_server" do
56
- specify "should fork a server on the specified port" do
57
- FileUtils.rm(DummyCluster::FN) rescue nil
58
- port = rand(5_000)
59
- pid = DummyCluster.fork_server(port)
60
- sleep 1
61
- Process.kill('TERM', pid)
62
- sleep 0.1
63
- File.file?(DummyCluster::FN).should == true
64
- File.open(DummyCluster::FN, 'r') do |f|
65
- f.gets.to_i.should == port
66
- f.eof?.should == true
67
- end
68
- FileUtils.rm(DummyCluster::FN) rescue nil
69
- end
70
- end
71
-
72
- context "Cluster.start_servers" do
73
- specify "should start a cluster of servers" do
74
- FileUtils.rm(DummyCluster::FN) rescue nil
75
- DummyCluster.start_servers
76
- sleep 0.5
77
- pids = Daemon::Cluster::PidFile.recall_pids
78
- pids.length.should == 2
79
- pids.each {|pid| Process.kill('TERM', pid)}
80
- sleep 0.5
81
- File.open(DummyCluster::FN, 'r') do |f|
82
- p1, p2 = f.gets.to_i, f.gets.to_i
83
- DummyCluster.ports.include?(p1).should == true
84
- DummyCluster.ports.include?(p2).should == true
85
- p1.should_not == p2
86
- f.eof?.should == true
87
- end
88
- FileUtils.rm(DummyCluster::FN) rescue nil
89
- end
90
- end
91
-
92
- context "Cluster.stop_servers" do
93
- specify "should stop the cluster of servers" do
94
- DummyCluster.start_servers
95
- sleep 0.5
96
- pids = Daemon::Cluster::PidFile.recall_pids
97
- DummyCluster.stop_servers
98
- sleep 0.5
99
- File.file?(Daemon::Cluster::PidFile::FN).should == false
100
- File.open(DummyCluster::FN, 'r') do |f|
101
- p1, p2 = f.gets.to_i, f.gets.to_i
102
- DummyCluster.ports.include?(p1).should == true
103
- DummyCluster.ports.include?(p2).should == true
104
- p1.should_not == p2
105
- f.eof?.should == true
106
- end
107
- FileUtils.rm(DummyCluster::FN) rescue nil
108
- end
109
- end
110
-
111
- class DummyCluster2 < Daemon::Cluster
112
- def self.daemon_loop
113
- @@a = true
114
- end
115
-
116
- def self.start_servers
117
- @@b = true
118
- end
119
-
120
- def self.stop_servers
121
- @@c = true
122
- end
123
-
124
- def self.a; @@a; end
125
- def self.b; @@b; end
126
- def self.c; @@c; end
127
- end
128
-
129
- context "Cluster.start and stop" do
130
- specify "should start and stop the cluster daemon" do
131
- DummyCluster2.start
132
- DummyCluster2.a.should == true
133
- DummyCluster2.b.should == true
134
-
135
- proc {DummyCluster2.c}.should_raise
136
- DummyCluster2.stop
137
- DummyCluster2.c.should == true
138
- end
139
- end
140
-
@@ -1,59 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
-
3
- class ServerSide::HTTP::Connection
4
- attr_reader :socket, :request_class, :thread
5
- end
6
-
7
- $pause_request = false
8
-
9
- class DummyRequest1 < ServerSide::HTTP::Request
10
- @@instance_count = 0
11
-
12
- def initialize(socket)
13
- @@instance_count += 1
14
- super(socket)
15
- end
16
-
17
- def self.instance_count
18
- @@instance_count
19
- end
20
-
21
- def process
22
- sleep 0.1 while $pause_request
23
-
24
- @socket[:count] ||= 0
25
- @socket[:count] += 1
26
- @socket[:count] < 1000
27
- end
28
- end
29
-
30
- class DummySocket < Hash
31
- attr_accessor :closed
32
- def close; @closed = true; end
33
- end
34
-
35
- include ServerSide::HTTP
36
-
37
- context "Connection.initialize" do
38
- specify "should take two parameters: socket and request_class" do
39
- proc {Connection.new}.should_raise ArgumentError
40
- proc {Connection.new(nil)}.should_raise ArgumentError
41
- s = 'socket'
42
- r = 'request_class'
43
- c = Connection.new(s, r)
44
- c.socket.should_be s
45
- c.request_class.should_be r
46
- end
47
-
48
- specify "should spawn a thread that invokes Connection.process" do
49
- $pause_request = true
50
- c = Connection.new(DummySocket.new, DummyRequest1)
51
- c.thread.should_be_an_instance_of Thread
52
- c.thread.alive?.should == true
53
- DummyRequest1.instance_count.should == 1
54
- $pause_request = false
55
- sleep 0.1 while c.thread.alive?
56
- DummyRequest1.instance_count.should == 1000
57
- end
58
- end
59
-
@@ -1,142 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
- require 'stringio'
3
-
4
- class ServerSide::Router
5
- def self.rules
6
- @@rules
7
- end
8
-
9
- def self.reset_rules
10
- @@rules = []
11
- end
12
-
13
- attr_accessor :path
14
- end
15
-
16
- context "ServerSide::Controller.mount" do
17
- specify "should accept a routing rule as argument" do
18
- proc {ServerSide::Controller.mount}.should_raise ArgumentError
19
- end
20
-
21
- specify "should return a subclass of ServerSide::Controller" do
22
- c = ServerSide::Controller.mount(:path => '/test')
23
- c.should_be_a_kind_of Class
24
- c.superclass.should_be ServerSide::Controller
25
- end
26
-
27
- specify "should add a routing rule using ServerSide::Router.route" do
28
- ServerSide::Router.reset_rules
29
- rule = {:path => '/test'}
30
- c = ServerSide::Controller.mount(rule)
31
- sub_class = Class.new(c)
32
- r = ServerSide::Router.rules.first
33
- r.first.should == rule
34
- r.last.should_be_a_kind_of Proc
35
- c.module_eval do
36
- define_method(:initialize) {|req| $req = req}
37
- end
38
- res = r.last.call
39
- res.should_be_a_kind_of sub_class
40
-
41
- r = ServerSide::Router.new(StringIO.new)
42
- r.path = '/test'
43
- r.respond
44
- $req.should_be_a_kind_of ServerSide::Router
45
- end
46
-
47
- specify "should accept either an argument or block as the rule" do
48
- ServerSide::Router.reset_rules
49
- rule = {:path => '/test'}
50
- c = Class.new(ServerSide::Controller.mount(rule))
51
- r = ServerSide::Router.rules.first
52
- r.first.should_be rule
53
-
54
- ServerSide::Router.reset_rules
55
- rule = proc {true}
56
- c = Class.new(ServerSide::Controller.mount(&rule))
57
- r = ServerSide::Router.rules.first
58
- r.first.should_be rule
59
- end
60
- end
61
-
62
- class ServerSide::Controller
63
- attr_accessor :request, :path, :parameters, :rendered
64
- end
65
-
66
- class ServerSide::HTTP::Request
67
- attr_accessor :path, :parameters
68
- end
69
-
70
- require 'metaid'
71
-
72
- class DummyController < ServerSide::Controller
73
- attr_reader :response_called
74
-
75
- def response
76
- @response_called = true
77
- end
78
-
79
- def render_default
80
- @rendered = :default
81
- end
82
- end
83
-
84
- context "ServerSide::Controller new instance" do
85
- specify "should set @request, @path, and @parameters instance variables" do
86
- req = ServerSide::HTTP::Request.new(StringIO.new)
87
- req.path = '/aa/bb/cc'
88
- req.parameters = {:q => 'node_state', :f => 'xml'}
89
- c = ServerSide::Controller.new(req)
90
- c.request.should_be req
91
- c.path.should_be req.path
92
- c.parameters.should_be req.parameters
93
- end
94
-
95
- specify "should invoke the response method" do
96
- req = ServerSide::HTTP::Request.new(StringIO.new)
97
- c = DummyController.new(req)
98
- c.response_called.should_be true
99
- end
100
-
101
- specify "should invoke render_default unless @rendered" do
102
- req = ServerSide::HTTP::Request.new(StringIO.new)
103
- c = DummyController.new(req)
104
- c.rendered.should_be :default
105
-
106
- c_class = Class.new(DummyController) do
107
- define_method(:response) {@rendered = true}
108
- end
109
- c = c_class.new(req)
110
- c.rendered.should_be true
111
- end
112
- end
113
-
114
- context "ServerSide::Controller.render_default" do
115
- specify "should render a default 200 response" do
116
- req = ServerSide::HTTP::Request.new(StringIO.new)
117
- c = ServerSide::Controller.new(req)
118
- req.socket.rewind
119
- resp = req.socket.read
120
- resp.should_match /HTTP\/1\.1\s200/
121
- end
122
- end
123
-
124
- context "ServerSide::Controller.render" do
125
- specify "should render a 200 response with body and content type arguments" do
126
- req = ServerSide::HTTP::Request.new(StringIO.new)
127
- c = ServerSide::Controller.new(req)
128
- c.render('hello world', 'text/plain')
129
- req.socket.rewind
130
- resp = req.socket.read
131
- resp.should_match /Content-Type:\stext\/plain\r\n/
132
- end
133
-
134
- specify "should set @rendered to true" do
135
- req = ServerSide::HTTP::Request.new(StringIO.new)
136
- c = ServerSide::Controller.new(req)
137
- c.render('hello world', 'text/plain')
138
- c.rendered.should == true
139
- end
140
- end
141
-
142
-