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.
Files changed (51) hide show
  1. data/CHANGELOG +10 -0
  2. data/README +3 -3
  3. data/Rakefile +4 -4
  4. data/bin/serverside +4 -4
  5. data/doc/rdoc/classes/Daemon.html +4 -4
  6. data/doc/rdoc/classes/ServerSide.html +14 -74
  7. data/doc/rdoc/classes/ServerSide/Application.html +13 -13
  8. data/doc/rdoc/classes/ServerSide/{Connection.html → HTTP.html} +12 -18
  9. data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +200 -0
  10. data/doc/rdoc/classes/ServerSide/{Connection → HTTP}/Const.html +6 -6
  11. data/doc/rdoc/classes/ServerSide/{Connection/Base.html → HTTP/Request.html} +213 -164
  12. data/doc/rdoc/classes/ServerSide/{Server.html → HTTP/Server.html} +22 -22
  13. data/doc/rdoc/classes/ServerSide/Router.html +496 -0
  14. data/doc/rdoc/classes/ServerSide/StaticFiles.html +31 -30
  15. data/doc/rdoc/classes/ServerSide/Template.html +12 -12
  16. data/doc/rdoc/created.rid +1 -1
  17. data/doc/rdoc/files/CHANGELOG.html +22 -4
  18. data/doc/rdoc/files/COPYING.html +1 -1
  19. data/doc/rdoc/files/README.html +4 -4
  20. data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
  21. data/doc/rdoc/files/lib/serverside/cluster_rb.html +1 -1
  22. data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
  23. data/doc/rdoc/files/lib/serverside/core_ext_rb.html +1 -1
  24. data/doc/rdoc/files/lib/serverside/daemon_rb.html +1 -1
  25. data/doc/rdoc/files/lib/serverside/request_rb.html +101 -0
  26. data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
  27. data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
  28. data/doc/rdoc/files/lib/serverside/static_rb.html +1 -1
  29. data/doc/rdoc/files/lib/serverside/template_rb.html +1 -1
  30. data/doc/rdoc/files/lib/serverside_rb.html +1 -1
  31. data/doc/rdoc/fr_class_index.html +6 -5
  32. data/doc/rdoc/fr_file_index.html +1 -0
  33. data/doc/rdoc/fr_method_index.html +31 -31
  34. data/lib/serverside/application.rb +1 -1
  35. data/lib/serverside/connection.rb +12 -141
  36. data/lib/serverside/request.rb +150 -0
  37. data/lib/serverside/routing.rb +117 -126
  38. data/lib/serverside/server.rb +13 -12
  39. data/lib/serverside/static.rb +1 -1
  40. data/test/functional/primitive_static_server_test.rb +3 -3
  41. data/test/functional/routing_server.rb +1 -1
  42. data/test/functional/routing_server_test.rb +1 -1
  43. data/test/functional/static_profile.rb +1 -1
  44. data/test/functional/static_server_test.rb +2 -2
  45. data/test/unit/connection_test.rb +30 -224
  46. data/test/unit/request_test.rb +248 -0
  47. data/test/unit/routing_test.rb +4 -4
  48. data/test/unit/server_test.rb +10 -8
  49. data/test/unit/static_test.rb +1 -1
  50. metadata +61 -56
  51. data/doc/rdoc/classes/ServerSide/Connection/Router.html +0 -493
@@ -1,140 +1,131 @@
1
+ require File.join(File.dirname(__FILE__), 'request')
2
+
1
3
  module ServerSide
2
- module Connection
3
- # The Router class defines a kind of connection that can route requests
4
- # to different handlers based on rules that can be specified either by
5
- # lambdas or by hashes that contain variable names corresponding to patterns.
6
- #
7
- # The simplest form of a routing rule specifies a path pattern:
8
- #
9
- # ServerSide.route('/static') {serve_static('.'/@path)}
10
- #
11
- # But you can also check for other attributes of the request:
12
- #
13
- # ServerSide.route(:path => '/static', :host => '^:subdomain\.mydomain') {
14
- # serve_static(@parameters[:subdomain]/@path)
15
- # }
16
- #
17
- # It also possible to pass a lambda as a rule:
18
- #
19
- # ServerSide.route(lambda {@headers['Agent'] =~ /Moz/}) {serve_static('moz'/@path)}
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
- # Routing rules are evaluated in backwards, so the rules should be ordered
22
- # from the general to the specific.
23
- class Router < Base
24
- # Returns true if routes were defined.
25
- def self.has_routes?
26
- @@rules && !@@rules.empty? rescue false
27
- end
28
-
29
- # Adds a routing rule. The normalized rule is a hash containing keys (acting
30
- # as instance variable names) with patterns as values. If the rule is not a
31
- # hash, it is normalized into a pattern checked against the request path.
32
- # Pattern values can also be arrays, any member of which is checked as a
33
- # pattern. The rule can also be a Proc or lambda which is run with the
34
- # connection object's binding. A contrived example:
35
- #
36
- # ServerSide.route(lambda{path = 'mypage'}) {serve_static('mypage.html')}
37
- def self.route(rule, &block)
38
- @@rules ||= []
39
- rule = {:path => rule} unless (Hash === rule) || (Proc === rule)
40
- @@rules.unshift [rule, block]
41
- compile_rules
42
- end
43
-
44
- # Compiles all rules into a respond method that is invoked when a request
45
- # is received.
46
- def self.compile_rules
47
- @@rules ||= []
48
- code = @@rules.inject('lambda {') {|m, r| m << rule_to_statement(r[0], r[1])}
49
- code << 'default_handler}'
50
- define_method(:respond, &eval(code))
51
- end
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
- # Pattern for finding parameters inside patterns. Parameters are parts of the
72
- # pattern, which the routing pre-processor turns into sub-regexp that are
73
- # used to extract parameter values from the pattern.
74
- #
75
- # For example, matching '/controller/show' against '/controller/:action' will
76
- # give us @parameters[:action] #=> "show"
77
- ParamRegexp = /(?::([a-z]+))/
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
- # Returns the condition part for the key and value specified. The key is the
80
- # name of an instance variable and the value is a pattern to match against.
81
- # If the pattern contains parameters (for example, /controller/:action,) the
82
- # method creates a lambda for extracting the parameter values.
83
- def self.condition_part(key, value)
84
- p_parse, p_count = '', 0
85
- while (String === value) && (value =~ ParamRegexp)
86
- value = value.dup
87
- p_name = $1
88
- p_count += 1
89
- value.sub!(ParamRegexp, '(.+)')
90
- p_parse << "@parameters[:#{p_name}] = $#{p_count}\n"
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
- # Converts a proc into a method, returning the method's name (as a symbol)
103
- def self.define_proc(&block)
104
- tag = block.proc_tag
105
- define_method(tag.to_sym, &block) unless instance_methods.include?(tag)
106
- tag.to_sym
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
- # Converts a value into a local constant and freezes it. Returns the
110
- # constant's tag name
111
- def self.cache_constant(value)
112
- tag = value.const_tag
113
- class_eval "#{tag} = #{value.inspect}.freeze" rescue nil
114
- tag
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
- # Sets the default handler for incoming requests.
118
- def self.route_default(&block)
119
- define_method(:default_handler, &block)
120
- compile_rules
121
- end
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
- def unhandled
124
- send_response(403, 'text', 'No handler found.')
125
- end
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
- alias_method :default_handler, :unhandled
124
+ # Generic responder for unhandled requests.
125
+ def unhandled
126
+ send_response(403, 'text', 'No handler found.')
128
127
  end
