serverside 0.2.8 → 0.2.9

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 CHANGED
@@ -1,3 +1,27 @@
1
+ ==0.2.9
2
+
3
+ * Improved rake clean task.
4
+
5
+ * Added HTTP::Request.send_file method.
6
+
7
+ * Added basic rendering capabilities to ServerSide::Controller.
8
+
9
+ * Added support for lambda rules in ServerSide::Controller.mount.
10
+
11
+ * Start work on controller class.
12
+
13
+ * Renamed Router.route_default to default_route.
14
+
15
+ * Fixed small errors in documentation.
16
+
17
+ * Changed ServerSide::Static.serve_static to look for index.html inside directory before serving the directory.
18
+
19
+ * Added HTTP::Caching.cache_etags and cache_stamp methods.
20
+
21
+ * Fixed bug in doc_rforge rake task.
22
+
23
+ * Rewrote request unit test into request spec.
24
+
1
25
  ==0.2.8
2
26
 
3
27
  * Refactored ServerSide::Static to use HTTP::Caching code.
data/Rakefile CHANGED
@@ -7,8 +7,8 @@ require 'fileutils'
7
7
  include FileUtils
8
8
 
9
9
  NAME = "serverside"
10
- VERS = "0.2.8"
11
- CLEAN.include ['**/.*.sw?', '*.gem', '.config']
10
+ VERS = "0.2.9"
11
+ CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
12
12
  RDOC_OPTS = ['--quiet', '--title', "ServerSide Documentation",
13
13
  "--opname", "index.html",
14
14
  "--line-numbers",
@@ -67,6 +67,7 @@ task :uninstall => [:clean] do
67
67
  sh %{sudo gem uninstall #{NAME}}
68
68
  end
69
69
 
70
+ desc 'Update docs and upload to rubyforge.org'
70
71
  task :doc_rforge do
71
72
  sh %{rake doc}
72
73
  sh %{scp -r doc/rdoc/* ciconia@rubyforge.org:/var/www/gforge-projects/serverside}
@@ -124,12 +125,6 @@ RCov::VerifyTask.new(:rcov_verify => :rcov) do |t|
124
125
  t.index_html = 'doc/output/coverage/index.html'
125
126
  end
126
127
 
127
- desc 'Update docs and upload to rubyforge.org'
128
- task :doc_rforge do
129
- sh %{rake doc}
130
- sh "scp -C -r * ciconia@rubyforge.org:/var/www/gforge-projects/serverside/"
131
- end
132
-
133
128
  ##############################################################################
134
129
  # Statistics
135
130
  ##############################################################################
data/bin/serverside CHANGED
@@ -65,7 +65,7 @@ else
65
65
  require fn if File.file?(fn)
66
66
  end
67
67
  unless ServerSide::Router.has_routes?
68
- ServerSide::Router.route_default {serve_static('.'/@path)}
68
+ ServerSide::Router.default_route {serve_static('.'/@path)}
69
69
  end
70
70
 
71
71
  if $cmd == 'serve'
@@ -14,7 +14,30 @@ module ServerSide
14
14
  NOT_MODIFIED_CLOSE = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nConnection: close\r\nLast-Modified: %s\r\nETag: \"%s\"\r\nCache-Control: max-age=%d\r\n\r\n".freeze
15
15
  NOT_MODIFIED_PERSIST = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nLast-Modified: %s\r\nETag: \"%s\"\r\nCache-Control: max-age=%d\r\n\r\n".freeze
16
16
  MAX_AGE = 'max-age=%d'.freeze
17
+ IF_NONE_MATCH_REGEXP = /^"?([^"]+)"?$/.freeze
17
18
 
19
+ # Returns an array containing all etags specified by the client in the
20
+ # If-None-Match header.
21
+ def cache_etags
22
+ h = @headers[IF_NONE_MATCH]
23
+ return [] unless h
24
+ h.split(',').inject([]) do |m, i|
25
+ i.strip =~ IF_NONE_MATCH_REGEXP ? (m << $1) : m
26
+ end
27
+ end
28
+
29
+ # Returns the cache stamp specified by the client in the
30
+ # If-Modified-Since header. If no stamp is specified, returns nil.
31
+ def cache_stamp
32
+ (h = @headers[IF_MODIFIED_SINCE]) ? Time.httpdate(h) : nil
33
+ rescue
34
+ nil
35
+ end
36
+
37
+ # Checks the request headers for validators and returns true if the
38
+ # client cache is valid. The validators can be either etags (specified
39
+ # in the If-None-Match header), or a modification stamp (specified in the
40
+ # If-Modified-Since header.)
18
41
  def valid_client_cache?(etag, http_stamp)
19
42
  none_match = @headers[IF_NONE_MATCH]
20
43
  modified_since = @headers[IF_MODIFIED_SINCE]
@@ -22,6 +45,15 @@ module ServerSide
22
45
  (modified_since && (modified_since == http_stamp))
23
46
  end
24
47
 
48
+ # Validates the client cache by checking any supplied validators in the
49
+ # request. If the client cache is not valid, the specified block is
50
+ # executed. This method also makes sure the correct validators are
51
+ # included in the response - along with a Cache-Control header, to allow
52
+ # the client to cache the response. A possible usage:
53
+ #
54
+ # validate_cache("1234-5678", Time.now, 360) do
55
+ # send_response(200, "text/html", body)
56
+ # end
25
57
  def validate_cache(etag, stamp, max_age = DEFAULT_MAX_AGE, &block)
26
58
  http_stamp = stamp.httpdate
27
59
  if valid_client_cache?(etag, http_stamp)
@@ -34,23 +66,14 @@ module ServerSide
34
66
  end
35
67
  end
36
68
 
69
+ # Sends a 304 HTTP response, along with etag and stamp validators, and a
70
+ # Cache-Control header.
37
71
  def send_not_modified(etag, http_time, max_age = DEFAULT_MAX_AGE)
38
72
  @socket << ((@persistent ? NOT_MODIFIED_PERSIST : NOT_MODIFIED_CLOSE) %
39
73
  [Time.now.httpdate, http_time, etag, max_age])
40
74
  end
41
- end
75
+ end
42
76
  end
43
77
  end
44
78
 
45
79
  __END__
46
-
47
- Reality::Controller.new(:node_history) do
48
- default_flavor :html
49
- valid_method :get
50
-
51
- def html
52
- validate_cache(etag, http_time) {|cache_headers|
53
- render_template(cache_headers)
54
- }
55
- end
56
- end
@@ -0,0 +1,67 @@
1
+ require File.join(File.dirname(__FILE__), 'routing')
2
+
3
+ module ServerSide
4
+ class Controller
5
+ def self.mount(rule = nil, &block)
6
+ rule ||= block
7
+ raise ArgumentError, "No routing rule specified." if rule.nil?
8
+ c = Class.new(self) {}
9
+ ServerSide::Router.route(rule) {c.new(self)}
10
+ c
11
+ end
12
+
13
+ def initialize(request)
14
+ @request = request
15
+ @path = request.path
16
+ @parameters = request.parameters
17
+ process
18
+ render_default if not @rendered
19
+ end
20
+
21
+ def process
22
+ end
23
+
24
+ def render_default
25
+ @request.send_response(200, 'text/plain', 'no response.')
26
+ end
27
+
28
+ def render(body, content_type)
29
+ @request.send_response(200, content_type, body)
30
+ @rendered = true
31
+ end
32
+ end
33
+ end
34
+
35
+ __END__
36
+
37
+ require 'rubygems'
38
+ require 'active_support/inflector'
39
+ require 'metaid'
40
+
41
+ class ActionController
42
+ def self.default_routing_rule
43
+ if name.split('::').last =~ /(.+)Controller$/
44
+ controller = Inflector.underscore($1)
45
+ {:path => ["/#{controller}/:action", "/#{controller}/:action/:id"]}
46
+ end
47
+ end
48
+
49
+ def self.inherited(c)
50
+ routing_rule = c.respond_to?(:routing_rule) ?
51
+ c.routing_rule : c.default_routing_rule
52
+ if routing_rule
53
+ ServerSide::Router.route(routing_rule) {c.new(self)}
54
+ end
55
+ end
56
+
57
+ def self.route(arg = nil, &block)
58
+ rule = arg || block
59
+ meta_def(:get_route) {rule}
60
+ end
61
+ end
62
+
63
+ class MyController < ActionController
64
+ route "hello"
65
+ end
66
+
67
+ p MyController.get_route
@@ -165,6 +165,16 @@ module ServerSide
165
165
  @persistent = false
166
166
  end
167
167
 
168
+ CONTENT_DISPOSITION = 'Content-Disposition'.freeze
169
+ CONTENT_DESCRIPTION = 'Content-Description'.freeze
170
+
171
+ def send_file(content, content_type, disposition = :inline, filename = nil, description = nil)
172
+ disposition = filename ? "#{disposition}; filename=#{filename}" : "#{disposition}"
173
+ @response_headers[CONTENT_DISPOSITION] = disposition
174
+ @response_headers[CONTENT_DESCRIPTION] = description if description
175
+ send_response(200, content_type, content)
176
+ end
177
+
168
178
  # Send a redirect response.
169
179
  def redirect(location, permanent = false)
170
180
  @socket << (STATUS_REDIRECT %
@@ -1,8 +1,8 @@
1
1
  require File.join(File.dirname(__FILE__), 'request')
2
2
 
3
3
  module ServerSide
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
4
+ # The Router class is a subclass of HTTP::Request that can invoke
5
+ # different handlers based on rules that can be specified either by
6
6
  # lambdas or by hashes that contain variable names corresponding to patterns.
7
7
  #
8
8
  # The simplest form of a routing rule specifies a path pattern:
@@ -19,7 +19,7 @@ module ServerSide
19
19
  #
20
20
  # ServerSide.route(lambda {@headers['Agent'] =~ /Moz/}) {serve_static('moz'/@path)}
21
21
  #
22
- # Routing rules are evaluated in backwards, so the rules should be ordered
22
+ # Routing rules are evaluated backwards, so the rules should be ordered
23
23
  # from the general to the specific.
24
24
  class Router < HTTP::Request
25
25
  # Returns true if routes were defined.
@@ -116,7 +116,7 @@ module ServerSide
116
116
  end
117
117
 
118
118
  # Sets the default handler for incoming requests.
119
- def self.route_default(&block)
119
+ def self.default_route(&block)
120
120
  define_method(:default_handler, &block)
121
121
  compile_rules
122
122
  end
@@ -76,7 +76,13 @@ module ServerSide
76
76
  elsif serve_template(path)
77
77
  return
78
78
  elsif File.directory?(path)
79
- serve_dir(path)
79
+ if File.file?(path/'index.html')
80
+ serve_file(path/'index.html')
81
+ elsif File.file?(path/'index.rhtml')
82
+ serve_template(path/'index.rhtml')
83
+ else
84
+ serve_dir(path)
85
+ end
80
86
  else
81
87
  send_response(404, 'text', FILE_NOT_FOUND)
82
88
  end
@@ -3,5 +3,5 @@ require 'lib/serverside'
3
3
  trap('INT') {exit}
4
4
  include ServerSide
5
5
  puts "Serving on port 8000..."
6
- Router.route_default {serve_static('.'/@path)}
6
+ Router.default_route {serve_static('.'/@path)}
7
7
  HTTP::Server.new('0.0.0.0', 8000, Router)
@@ -98,4 +98,42 @@ context "Caching::valid_client_cache?" do
98
98
  r.headers['If-Modified-Since'] = t.httpdate
99
99
  r.valid_client_cache?('eeee', t.httpdate).should_not_be false
100
100
  end
101
- end
101
+ end
102
+
103
+ context "Caching::cache_etags" do
104
+ specify "should return an empty array if no If-None-Match header is included" do
105
+ r = DummyRequest.new
106
+ r.cache_etags.should_be_a_kind_of Array
107
+ r.cache_etags.should_equal []
108
+ end
109
+
110
+ specify "should return all etags included in the If-None-Match header" do
111
+ r = DummyRequest.new
112
+ r.headers['If-None-Match'] = '*'
113
+ r.cache_etags.should_equal ['*']
114
+ r.headers['If-None-Match'] = '*, "XXX-YYY","AAA-BBB"'
115
+ r.cache_etags.should_equal ['*', 'XXX-YYY', 'AAA-BBB']
116
+ r.headers['If-None-Match'] = '"abcd-EFGH"'
117
+ r.cache_etags.should_equal ['abcd-EFGH']
118
+ end
119
+ end
120
+
121
+ context "Caching::cache_stamps" do
122
+ specify "should return nil if no If-Modified-Since header is included" do
123
+ r = DummyRequest.new
124
+ r.cache_stamp.should_be_nil
125
+ end
126
+
127
+ specify "should return nil if no an invalid stamp is specified" do
128
+ r = DummyRequest.new
129
+ r.headers['If-Modified-Since'] = 'invalid stamp'
130
+ r.cache_stamp.should_be_nil
131
+ end
132
+
133
+ specify "should return the stamp specified in the If-Modified-Since header" do
134
+ r = DummyRequest.new
135
+ t = Time.now
136
+ r.headers['If-Modified-Since'] = t.httpdate
137
+ r.cache_stamp.to_i.should_equal t.to_i
138
+ end
139
+ end
@@ -0,0 +1,139 @@
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
+ r = ServerSide::Router.rules.first
32
+ r.first.should_equal rule
33
+ r.last.should_be_a_kind_of Proc
34
+ c.module_eval do
35
+ define_method(:initialize) {|req| $req = req}
36
+ end
37
+ res = r.last.call
38
+ res.should_be_a_kind_of c
39
+
40
+ r = ServerSide::Router.new(StringIO.new)
41
+ r.path = '/test'
42
+ r.respond
43
+ $req.should_be_a_kind_of ServerSide::Router
44
+ end
45
+
46
+ specify "should accept either an argument or block as the rule" do
47
+ ServerSide::Router.reset_rules
48
+ rule = {:path => '/test'}
49
+ c = ServerSide::Controller.mount(rule)
50
+ r = ServerSide::Router.rules.first
51
+ r.first.should_be rule
52
+
53
+ ServerSide::Router.reset_rules
54
+ rule = proc {true}
55
+ c = ServerSide::Controller.mount(&rule)
56
+ r = ServerSide::Router.rules.first
57
+ r.first.should_be rule
58
+ end
59
+ end
60
+
61
+ class ServerSide::Controller
62
+ attr_accessor :request, :path, :parameters, :rendered
63
+ end
64
+
65
+ class ServerSide::HTTP::Request
66
+ attr_accessor :path, :parameters
67
+ end
68
+
69
+ require 'metaid'
70
+
71
+ class DummyController < ServerSide::Controller
72
+ attr_reader :process_called
73
+
74
+ def process
75
+ @process_called = true
76
+ end
77
+
78
+ def render_default
79
+ @rendered = :default
80
+ end
81
+ end
82
+
83
+ context "ServerSide::Controller new instance" do
84
+ specify "should set @request, @path, and @parameters instance variables" do
85
+ req = ServerSide::HTTP::Request.new(StringIO.new)
86
+ req.path = '/aa/bb/cc'
87
+ req.parameters = {:q => 'node_state', :f => 'xml'}
88
+ c = ServerSide::Controller.new(req)
89
+ c.request.should_be req
90
+ c.path.should_be req.path
91
+ c.parameters.should_be req.parameters
92
+ end
93
+
94
+ specify "should invoke the process method" do
95
+ req = ServerSide::HTTP::Request.new(StringIO.new)
96
+ c = DummyController.new(req)
97
+ c.process_called.should_be true
98
+ end
99
+
100
+ specify "should invoke render_default unless @rendered" do
101
+ req = ServerSide::HTTP::Request.new(StringIO.new)
102
+ c = DummyController.new(req)
103
+ c.rendered.should_be :default
104
+
105
+ c_class = Class.new(DummyController) do
106
+ define_method(:process) {@rendered = true}
107
+ end
108
+ c = c_class.new(req)
109
+ c.rendered.should_be true
110
+ end
111
+ end
112
+
113
+ context "ServerSide::Controller.render_default" do
114
+ specify "should render a default 200 response" do
115
+ req = ServerSide::HTTP::Request.new(StringIO.new)
116
+ c = ServerSide::Controller.new(req)
117
+ req.socket.rewind
118
+ resp = req.socket.read
119
+ resp.should_match /HTTP\/1\.1\s200/
120
+ end
121
+ end
122
+
123
+ context "ServerSide::Controller.render" do
124
+ specify "should render a 200 response with body and content type arguments" do
125
+ req = ServerSide::HTTP::Request.new(StringIO.new)
126
+ c = ServerSide::Controller.new(req)
127
+ c.render('hello world', 'text/plain')
128
+ req.socket.rewind
129
+ resp = req.socket.read
130
+ resp.should_match /Content-Type:\stext\/plain\r\n/
131
+ end
132
+
133
+ specify "should set @rendered to true" do
134
+ req = ServerSide::HTTP::Request.new(StringIO.new)
135
+ c = ServerSide::Controller.new(req)
136
+ c.render('hello world', 'text/plain')
137
+ c.rendered.should_equal true
138
+ end
139
+ end
@@ -0,0 +1,288 @@
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_equal [:parse]
24
+
25
+ r.calls = []
26
+ r.parse_result = false
27
+ r.process.should_be false
28
+ r.calls.should_equal [: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_equal [:parse, :respond]
36
+
37
+ r.calls = []
38
+ r.persistent = 'mau'
39
+ r.process.should_equal 'mau'
40
+ r.calls.should_equal [: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_equal :post
73
+ r.path.should_equal '/test'
74
+ r.query.should_be_nil
75
+ r.version.should_equal '1.1'
76
+ r.parameters.should_equal({})
77
+ r.headers.should_equal({'Content-Type' => 'text/html'})
78
+ r.cookies.should_equal({})
79
+ r.response_cookies.should_be_nil
80
+ r.persistent.should_equal 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_equal '/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_equal '/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_equal 2
101
+ r.parameters[:time].should_equal '24 hours'
102
+ r.parameters[:q].should_equal '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_equal '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_equal '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_equal 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_equal 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_equal 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_equal 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_equal 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_equal 'abc=1342; def=7%2f4'
142
+ r.cookies.size.should_equal 2
143
+ r.cookies[:abc].should_equal '1342'
144
+ r.cookies[:def].should_equal '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_equal 2
153
+ r.parameters[:q].should_equal 'node_history'
154
+ r.parameters[:time].should_equal '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_equal "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_equal "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_equal "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_equal "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_equal "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_equal "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_equal 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_equal "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_equal "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_equal "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_equal "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_equal "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_equal "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
@@ -69,7 +69,7 @@ class RoutingTest < Test::Unit::TestCase
69
69
  assert_equal nil, R.new(StringIO.new).respond
70
70
  R.rules << [{:t => 'abc'}, lambda{1}]
71
71
  R.rules << [{:t => 'def'}, lambda{2}]
72
- R.route_default {3}
72
+ R.default_route {3}
73
73
  R.compile_rules
74
74
  r = R.new(StringIO.new)
75
75
  r.t = 'abc'
@@ -148,11 +148,11 @@ class RoutingTest < Test::Unit::TestCase
148
148
  assert_equal c, eval("R::#{tag}")
149
149
  end
150
150
 
151
- def test_route_default
152
- R.route_default {'mau m'}
151
+ def test_default_route
152
+ R.default_route {'mau m'}
153
153
  assert_equal 'mau m', R.new(StringIO.new).default_handler
154
154
 
155
- R.route_default {654321}
155
+ R.default_route {654321}
156
156
  assert_equal 654321, R.new(StringIO.new).default_handler
157
157
  end
158
158
 
@@ -164,8 +164,8 @@ class RoutingTest < Test::Unit::TestCase
164
164
  assert_equal 2, R.rules[0][1].call
165
165
  end
166
166
 
167
- def test_serverside_route_default
168
- ServerSide::Router.route_default {1234}
167
+ def test_serverside_default_route
168
+ ServerSide::Router.default_route {1234}
169
169
  assert_equal 1234, R.new(StringIO.new).default_handler
170
170
  end
171
171
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: serverside
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.8
7
- date: 2006-10-14 00:00:00 +02:00
6
+ version: 0.2.9
7
+ date: 2006-10-25 00:00:00 +02:00
8
8
  summary: Performance-oriented web framework.
9
9
  require_paths:
10
10
  - lib
@@ -33,43 +33,46 @@ files:
33
33
  - README
34
34
  - Rakefile
35
35
  - bin/serverside
36
+ - test/unit
36
37
  - test/functional
37
38
  - test/spec
38
39
  - test/test_helper.rb
39
- - test/unit
40
+ - test/unit/connection_test.rb
41
+ - test/unit/cluster_test.rb
42
+ - test/unit/daemon_test.rb
43
+ - test/unit/server_test.rb
44
+ - test/unit/static_test.rb
45
+ - test/unit/routing_test.rb
46
+ - test/unit/core_ext_test.rb
47
+ - test/unit/request_test.rb
48
+ - test/unit/template_test.rb
49
+ - test/functional/routing_server.rb
50
+ - test/functional/static_server.rb
40
51
  - test/functional/primitive_static_server_test.rb
41
52
  - test/functional/request_body_test.rb
42
- - test/functional/routing_server.rb
43
- - test/functional/routing_server_test.rb
44
53
  - test/functional/static_profile.rb
45
54
  - test/functional/static_rfuzz.rb
46
- - test/functional/static_server.rb
47
55
  - test/functional/static_server_test.rb
56
+ - test/functional/routing_server_test.rb
57
+ - test/spec/controller_spec.rb
58
+ - test/spec/core_ext_spec.rb
59
+ - test/spec/request_spec.rb
48
60
  - test/spec/caching_spec.rb
49
61
  - test/spec/connection_spec.rb
50
- - test/spec/core_ext_spec.rb
51
- - test/unit/cluster_test.rb
52
- - test/unit/connection_test.rb
53
- - test/unit/core_ext_test.rb
54
- - test/unit/daemon_test.rb
55
- - test/unit/request_test.rb
56
- - test/unit/routing_test.rb
57
- - test/unit/server_test.rb
58
- - test/unit/static_test.rb
59
- - test/unit/template_test.rb
60
62
  - lib/serverside
61
63
  - lib/serverside.rb
62
- - lib/serverside/application.rb
63
- - lib/serverside/caching.rb
64
- - lib/serverside/cluster.rb
65
64
  - lib/serverside/connection.rb
66
- - lib/serverside/core_ext.rb
65
+ - lib/serverside/cluster.rb
67
66
  - lib/serverside/daemon.rb
68
- - lib/serverside/request.rb
69
- - lib/serverside/routing.rb
67
+ - lib/serverside/application.rb
68
+ - lib/serverside/controller.rb
70
69
  - lib/serverside/server.rb
71
70
  - lib/serverside/static.rb
71
+ - lib/serverside/routing.rb
72
+ - lib/serverside/core_ext.rb
73
+ - lib/serverside/request.rb
72
74
  - lib/serverside/template.rb
75
+ - lib/serverside/caching.rb
73
76
  - CHANGELOG
74
77
  test_files: []
75
78