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/request_spec.rb DELETED
@@ -1,288 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
- require 'stringio'
3
-
4
- class DummyRequest2 < ServerSide::HTTP::Request
5
- attr_accessor :calls, :parse_result, :persistent
6
-
7
- def parse
8
- @calls ||= []
9
- @calls << :parse
10
- @parse_result
11
- end
12
-
13
- def respond
14
- @calls ||= []
15
- @calls << :respond
16
- end
17
- end
18
-
19
- context "HTTP::Request.process" do
20
- specify "should call parse and and short-circuit if the result is nil" do
21
- r = DummyRequest2.new(nil)
22
- r.process.should_be_nil
23
- r.calls.should == [:parse]
24
-
25
- r.calls = []
26
- r.parse_result = false
27
- r.process.should_be false
28
- r.calls.should == [:parse]
29
- end
30
-
31
- specify "should follow parse with respond and return @persistent" do
32
- r = DummyRequest2.new(nil)
33
- r.parse_result = true
34
- r.process.should_be_nil
35
- r.calls.should == [:parse, :respond]
36
-
37
- r.calls = []
38
- r.persistent = 'mau'
39
- r.process.should == 'mau'
40
- r.calls.should == [:parse, :respond]
41
- end
42
- end
43
-
44
- class ServerSide::HTTP::Request
45
- attr_writer :socket, :persistent, :response_headers
46
- end
47
-
48
- context "HTTP::Request.parse" do
49
- specify "should return nil for invalid requests" do
50
- r = ServerSide::HTTP::Request.new(nil)
51
- r.socket = StringIO.new('POST /test')
52
- r.parse.should_be_nil
53
- r.socket = StringIO.new('invalid string')
54
- r.parse.should_be_nil
55
- r.socket = StringIO.new('GET HTTP/1.1')
56
- r.parse.should_be_nil
57
- r.socket = StringIO.new('GET /test http')
58
- r.parse.should_be_nil
59
- r.socket = StringIO.new('GET /test HTTP')
60
- r.parse.should_be_nil
61
- r.socket = StringIO.new('GET /test HTTP/')
62
- r.parse.should_be_nil
63
- r.socket = StringIO.new('POST /test HTTP/1.1')
64
- r.parse.should_be_nil
65
- end
66
-
67
- specify "should parse valid requests and return request headers" do
68
- r = ServerSide::HTTP::Request.new(nil)
69
- r.socket = StringIO.new(
70
- "POST /test HTTP/1.1\r\nContent-Type: text/html\r\n\r\n")
71
- r.parse.should_be r.headers
72
- r.method.should == :post
73
- r.path.should == '/test'
74
- r.query.should_be_nil
75
- r.version.should == '1.1'
76
- r.parameters.should == {}
77
- r.headers.should == {'Content-Type' => 'text/html'}
78
- r.cookies.should == {}
79
- r.response_cookies.should_be_nil
80
- r.persistent.should == true
81
- end
82
-
83
- specify "should correctly handle trailing slash in path" do
84
- r = ServerSide::HTTP::Request.new(nil)
85
- r.socket = StringIO.new("POST /test/asdf/qw/ HTTP/1.1\r\n\r\n")
86
- r.parse.should_not_be_nil
87
- r.path.should == '/test/asdf/qw'
88
-
89
- r.socket = StringIO.new(
90
- "POST /test/asdf/qw/?time=24%20hours HTTP/1.1\r\n\r\n")
91
- r.parse.should_not_be_nil
92
- r.path.should == '/test/asdf/qw'
93
- end
94
-
95
- specify "should parse URL-encoded parameters" do
96
- r = ServerSide::HTTP::Request.new(nil)
97
- r.socket = StringIO.new(
98
- "POST /test?q=node_history&time=24%20hours HTTP/1.1\r\n\r\n")
99
- r.parse.should_not_be_nil
100
- r.parameters.size.should == 2
101
- r.parameters[:time].should == '24 hours'
102
- r.parameters[:q].should == 'node_history'
103
- end
104
-
105
- specify "should correctly parse the HTTP version" do
106
- r = ServerSide::HTTP::Request.new(nil)
107
- r.socket = StringIO.new(
108
- "POST / HTTP/1.0\r\n\r\n")
109
- r.parse.should_not_be_nil
110
- r.version.should == '1.0'
111
- r.socket = StringIO.new(
112
- "POST / HTTP/3.2\r\n\r\n")
113
- r.parse.should_not_be_nil
114
- r.version.should == '3.2'
115
- end
116
-
117
- specify "should set @persistent correctly" do
118
- r = ServerSide::HTTP::Request.new(nil)
119
- r.socket = StringIO.new("POST / HTTP/1.0\r\n\r\n")
120
- r.parse.should_not_be_nil
121
- r.persistent.should == false
122
- r.socket = StringIO.new("POST / HTTP/1.1\r\n\r\n")
123
- r.parse.should_not_be_nil
124
- r.persistent.should == true
125
- r.socket = StringIO.new("POST / HTTP/0.6\r\n\r\n")
126
- r.parse.should_not_be_nil
127
- r.persistent.should == false
128
- r.socket = StringIO.new("POST / HTTP/1.1\r\nConnection: close\r\n\r\n")
129
- r.parse.should_not_be_nil
130
- r.persistent.should == false
131
- r.socket = StringIO.new("POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
132
- r.parse.should_not_be_nil
133
- r.persistent.should == true
134
- end
135
-
136
- specify "should parse cookies" do
137
- r = ServerSide::HTTP::Request.new(nil)
138
- r.socket = StringIO.new(
139
- "POST / HTTP/1.0\r\nCookie: abc=1342; def=7%2f4\r\n\r\n")
140
- r.parse.should_not_be_nil
141
- r.headers['Cookie'].should == 'abc=1342; def=7%2f4'
142
- r.cookies.size.should == 2
143
- r.cookies[:abc].should == '1342'
144
- r.cookies[:def].should == '7/4'
145
- end
146
-
147
- specify "should parse the post body" do
148
- r = ServerSide::HTTP::Request.new(nil)
149
- r.socket = StringIO.new(
150
- "POST /?q=node_history HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\ntime=24%20hours")
151
- r.parse.should_not_be_nil
152
- r.parameters.size.should == 2
153
- r.parameters[:q].should == 'node_history'
154
- r.parameters[:time].should == '24 hours'
155
- end
156
- end
157
-
158
- context "HTTP::Request.send_response" do
159
- specify "should format a response with status and body" do
160
- r = ServerSide::HTTP::Request.new(nil)
161
- r.socket = StringIO.new
162
- r.send_response(200, 'text', 'Hello there!')
163
- r.socket.rewind
164
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
165
- end
166
-
167
- specify "should format a response without connect-close when persistent" do
168
- r = ServerSide::HTTP::Request.new(nil)
169
- r.socket = StringIO.new
170
- r.persistent = true
171
- r.send_response(200, 'text', 'Hello there!')
172
- r.socket.rewind
173
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
174
- end
175
-
176
- specify "should format a response without content-length for streaming response" do
177
- r = ServerSide::HTTP::Request.new(nil)
178
- r.socket = StringIO.new
179
- r.persistent = true
180
- r.send_response(200, 'text')
181
- r.socket.rewind
182
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\n"
183
- r.stream('hey there')
184
- r.socket.rewind
185
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\nhey there"
186
- end
187
-
188
- specify "should include response_headers and headers in the response" do
189
- r = ServerSide::HTTP::Request.new(nil)
190
- r.socket = StringIO.new
191
- r.persistent = true
192
- r.response_headers['XXX'] = 'Test'
193
- r.send_response(200, 'text')
194
- r.socket.rewind
195
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nXXX: Test\r\n\r\n"
196
-
197
- r = ServerSide::HTTP::Request.new(nil)
198
- r.socket = StringIO.new
199
- r.persistent = true
200
- r.send_response(200, 'text', nil, nil, {'YYY' => 'TTT'})
201
- r.socket.rewind
202
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nYYY: TTT\r\n\r\n"
203
- end
204
-
205
- specify "should set persistent to false if exception is raised" do
206
- r = ServerSide::HTTP::Request.new(nil)
207
- r.persistent = true
208
- proc {r.send_response(200, 'text', 'Hello there!')}.should_not_raise
209
- r.persistent.should == false
210
- end
211
-
212
- specify "should include cookies in the response" do
213
- r = ServerSide::HTTP::Request.new(nil)
214
- r.socket = StringIO.new
215
- t = Time.now + 360
216
- r.set_cookie(:session, "ABCDEFG", t)
217
- r.response_cookies.should == "Set-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\n"
218
- r.send_response(200, 'text', 'Hi!')
219
- r.socket.rewind
220
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\nContent-Length: 3\r\n\r\nHi!"
221
-
222
- r = ServerSide::HTTP::Request.new(nil)
223
- r.socket = StringIO.new
224
- r.delete_cookie(:session)
225
- r.response_cookies.should == "Set-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\n"
226
- r.send_response(200, 'text', 'Hi!')
227
- r.socket.rewind
228
- r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\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!"
229
- end
230
- end
231
-
232
- context "HTTP::Request.send_file" do
233
- specify "should include a disposition header in the response" do
234
- socket = StringIO.new
235
- r = ServerSide::HTTP::Request.new(socket)
236
- r.send_file('text', 'text/plain', :attachment)
237
- socket.rewind
238
- socket.read.should_match /Content-Disposition: attachment\r\n/
239
- end
240
-
241
- specify "should use :inline as default disposition" do
242
- socket = StringIO.new
243
- r = ServerSide::HTTP::Request.new(socket)
244
- r.send_file('text', 'text/plain')
245
- socket.rewind
246
- socket.read.should_match /Content-Disposition: inline\r\n/
247
- end
248
-
249
- specify "should include filename parameter if specified" do
250
- socket = StringIO.new
251
- r = ServerSide::HTTP::Request.new(socket)
252
- r.send_file('text', 'text/plain', :attachment, 'text.txt')
253
- socket.rewind
254
- socket.read.should_match /Content-Disposition: attachment; filename=text.txt\r\n/
255
- end
256
-
257
- specify "should include a description header if specified" do
258
- socket = StringIO.new
259
- r = ServerSide::HTTP::Request.new(socket)
260
- r.send_file('text', 'text/plain', :attachment, 'text.txt')
261
- socket.rewind
262
- socket.read.should_not_match /Content-Description:\s/
263
-
264
- socket = StringIO.new
265
- r = ServerSide::HTTP::Request.new(socket)
266
- r.send_file('text', 'text/plain', :attachment, 'text.txt', 'this is text.')
267
- socket.rewind
268
- socket.read.should_match /Content-Description:\sthis is text\./
269
- end
270
- end
271
-
272
- context "HTTP::Request.redirect" do
273
- specify "should send a 302 response for temporary redirect" do
274
- r = ServerSide::HTTP::Request.new(nil)
275
- r.socket = StringIO.new
276
- r.redirect('http://mau.com/132')
277
- r.socket.rewind
278
- r.socket.read.should == "HTTP/1.1 302\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
279
- end
280
-
281
- specify "should send a 301 response for permanent redirect" do
282
- r = ServerSide::HTTP::Request.new(nil)
283
- r.socket = StringIO.new
284
- r.redirect('http://mau.com/132', true)
285
- r.socket.rewind
286
- r.socket.read.should == "HTTP/1.1 301\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
287
- end
288
- end
data/spec/routing_spec.rb DELETED
@@ -1,240 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
- require 'stringio'
3
-
4
- class ServerSide::Router
5
- attr_accessor :t, :parameters, :path
6
-
7
- def self.rules
8
- @@rules
9
- end
10
-
11
- def self.reset
12
- @@rules = []
13
- @@default_route = nil
14
- define_method(:respond) {nil}
15
- end
16
- end
17
-
18
- R = ServerSide::Router
19
-
20
- context "Router.routes_defined?" do
21
- specify "should return nil if no routes were defined" do
22
- R.reset
23
- R.routes_defined?.should_be_nil
24
- end
25
-
26
- specify "should return true if routes were defined" do
27
- R.reset
28
- R.route('/controller') {}
29
- R.routes_defined?.should_be true
30
- end
31
- end
32
-
33
- context "Router.route" do
34
- specify "should add the rule to @@rules" do
35
- l = proc {1 + 1}
36
- R.reset
37
- R.route(:path => '/t', &l)
38
- R.rules.size.should == 1
39
- R.rules[0][0].should == {:path => '/t'}
40
- R.rules[0][1].should == l
41
- end
42
-
43
- specify "should convert a string argument to a path rule" do
44
- R.reset
45
- R.route('/test') {}
46
- R.rules[0][0].should == {:path => '/test'}
47
- end
48
-
49
- specify "should convert a regexp argument to a path rule" do
50
- R.reset
51
- R.route(/abc/) {}
52
- R.rules[0][0].should == {:path => /abc/}
53
- end
54
-
55
- specify "should convert an array argument into a multiple path rule" do
56
- R.reset
57
- R.route(['/a', '/b', '/c']) {}
58
- R.rules[0][0].should == {:path => ['/a', '/b', '/c']}
59
- end
60
-
61
- specify "should store a hash argument as the rule" do
62
- R.reset
63
- R.route(:a => 'abc', :b => 'def') {}
64
- R.rules[0][0].should_be_a_kind_of Hash
65
- R.rules[0][0].size.should == 2
66
- R.rules[0][0][:a].should == 'abc'
67
- R.rules[0][0][:b].should == 'def'
68
- end
69
-
70
- specify "should unshift new rules into the rules array" do
71
- R.reset
72
- R.route('abc') {}
73
- R.route('def') {}
74
- R.route('ghi') {}
75
- R.rules.size.should == 3
76
- R.rules[0][0][:path].should == 'ghi'
77
- R.rules[1][0][:path].should == 'def'
78
- R.rules[2][0][:path].should == 'abc'
79
- end
80
-
81
- specify "should accept a proc as a rule" do
82
- R.reset
83
- l1 = proc {}
84
- l2 = proc {}
85
- R.route(l1, &l2)
86
- R.rules.size.should == 1
87
- R.rules[0][0].should_be l1
88
- R.rules[0][1].should_be l2
89
- end
90
- end
91
-
92
- context "Router.compile_rules" do
93
- specify "should compile a respond method for routing requests" do
94
- R.reset
95
- R.new(StringIO.new).respond.should_be_nil
96
- R.rules << [{:t => 'abc'}, proc{:abc}]
97
- R.rules << [{:t => 'def'}, proc{:def}]
98
- R.default_route {:default}
99
- # R.compile_rules - already called by default_route
100
- r = R.new(StringIO.new)
101
- r.t = 'abc'
102
- r.respond.should == :abc
103
- r.t = 'def'
104
- r.respond.should == :def
105
- r.t = ''
106
- r.respond.should == :default
107
- end
108
-
109
- specify "should allow handlers to give up on a request, and then pass it on." do
110
- R.reset
111
- R.default_route {:default}
112
- R.new(StringIO.new).respond.should == :default
113
- R.route('.*') {@path == '/first' ? :first : nil}
114
- R.route('.*') {@path == '/second' ? :second : nil}
115
- r = R.new(StringIO.new)
116
- r.path = '/second'
117
- r.respond.should == :second
118
- r.path = '/first'
119
- r.respond.should == :first
120
- r.path = '/other'
121
- r.respond.should == :default
122
- end
123
- end
124
-
125
- context "Router.rule_to_statement" do
126
- specify "should define procs as methods and construct a test expression" do
127
- l1 = proc {}
128
- l2 = proc {}
129
- R.rule_to_statement(l1, l2).should == "if #{l1.proc_tag} && (r = #{l2.proc_tag}); return r; end\n"
130
- r = R.new(StringIO.new)
131
- r.should_respond_to l1.proc_tag
132
- r.should_respond_to l2.proc_tag
133
- end
134
-
135
- specify "should convert hash rule with single key-value to a test expression" do
136
- l3 = proc {}
137
- s = R.rule_to_statement({:path => '/.*'}, l3)
138
- s =~ /^if \(@path =~ ([^\(]*)\)/
139
- eval("R::#{$1}").should == /\/.*/
140
- r = R.new(StringIO.new)
141
- r.should_respond_to l3.proc_tag
142
- end
143
-
144
- specify "should convert hash with multiple key-values to an OR test expression" do
145
- l4 = proc {}
146
-
147
- s = R.rule_to_statement({:path => '/controller', :host => 'static'}, l4)
148
- s.should_match /\(@path\s=~\s([^\)]+)\)/
149
- s =~ /\(@path\s=~\s([^\)]+)\)/
150
- eval("R::#{$1}").should == /\/controller/
151
- s.should_match /\(@host\s=~\s([^\)]+)\)/
152
- s =~ /\(@host\s=~\s([^\)]+)\)/
153
- eval("R::#{$1}").should == /static/
154
- r = R.new(StringIO.new)
155
- r.should_respond_to l4.proc_tag
156
- end
157
-
158
- specify "should convert hash with Array value to a test expression" do
159
- l5 = proc {}
160
- s = R.rule_to_statement({:path => ['/x', '/y']}, l5)
161
- s =~ /^if\s\(\(@path\s=~\s([^\)]*)\)\|\|\(@path\s=~\s([^\)]*)\)\)/
162
- eval("R::#{$1}").should == /\/x/
163
- eval("R::#{$2}").should == /\/y/
164
- r = R.new(StringIO.new)
165
- r.should_respond_to l5.proc_tag
166
- end
167
- end
168
-
169
- context "Router.condition part" do
170
- specify "should compile a condition expression with key and value" do
171
- s = R.condition_part(:path, 'abc')
172
- s.should_match /\(@path\s=~\s(.*)\)$/
173
- s =~ /\(@path\s=~\s(.*)\)$/
174
- eval("R::#{$1}").should == /abc/
175
- end
176
-
177
- specify "should parse parametrized value and compile it into a lambda" do
178
- s = R.condition_part(:t, ':action/:id')
179
- (s =~ /^\((.*)\)$/).should_not_be_nil
180
- tag = $1
181
- r = R.new(StringIO.new)
182
- r.should_respond_to tag
183
- r.parameters = {}
184
- r.t = 'abc'
185
- r.send(tag).should_be false
186
- r.t = 'show/16'
187
- r.send(tag).should_be true
188
- r.parameters[:action].should == 'show'
189
- r.parameters[:id].should == '16'
190
- end
191
- end
192
-
193
- context "Router.define_proc" do
194
- specify "should convert a lambda into an instance method" do
195
- l1 = proc {1 + 1}
196
- tag = R.define_proc(&l1)
197
- tag.should_be_a_kind_of Symbol
198
- tag.should == l1.proc_tag.to_sym
199
- r = R.new(StringIO.new)
200
- r.should_respond_to(tag)
201
- r.send(tag).should == 2
202
- end
203
- end
204
-
205
- context "Router.cache_constant" do
206
- specify "should cache a value as a constant inside the Router namespace" do
207
- c = rand(100000)
208
- tag = R.cache_constant(c)
209
- tag.should_be_a_kind_of String
210
- tag.should == c.const_tag
211
- eval("R::#{tag}").should == c
212
- end
213
- end
214
-
215
- context "Router.default_route" do
216
- specify "should set the default route" do
217
- R.default_route {'mau m'}
218
- R.new(StringIO.new).default_handler.should == 'mau m'
219
-
220
- R.default_route {654321}
221
- R.new(StringIO.new).default_handler.should == 654321
222
- end
223
-
224
- specify "should affect the result of routes_defined?" do
225
- R.reset
226
- R.routes_defined?.should_be_nil
227
- R.default_route {654321}
228
- R.routes_defined?.should_not_be_nil
229
- end
230
- end
231
-
232
- context "Router.unhandled" do
233
- specify "should send a 403 response" do
234
- r = R.new(StringIO.new)
235
- r.unhandled
236
- r.socket.rewind
237
- resp = r.socket.read
238
- resp.should_match /HTTP\/1.1\s403(.*)\r\n/
239
- end
240
- end
data/spec/server_spec.rb DELETED
@@ -1,40 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '../lib/serverside')
2
- require 'stringio'
3
- require 'fileutils'
4
-
5
- include ServerSide::HTTP
6
-
7
- $http_connection_created = false
8
-
9
- class Connection
10
- alias_method :orig_initialize, :initialize
11
- def initialize(socket, request_class)
12
- orig_initialize(socket, request_class)
13
- if (request_class == ServerSide::HTTP::Request) && socket.is_a?(TCPSocket)
14
- $http_connection_created = true
15
- end
16
- end
17
- end
18
-
19
- context "HTTP::Server" do
20
- specify "should open TCP port for listening" do
21
- server = Server.new('0.0.0.0', 17863, Request)
22
- t = Thread.new {server.start}
23
- proc {TCPServer.new('0.0.0.0', 17863)}.should_raise Errno::EADDRINUSE
24
- t.exit
25
- t.alive?.should_be false
26
- server.listener.close
27
- end
28
-
29
- specify "should loop indefinitely, accepting connections" do
30
- $http_connection_created = false
31
- server = Server.new('0.0.0.0', 17863, Request)
32
- t = Thread.new {server.start}
33
- sleep 0.2
34
- s = nil
35
- proc {s = TCPSocket.new('localhost', 17863)}.should_not_raise
36
- sleep 0.2
37
- $http_connection_created.should == true
38
- server.listener.close
39
- end
40
- end