serverside 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
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