serverside 0.2.5 → 0.2.6
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/CHANGELOG +10 -0
- data/README +3 -3
- data/Rakefile +4 -4
- data/bin/serverside +4 -4
- data/doc/rdoc/classes/Daemon.html +4 -4
- data/doc/rdoc/classes/ServerSide.html +14 -74
- data/doc/rdoc/classes/ServerSide/Application.html +13 -13
- data/doc/rdoc/classes/ServerSide/{Connection.html → HTTP.html} +12 -18
- data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +200 -0
- data/doc/rdoc/classes/ServerSide/{Connection → HTTP}/Const.html +6 -6
- data/doc/rdoc/classes/ServerSide/{Connection/Base.html → HTTP/Request.html} +213 -164
- data/doc/rdoc/classes/ServerSide/{Server.html → HTTP/Server.html} +22 -22
- data/doc/rdoc/classes/ServerSide/Router.html +496 -0
- data/doc/rdoc/classes/ServerSide/StaticFiles.html +31 -30
- data/doc/rdoc/classes/ServerSide/Template.html +12 -12
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/CHANGELOG.html +22 -4
- data/doc/rdoc/files/COPYING.html +1 -1
- data/doc/rdoc/files/README.html +4 -4
- data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/cluster_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/core_ext_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/daemon_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/request_rb.html +101 -0
- data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/static_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/template_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside_rb.html +1 -1
- data/doc/rdoc/fr_class_index.html +6 -5
- data/doc/rdoc/fr_file_index.html +1 -0
- data/doc/rdoc/fr_method_index.html +31 -31
- data/lib/serverside/application.rb +1 -1
- data/lib/serverside/connection.rb +12 -141
- data/lib/serverside/request.rb +150 -0
- data/lib/serverside/routing.rb +117 -126
- data/lib/serverside/server.rb +13 -12
- data/lib/serverside/static.rb +1 -1
- data/test/functional/primitive_static_server_test.rb +3 -3
- data/test/functional/routing_server.rb +1 -1
- data/test/functional/routing_server_test.rb +1 -1
- data/test/functional/static_profile.rb +1 -1
- data/test/functional/static_server_test.rb +2 -2
- data/test/unit/connection_test.rb +30 -224
- data/test/unit/request_test.rb +248 -0
- data/test/unit/routing_test.rb +4 -4
- data/test/unit/server_test.rb +10 -8
- data/test/unit/static_test.rb +1 -1
- metadata +61 -56
- data/doc/rdoc/classes/ServerSide/Connection/Router.html +0 -493
data/lib/serverside/routing.rb
CHANGED
@@ -1,140 +1,131 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'request')
|
2
|
+
|
1
3
|
module ServerSide
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
4
|
+
# The Router class defines a kind of connection that can route requests
|
5
|
+
# to different handlers based on rules that can be specified either by
|
6
|
+
# lambdas or by hashes that contain variable names corresponding to patterns.
|
7
|
+
#
|
8
|
+
# The simplest form of a routing rule specifies a path pattern:
|
9
|
+
#
|
10
|
+
# ServerSide.route('/static') {serve_static('.'/@path)}
|
11
|
+
#
|
12
|
+
# But you can also check for other attributes of the request:
|
13
|
+
#
|
14
|
+
# ServerSide.route(:path => '/static', :host => '^:subdomain\.mydomain') {
|
15
|
+
# serve_static(@parameters[:subdomain]/@path)
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# It also possible to pass a lambda as a rule:
|
19
|
+
#
|
20
|
+
# ServerSide.route(lambda {@headers['Agent'] =~ /Moz/}) {serve_static('moz'/@path)}
|
21
|
+
#
|
22
|
+
# Routing rules are evaluated in backwards, so the rules should be ordered
|
23
|
+
# from the general to the specific.
|
24
|
+
class Router < HTTP::Request
|
25
|
+
# Returns true if routes were defined.
|
26
|
+
def self.has_routes?
|
27
|
+
@@rules && !@@rules.empty? rescue false
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds a routing rule. The normalized rule is a hash containing keys (acting
|
31
|
+
# as instance variable names) with patterns as values. If the rule is not a
|
32
|
+
# hash, it is normalized into a pattern checked against the request path.
|
33
|
+
# Pattern values can also be arrays, any member of which is checked as a
|
34
|
+
# pattern. The rule can also be a Proc or lambda which is run with the
|
35
|
+
# connection object's binding. A contrived example:
|
20
36
|
#
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
# Converts a rule into an if statement. All keys in the rule are matched
|
54
|
-
# against their respective values.
|
55
|
-
def self.rule_to_statement(rule, block)
|
56
|
-
proc_tag = define_proc(&block)
|
57
|
-
if Proc === rule
|
58
|
-
cond = define_proc(&rule).to_s
|
59
|
-
else
|
60
|
-
cond = rule.to_a.map {|kv|
|
61
|
-
if Array === kv[1]
|
62
|
-
'(' + kv[1].map {|v| condition_part(kv[0], v)}.join('||') + ')'
|
63
|
-
else
|
64
|
-
condition_part(kv[0], kv[1])
|
65
|
-
end
|
66
|
-
}.join('&&')
|
67
|
-
end
|
68
|
-
"return #{proc_tag} if #{cond}\n"
|
37
|
+
# ServerSide.route(lambda{path = 'mypage'}) {serve_static('mypage.html')}
|
38
|
+
def self.route(rule, &block)
|
39
|
+
@@rules ||= []
|
40
|
+
rule = {:path => rule} unless (Hash === rule) || (Proc === rule)
|
41
|
+
@@rules.unshift [rule, block]
|
42
|
+
compile_rules
|
43
|
+
end
|
44
|
+
|
45
|
+
# Compiles all rules into a respond method that is invoked when a request
|
46
|
+
# is received.
|
47
|
+
def self.compile_rules
|
48
|
+
@@rules ||= []
|
49
|
+
code = @@rules.inject('lambda {') {|m, r| m << rule_to_statement(r[0], r[1])}
|
50
|
+
code << 'default_handler}'
|
51
|
+
define_method(:respond, &eval(code))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Converts a rule into an if statement. All keys in the rule are matched
|
55
|
+
# against their respective values.
|
56
|
+
def self.rule_to_statement(rule, block)
|
57
|
+
proc_tag = define_proc(&block)
|
58
|
+
if Proc === rule
|
59
|
+
cond = define_proc(&rule).to_s
|
60
|
+
else
|
61
|
+
cond = rule.to_a.map {|kv|
|
62
|
+
if Array === kv[1]
|
63
|
+
'(' + kv[1].map {|v| condition_part(kv[0], v)}.join('||') + ')'
|
64
|
+
else
|
65
|
+
condition_part(kv[0], kv[1])
|
66
|
+
end
|
67
|
+
}.join('&&')
|
69
68
|
end
|
69
|
+
"return #{proc_tag} if #{cond}\n"
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
72
|
+
# Pattern for finding parameters inside patterns. Parameters are parts of the
|
73
|
+
# pattern, which the routing pre-processor turns into sub-regexp that are
|
74
|
+
# used to extract parameter values from the pattern.
|
75
|
+
#
|
76
|
+
# For example, matching '/controller/show' against '/controller/:action' will
|
77
|
+
# give us @parameters[:action] #=> "show"
|
78
|
+
ParamRegexp = /(?::([a-z]+))/
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
cond = "(@#{key} =~ #{cache_constant(Regexp.new(value))})"
|
93
|
-
if p_count == 0
|
94
|
-
cond
|
95
|
-
else
|
96
|
-
tag = define_proc(&eval(
|
97
|
-
"lambda {if #{cond}\n#{p_parse}true\nelse\nfalse\nend}"))
|
98
|
-
"(#{tag})"
|
99
|
-
end
|
80
|
+
# Returns the condition part for the key and value specified. The key is the
|
81
|
+
# name of an instance variable and the value is a pattern to match against.
|
82
|
+
# If the pattern contains parameters (for example, /controller/:action,) the
|
83
|
+
# method creates a lambda for extracting the parameter values.
|
84
|
+
def self.condition_part(key, value)
|
85
|
+
p_parse, p_count = '', 0
|
86
|
+
while (String === value) && (value =~ ParamRegexp)
|
87
|
+
value = value.dup
|
88
|
+
p_name = $1
|
89
|
+
p_count += 1
|
90
|
+
value.sub!(ParamRegexp, '(.+)')
|
91
|
+
p_parse << "@parameters[:#{p_name}] = $#{p_count}\n"
|
100
92
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
93
|
+
cond = "(@#{key} =~ #{cache_constant(Regexp.new(value))})"
|
94
|
+
if p_count == 0
|
95
|
+
cond
|
96
|
+
else
|
97
|
+
tag = define_proc(&eval(
|
98
|
+
"lambda {if #{cond}\n#{p_parse}true\nelse\nfalse\nend}"))
|
99
|
+
"(#{tag})"
|
107
100
|
end
|
101
|
+
end
|
108
102
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
103
|
+
# Converts a proc into a method, returning the method's name (as a symbol)
|
104
|
+
def self.define_proc(&block)
|
105
|
+
tag = block.proc_tag
|
106
|
+
define_method(tag.to_sym, &block) unless instance_methods.include?(tag)
|
107
|
+
tag.to_sym
|
108
|
+
end
|
116
109
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
110
|
+
# Converts a value into a local constant and freezes it. Returns the
|
111
|
+
# constant's tag name
|
112
|
+
def self.cache_constant(value)
|
113
|
+
tag = value.const_tag
|
114
|
+
class_eval "#{tag} = #{value.inspect}.freeze" rescue nil
|
115
|
+
tag
|
116
|
+
end
|
122
117
|
|
123
|
-
|
124
|
-
|
125
|
-
|
118
|
+
# Sets the default handler for incoming requests.
|
119
|
+
def self.route_default(&block)
|
120
|
+
define_method(:default_handler, &block)
|
121
|
+
compile_rules
|
122
|
+
end
|
126
123
|
|
127
|
-
|
124
|
+
# Generic responder for unhandled requests.
|
125
|
+
def unhandled
|
126
|
+
send_response(403, 'text', 'No handler found.')
|
128
127
|
end
|
129
|
-
|
130
|
-
|
131
|
-
# Adds a routing rule. This is a convenience method.
|
132
|
-
def self.route(rule, &block)
|
133
|
-
Connection::Router.route(rule, &block)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Sets the default request handler. This is a convenience method.
|
137
|
-
def self.route_default(&block)
|
138
|
-
Connection::Router.route_default(&block)
|
128
|
+
|
129
|
+
alias_method :default_handler, :unhandled
|
139
130
|
end
|
140
131
|
end
|
data/lib/serverside/server.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
module ServerSide
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
module HTTP
|
5
|
+
# The ServerSide HTTP server is designed to be fast and simple. It is also
|
6
|
+
# designed to support both HTTP 1.1 persistent connections, and HTTP streaming
|
7
|
+
# for applications which use Comet techniques.
|
8
|
+
class Server
|
9
|
+
# Creates a new server by opening a listening socket and starting an accept
|
10
|
+
# loop. When a new connection is accepted, a new instance of the
|
11
|
+
# supplied connection class is instantiated and passed the connection for
|
12
|
+
# processing.
|
13
|
+
def initialize(host, port, request_class)
|
14
|
+
@server = TCPServer.new(host, port)
|
15
|
+
loop {Connection.new(@server.accept, request_class)}
|
16
|
+
end
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
data/lib/serverside/static.rb
CHANGED
@@ -3,7 +3,7 @@ require 'net/http'
|
|
3
3
|
|
4
4
|
class StaticServerTest < Test::Unit::TestCase
|
5
5
|
|
6
|
-
class
|
6
|
+
class StaticRequest < ServerSide::HTTP::Request
|
7
7
|
def respond
|
8
8
|
begin
|
9
9
|
# set_cookie(:hello, 'world', Time.now + 30) if @cookies[:test] == 'hello'
|
@@ -21,7 +21,7 @@ class StaticServerTest < Test::Unit::TestCase
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_basic
|
24
|
-
@t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654,
|
24
|
+
@t = Thread.new {ServerSide::HTTP::Server.new('0.0.0.0', 17654, StaticRequest)}
|
25
25
|
sleep 0.1
|
26
26
|
|
27
27
|
h = Net::HTTP.new('localhost', 17654)
|
@@ -40,7 +40,7 @@ class StaticServerTest < Test::Unit::TestCase
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def test_cookies
|
43
|
-
@t = Thread.new {ServerSide::Server.new('0.0.0.0', 17655,
|
43
|
+
@t = Thread.new {ServerSide::HTTP::Server.new('0.0.0.0', 17655, StaticRequest)}
|
44
44
|
sleep 0.1
|
45
45
|
|
46
46
|
h = Net::HTTP.new('localhost', 17655)
|
@@ -9,7 +9,7 @@ class StaticServerTest < Test::Unit::TestCase
|
|
9
9
|
redirect('http://feeds.feedburner.com/RobbyOnRails')
|
10
10
|
end
|
11
11
|
|
12
|
-
t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654, ServerSide::
|
12
|
+
t = Thread.new {ServerSide::HTTP::Server.new('0.0.0.0', 17654, ServerSide::Router)}
|
13
13
|
sleep 0.1
|
14
14
|
|
15
15
|
h = Net::HTTP.new('localhost', 17654)
|
@@ -3,14 +3,14 @@ require 'net/http'
|
|
3
3
|
|
4
4
|
class StaticServerTest < Test::Unit::TestCase
|
5
5
|
|
6
|
-
class
|
6
|
+
class StaticRequest < ServerSide::HTTP::Request
|
7
7
|
def respond
|
8
8
|
serve_static('.'/@path)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_basic
|
13
|
-
t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654,
|
13
|
+
t = Thread.new {ServerSide::HTTP::Server.new('0.0.0.0', 17654, StaticRequest)}
|
14
14
|
sleep 0.1
|
15
15
|
|
16
16
|
h = Net::HTTP.new('localhost', 17654)
|
@@ -2,241 +2,47 @@ require File.dirname(__FILE__) + '/../test_helper'
|
|
2
2
|
require 'stringio'
|
3
3
|
|
4
4
|
class ConnectionTest < Test::Unit::TestCase
|
5
|
-
class
|
6
|
-
attr_reader :
|
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
|
5
|
+
class ServerSide::HTTP::Connection
|
6
|
+
attr_reader :conn, :request_class, :thread
|
18
7
|
end
|
19
8
|
|
20
|
-
class
|
21
|
-
|
22
|
-
|
23
|
-
def initialize
|
24
|
-
@opened = true
|
25
|
-
end
|
9
|
+
class DummyRequest1 < ServerSide::HTTP::Request
|
10
|
+
@@instance_count = 0
|
26
11
|
|
27
|
-
def
|
28
|
-
|
12
|
+
def initialize(conn)
|
13
|
+
@@instance_count += 1
|
14
|
+
super(conn)
|
29
15
|
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class DummyConnection2 < ServerSide::Connection::Base
|
33
|
-
attr_accessor :count, :persistent
|
34
16
|
|
35
|
-
def
|
36
|
-
|
37
|
-
@count += 1
|
38
|
-
@persistent = @count < 1000
|
17
|
+
def self.instance_count
|
18
|
+
@@instance_count
|
39
19
|
end
|
40
20
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
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, :cookies, :response_cookies
|
56
|
-
end
|
57
|
-
|
58
|
-
class DummyConnection3 < ServerSide::Connection::Base
|
59
|
-
def initialize
|
21
|
+
def process
|
22
|
+
@conn[:count] ||= 0
|
23
|
+
@conn[:count] += 1
|
24
|
+
@conn[:count] < 1000
|
60
25
|
end
|
61
26
|
end
|
62
27
|
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
28
|
+
class DummyConn < Hash
|
29
|
+
attr_accessor :closed
|
30
|
+
def close; @closed = true; end
|
79
31
|
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({}, r.cookies)
|
92
|
-
assert_nil r.response_cookies
|
93
|
-
assert_equal true, r.persistent
|
94
32
|
|
95
|
-
|
96
|
-
r =
|
97
|
-
|
98
|
-
|
99
|
-
assert_equal
|
100
|
-
assert_equal
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
assert_equal
|
105
|
-
|
106
|
-
|
107
|
-
assert_equal
|
108
|
-
|
109
|
-
r.conn = StringIO.new("GET /cex?q=node_state HTTP/1.1\r\n\r\n")
|
110
|
-
assert_kind_of Hash, r.parse_request
|
111
|
-
assert_equal :get, r.method
|
112
|
-
assert_equal '/cex', r.path
|
113
|
-
assert_equal 'q=node_state', r.query
|
114
|
-
assert_equal({:q => 'node_state'}, r.parameters)
|
115
|
-
assert_equal({}, r.cookies)
|
116
|
-
assert_nil r.response_cookies
|
117
|
-
|
118
|
-
r.conn = StringIO.new("GET / HTTP/1.0\r\n\r\n")
|
119
|
-
assert_kind_of Hash, r.parse_request
|
120
|
-
assert_equal false, r.persistent
|
121
|
-
assert_equal({}, r.cookies)
|
122
|
-
assert_nil r.response_cookies
|
123
|
-
|
124
|
-
r.conn = StringIO.new("GET / HTTP/invalid\r\n\r\n")
|
125
|
-
assert_kind_of Hash, r.parse_request
|
126
|
-
assert_equal 'invalid', r.version
|
127
|
-
assert_equal false, r.persistent
|
128
|
-
|
129
|
-
r.conn = StringIO.new("GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
|
130
|
-
assert_kind_of Hash, r.parse_request
|
131
|
-
assert_equal '1.1', r.version
|
132
|
-
assert_equal 'close', r.headers['Connection']
|
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]
|
141
|
-
end
|
142
|
-
|
143
|
-
def test_send_response
|
144
|
-
r = DummyConnection3.new
|
145
|
-
# simple case
|
146
|
-
r.conn = StringIO.new
|
147
|
-
r.persistent = true
|
148
|
-
r.send_response(200, 'text', 'Hello there!')
|
149
|
-
r.conn.rewind
|
150
|
-
assert_equal "HTTP/1.1 200\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!",
|
151
|
-
r.conn.read
|
152
|
-
|
153
|
-
# include content-length
|
154
|
-
r.conn = StringIO.new
|
155
|
-
r.persistent = true
|
156
|
-
r.send_response(404, 'text/html', '<h1>404!</h1>', 10) # incorrect length
|
157
|
-
r.conn.rewind
|
158
|
-
assert_equal "HTTP/1.1 404\r\nContent-Type: text/html\r\nContent-Length: 10\r\n\r\n<h1>404!</h1>",
|
159
|
-
r.conn.read
|
160
|
-
|
161
|
-
# headers
|
162
|
-
r.conn = StringIO.new
|
163
|
-
r.persistent = true
|
164
|
-
r.send_response(404, 'text/html', '<h1>404!</h1>', nil, {'ETag' => 'xxyyzz'})
|
165
|
-
r.conn.rewind
|
166
|
-
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>",
|
167
|
-
r.conn.read
|
168
|
-
|
169
|
-
# no body
|
170
|
-
r.conn = StringIO.new
|
171
|
-
r.persistent = true
|
172
|
-
r.send_response(404, 'text/html', nil, nil, {'Set-Cookie' => 'abc=123'})
|
173
|
-
r.conn.rewind
|
174
|
-
assert_equal "HTTP/1.1 404\r\nConnection: close\r\nContent-Type: text/html\r\nSet-Cookie: abc=123\r\n\r\n",
|
175
|
-
r.conn.read
|
176
|
-
assert_equal false, r.persistent
|
177
|
-
|
178
|
-
# not persistent
|
179
|
-
r.conn = StringIO.new
|
180
|
-
r.persistent = false
|
181
|
-
r.send_response(200, 'text', 'Hello there!')
|
182
|
-
r.conn.rewind
|
183
|
-
assert_equal "HTTP/1.1 200\r\nConnection: close\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!",
|
184
|
-
r.conn.read
|
185
|
-
|
186
|
-
# socket error
|
187
|
-
r.conn = nil
|
188
|
-
r.persistent = true
|
189
|
-
assert_nothing_raised {r.send_response(200, 'text', 'Hello there!')}
|
190
|
-
assert_equal false, r.persistent
|
191
|
-
end
|
192
|
-
|
193
|
-
def test_stream
|
194
|
-
r = DummyConnection3.new
|
195
|
-
r.conn = StringIO.new
|
196
|
-
r.stream 'hey there'
|
197
|
-
r.conn.rewind
|
198
|
-
assert_equal 'hey there', r.conn.read
|
199
|
-
|
200
|
-
r.conn = StringIO.new
|
201
|
-
r.persistent = true
|
202
|
-
r.send_response(404, 'text/html', nil, nil, {'Set-Cookie' => 'abc=123'})
|
203
|
-
r.stream('hey there')
|
204
|
-
r.conn.rewind
|
205
|
-
assert_equal "HTTP/1.1 404\r\nConnection: close\r\nContent-Type: text/html\r\nSet-Cookie: abc=123\r\n\r\nhey there",
|
206
|
-
r.conn.read
|
207
|
-
end
|
208
|
-
|
209
|
-
def test_redirect
|
210
|
-
r = DummyConnection3.new
|
211
|
-
r.conn = StringIO.new
|
212
|
-
r.redirect('http://mau.com/132')
|
213
|
-
r.conn.rewind
|
214
|
-
assert_equal "HTTP/1.1 302\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n", r.conn.read
|
215
|
-
|
216
|
-
r.conn = StringIO.new
|
217
|
-
r.redirect('http://www.google.com/search?q=meaning%20of%20life', true)
|
218
|
-
r.conn.rewind
|
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
|
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
|
33
|
+
def test_new
|
34
|
+
r = ServerSide::HTTP::Connection.new('hello', ServerSide::HTTP::Request)
|
35
|
+
sleep 0.1
|
36
|
+
assert_equal 'hello', r.conn
|
37
|
+
assert_equal ServerSide::HTTP::Request, r.request_class
|
38
|
+
assert_equal false, r.thread.alive?
|
39
|
+
|
40
|
+
c = DummyConn.new
|
41
|
+
r = ServerSide::HTTP::Connection.new(c, DummyRequest1)
|
42
|
+
assert_equal DummyRequest1, r.request_class
|
43
|
+
r.thread.join
|
44
|
+
assert_equal 1000, c[:count]
|
45
|
+
assert_equal 1000, DummyRequest1.instance_count
|
46
|
+
assert_equal true, c.closed
|
241
47
|
end
|
242
48
|
end
|