129
- end
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
@@ -1,18 +1,19 @@
1
1
  require 'socket'
2
2
 
3
3
  module ServerSide
4
- # The ServerSide HTTP server is designed to be fast and simple. It is also
5
- # designed to support both HTTP 1.1 persistent connections, and HTTP streaming
6
- # for applications which use Comet techniques.
7
- class Server
8
- # Creates a new server by opening a listening socket and starting an accept
9
- # loop. When a new connection is accepted, a new instance of the
10
- # supplied connection class is instantiated and passed the connection for
11
- # processing.
12
- def initialize(host, port, connection_class)
13
- @connection_class = connection_class
14
- @server = TCPServer.new(host, port)
15
- loop {@connection_class.new(@server.accept)}
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
@@ -1,6 +1,6 @@
1
1
  module ServerSide
2
2
  # This module provides functionality for serving files and directory listings
3
- # over HTTP. It is mainly used by ServerSide::Connection::Static.
3
+ # over HTTP.
4
4
  module StaticFiles
5
5
  # Frozen constants to be used by the module.
6
6
  module Const
@@ -3,7 +3,7 @@ require 'net/http'
3
3
 
4
4
  class StaticServerTest < Test::Unit::TestCase
5
5
 
6
- class StaticConnection < ServerSide::Connection::Base
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, StaticConnection)}
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, StaticConnection)}
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)
@@ -11,4 +11,4 @@ ServerSide.route(:path => '/xml/:flavor/feed.xml') do
11
11
  redirect('http://feeds.feedburner.com/RobbyOnRails')
12
12
  end
13
13
 
14
- ServerSide::Server.new('0.0.0.0', 8000, ServerSide::Connection::Router)
14
+ ServerSide::HTTP::Server.new('0.0.0.0', 8000, ServerSide::Router)
@@ -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::Connection::Router)}
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)
@@ -7,7 +7,7 @@ FileUtils.cd(File.dirname(__FILE__))
7
7
  pid = fork do
8
8
  trap('TERM') {exit}
9
9
  require 'profile'
10
- ServerSide::Server.new('0.0.0.0', 8000, ServerSide::Connection::Static)
10
+ ServerSide::HTTP::Server.new('0.0.0.0', 8000, nil)
11
11
  end
12
12
 
13
13
  puts "Please wait..."
@@ -3,14 +3,14 @@ require 'net/http'
3
3
 
4
4
  class StaticServerTest < Test::Unit::TestCase
5
5
 
6
- class StaticServer < ServerSide::Connection::Base
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, StaticServer)}
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 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
5
+ class ServerSide::HTTP::Connection
6
+ attr_reader :conn, :request_class, :thread
18
7
  end
19
8
 
20
- class DummyConnection
21
- attr_reader :opened
22
-
23
- def initialize
24
- @opened = true
25
- end
9
+ class DummyRequest1 < ServerSide::HTTP::Request
10
+ @@instance_count = 0
26
11
 
27
- def close
28
- @opened = false
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 parse_request
36
- @count ||= 0
37
- @count += 1
38
- @persistent = @count < 1000
17
+ def self.instance_count
18
+ @@instance_count
39
19
  end
40
20
 
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, :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
- 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
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
- # trailing slash in path
96
- r = DummyConnection3.new
97
- r.conn = StringIO.new("POST /test/asdf/qw/?time=24%20hours HTTP/1.1\r\n\r\n")
98
- assert_kind_of Hash, r.parse_request
99
- assert_equal :post, r.method
100
- assert_equal '/test/asdf/qw', r.path
101
- assert_equal 'time=24%20hours', r.query
102
- assert_equal '1.1', r.version
103
- assert_equal({:time => '24 hours'}, r.parameters)
104
- assert_equal({}, r.headers)
105
- assert_equal({}, r.cookies)
106
- assert_nil r.response_cookies
107
- assert_equal true, r.persistent
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