serverside 0.1.59 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGELOG +21 -1
  2. data/Rakefile +69 -40
  3. data/bin/serverside +7 -15
  4. data/doc/rdoc/classes/Daemon.html +18 -18
  5. data/doc/rdoc/classes/Daemon/Base.html +8 -6
  6. data/doc/rdoc/classes/Daemon/Cluster.html +36 -36
  7. data/doc/rdoc/classes/Daemon/Cluster/PidFile.html +20 -18
  8. data/doc/rdoc/classes/Daemon/PidFile.html +12 -12
  9. data/doc/rdoc/classes/Object.html +151 -0
  10. data/doc/rdoc/classes/Proc.html +151 -0
  11. data/doc/rdoc/classes/ServerSide.html +69 -0
  12. data/doc/rdoc/classes/ServerSide/Application.html +43 -11
  13. data/doc/rdoc/classes/ServerSide/Application/Base.html +30 -30
  14. data/doc/rdoc/classes/ServerSide/Application/Static.html +20 -18
  15. data/doc/rdoc/classes/ServerSide/Connection.html +3 -3
  16. data/doc/rdoc/classes/ServerSide/Connection/Base.html +142 -99
  17. data/doc/rdoc/classes/ServerSide/Connection/Const.html +11 -13
  18. data/doc/rdoc/classes/ServerSide/Connection/Router.html +464 -0
  19. data/doc/rdoc/classes/ServerSide/Server.html +8 -6
  20. data/doc/rdoc/classes/ServerSide/StaticFiles.html +75 -43
  21. data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +5 -0
  22. data/doc/rdoc/classes/String.html +20 -18
  23. data/doc/rdoc/classes/Symbol.html +2 -0
  24. data/doc/rdoc/created.rid +1 -1
  25. data/doc/rdoc/files/CHANGELOG.html +32 -2
  26. data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
  27. data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
  28. data/doc/rdoc/files/lib/serverside/core_ext_rb.html +1 -1
  29. data/doc/rdoc/files/lib/serverside/daemon_rb.html +1 -1
  30. data/doc/rdoc/files/lib/serverside/routing_rb.html +101 -0
  31. data/doc/rdoc/files/lib/serverside/static_rb.html +1 -1
  32. data/doc/rdoc/fr_class_index.html +3 -1
  33. data/doc/rdoc/fr_file_index.html +1 -0
  34. data/doc/rdoc/fr_method_index.html +49 -33
  35. data/lib/serverside/application.rb +17 -4
  36. data/lib/serverside/connection.rb +23 -3
  37. data/lib/serverside/core_ext.rb +16 -1
  38. data/lib/serverside/routing.rb +119 -0
  39. data/lib/serverside/static.rb +9 -23
  40. data/test/functional/routing_server.rb +14 -0
  41. data/test/functional/routing_server_test.rb +41 -0
  42. data/test/functional/static_server_test.rb +8 -2
  43. data/test/unit/connection_test.rb +13 -0
  44. data/test/unit/core_ext_test.rb +14 -0
  45. data/test/unit/routing_test.rb +171 -0
  46. data/test/unit/static_test.rb +34 -1
  47. metadata +10 -3
  48. data/doc/rdoc/classes/ServerSide/Connection/Static.html +0 -172
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Sun Aug 20 14:44:20 IDT 2006</td>
59
+ <td>Sun Aug 27 10:06:32 IDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -25,6 +25,8 @@
25
25
  <a href="classes/Daemon/Cluster.html">Daemon::Cluster</a><br />
26
26
  <a href="classes/Daemon/Cluster/PidFile.html">Daemon::Cluster::PidFile</a><br />
27
27
  <a href="classes/Daemon/PidFile.html">Daemon::PidFile</a><br />
28
+ <a href="classes/Object.html">Object</a><br />
29
+ <a href="classes/Proc.html">Proc</a><br />
28
30
  <a href="classes/ServerSide.html">ServerSide</a><br />
29
31
  <a href="classes/ServerSide/Application.html">ServerSide::Application</a><br />
30
32
  <a href="classes/ServerSide/Application/Base.html">ServerSide::Application::Base</a><br />
@@ -32,7 +34,7 @@
32
34
  <a href="classes/ServerSide/Connection.html">ServerSide::Connection</a><br />
33
35
  <a href="classes/ServerSide/Connection/Base.html">ServerSide::Connection::Base</a><br />
34
36
  <a href="classes/ServerSide/Connection/Const.html">ServerSide::Connection::Const</a><br />
35
- <a href="classes/ServerSide/Connection/Static.html">ServerSide::Connection::Static</a><br />
37
+ <a href="classes/ServerSide/Connection/Router.html">ServerSide::Connection::Router</a><br />
36
38
  <a href="classes/ServerSide/Server.html">ServerSide::Server</a><br />
37
39
  <a href="classes/ServerSide/StaticFiles.html">ServerSide::StaticFiles</a><br />
38
40
  <a href="classes/ServerSide/StaticFiles/Const.html">ServerSide::StaticFiles::Const</a><br />
@@ -29,6 +29,7 @@
29
29
  <a href="files/lib/serverside/connection_rb.html">lib/serverside/connection.rb</a><br />
30
30
  <a href="files/lib/serverside/core_ext_rb.html">lib/serverside/core_ext.rb</a><br />
31
31
  <a href="files/lib/serverside/daemon_rb.html">lib/serverside/daemon.rb</a><br />
32
+ <a href="files/lib/serverside/routing_rb.html">lib/serverside/routing.rb</a><br />
32
33
  <a href="files/lib/serverside/server_rb.html">lib/serverside/server.rb</a><br />
33
34
  <a href="files/lib/serverside/static_rb.html">lib/serverside/static.rb</a><br />
34
35
  </div>
@@ -20,40 +20,56 @@
20
20
  <div id="index">
21
21
  <h1 class="section-bar">Methods</h1>
22
22
  <div id="index-entries">
23
- <a href="classes/String.html#M000004">/ (String)</a><br />
24
- <a href="classes/ServerSide/Application/Base.html#M000024">configuration (ServerSide::Application::Base)</a><br />
25
- <a href="classes/ServerSide/Application/Base.html#M000023">configure (ServerSide::Application::Base)</a><br />
26
- <a href="classes/Daemon.html#M000005">control (Daemon)</a><br />
27
- <a href="classes/Daemon/Cluster.html#M000014">daemon_loop (Daemon::Cluster)</a><br />
28
- <a href="classes/ServerSide/Application.html#M000022">daemonize (ServerSide::Application)</a><br />
29
- <a href="classes/ServerSide/Application/Static.html#M000026">daemonize (ServerSide::Application::Static)</a><br />
30
- <a href="classes/Daemon/Cluster/PidFile.html#M000017">delete (Daemon::Cluster::PidFile)</a><br />
31
- <a href="classes/Daemon/Cluster.html#M000011">fork_server (Daemon::Cluster)</a><br />
32
- <a href="classes/ServerSide/Connection/Base.html#M000027">new (ServerSide::Connection::Base)</a><br />
33
- <a href="classes/ServerSide/Application/Base.html#M000025">new (ServerSide::Application::Base)</a><br />
34
- <a href="classes/ServerSide/Server.html#M000034">new (ServerSide::Server)</a><br />
35
- <a href="classes/ServerSide/Connection/Base.html#M000030">parse_parameters (ServerSide::Connection::Base)</a><br />
36
- <a href="classes/ServerSide/Connection/Base.html#M000029">parse_request (ServerSide::Connection::Base)</a><br />
37
- <a href="classes/Daemon/Base.html#M000010">pid_fn (Daemon::Base)</a><br />
38
- <a href="classes/ServerSide/Connection/Base.html#M000028">process (ServerSide::Connection::Base)</a><br />
39
- <a href="classes/Daemon/PidFile.html#M000009">recall (Daemon::PidFile)</a><br />
40
- <a href="classes/Daemon/Cluster/PidFile.html#M000019">recall_pids (Daemon::Cluster::PidFile)</a><br />
41
- <a href="classes/ServerSide/Connection/Static.html#M000033">respond (ServerSide::Connection::Static)</a><br />
42
- <a href="classes/ServerSide/Connection/Base.html#M000031">send_response (ServerSide::Connection::Base)</a><br />
43
- <a href="classes/ServerSide/StaticFiles.html#M000021">serve_dir (ServerSide::StaticFiles)</a><br />
44
- <a href="classes/ServerSide/StaticFiles.html#M000020">serve_file (ServerSide::StaticFiles)</a><br />
45
- <a href="classes/Daemon.html#M000006">start (Daemon)</a><br />
46
- <a href="classes/Daemon/Cluster.html#M000015">start (Daemon::Cluster)</a><br />
47
- <a href="classes/Daemon/Cluster.html#M000012">start_servers (Daemon::Cluster)</a><br />
48
- <a href="classes/Daemon.html#M000007">stop (Daemon)</a><br />
49
- <a href="classes/Daemon/Cluster.html#M000016">stop (Daemon::Cluster)</a><br />
50
- <a href="classes/Daemon/Cluster.html#M000013">stop_servers (Daemon::Cluster)</a><br />
51
- <a href="classes/Daemon/PidFile.html#M000008">store (Daemon::PidFile)</a><br />
52
- <a href="classes/Daemon/Cluster/PidFile.html#M000018">store_pid (Daemon::Cluster::PidFile)</a><br />
53
- <a href="classes/ServerSide/Connection/Base.html#M000032">stream (ServerSide::Connection::Base)</a><br />
23
+ <a href="classes/String.html#M000006">/ (String)</a><br />
24
+ <a href="classes/ServerSide/Connection/Router.html#M000046">cache_constant (ServerSide::Connection::Router)</a><br />
25
+ <a href="classes/ServerSide/Connection/Router.html#M000042">compile_rules (ServerSide::Connection::Router)</a><br />
26
+ <a href="classes/ServerSide/Connection/Router.html#M000044">condition_part (ServerSide::Connection::Router)</a><br />
27
+ <a href="classes/ServerSide/Application.html#M000027">config= (ServerSide::Application)</a><br />
28
+ <a href="classes/ServerSide/Application/Base.html#M000030">configuration (ServerSide::Application::Base)</a><br />
29
+ <a href="classes/ServerSide/Application/Base.html#M000029">configure (ServerSide::Application::Base)</a><br />
30
+ <a href="classes/Object.html#M000003">const_tag (Object)</a><br />
31
+ <a href="classes/Daemon.html#M000007">control (Daemon)</a><br />
32
+ <a href="classes/Daemon/Cluster.html#M000016">daemon_loop (Daemon::Cluster)</a><br />
33
+ <a href="classes/ServerSide/Application/Static.html#M000032">daemonize (ServerSide::Application::Static)</a><br />
34
+ <a href="classes/ServerSide/Application.html#M000028">daemonize (ServerSide::Application)</a><br />
35
+ <a href="classes/ServerSide/Connection/Router.html#M000049">default_handler (ServerSide::Connection::Router)</a><br />
36
+ <a href="classes/ServerSide/Connection/Router.html#M000045">define_proc (ServerSide::Connection::Router)</a><br />
37
+ <a href="classes/Daemon/Cluster/PidFile.html#M000019">delete (Daemon::Cluster::PidFile)</a><br />
38
+ <a href="classes/Daemon/Cluster.html#M000013">fork_server (Daemon::Cluster)</a><br />
39
+ <a href="classes/ServerSide/Connection/Router.html#M000040">has_routes? (ServerSide::Connection::Router)</a><br />
40
+ <a href="classes/ServerSide/Server.html#M000050">new (ServerSide::Server)</a><br />
41
+ <a href="classes/ServerSide/Application/Base.html#M000031">new (ServerSide::Application::Base)</a><br />
42
+ <a href="classes/ServerSide/Connection/Base.html#M000033">new (ServerSide::Connection::Base)</a><br />
43
+ <a href="classes/ServerSide/Connection/Base.html#M000036">parse_parameters (ServerSide::Connection::Base)</a><br />
44
+ <a href="classes/ServerSide/Connection/Base.html#M000035">parse_request (ServerSide::Connection::Base)</a><br />
45
+ <a href="classes/Daemon/Base.html#M000012">pid_fn (Daemon::Base)</a><br />
46
+ <a href="classes/Proc.html#M000002">proc_tag (Proc)</a><br />
47
+ <a href="classes/ServerSide/Connection/Base.html#M000034">process (ServerSide::Connection::Base)</a><br />
48
+ <a href="classes/Daemon/PidFile.html#M000011">recall (Daemon::PidFile)</a><br />
49
+ <a href="classes/Daemon/Cluster/PidFile.html#M000021">recall_pids (Daemon::Cluster::PidFile)</a><br />
50
+ <a href="classes/ServerSide/Connection/Base.html#M000038">redirect (ServerSide::Connection::Base)</a><br />
51
+ <a href="classes/ServerSide/Connection/Router.html#M000041">route (ServerSide::Connection::Router)</a><br />
52
+ <a href="classes/ServerSide.html#M000022">route (ServerSide)</a><br />
53
+ <a href="classes/ServerSide.html#M000023">route_default (ServerSide)</a><br />
54
+ <a href="classes/ServerSide/Connection/Router.html#M000047">route_default (ServerSide::Connection::Router)</a><br />
55
+ <a href="classes/ServerSide/Connection/Router.html#M000043">rule_to_statement (ServerSide::Connection::Router)</a><br />
56
+ <a href="classes/ServerSide/Connection/Base.html#M000037">send_response (ServerSide::Connection::Base)</a><br />
57
+ <a href="classes/ServerSide/StaticFiles.html#M000025">serve_dir (ServerSide::StaticFiles)</a><br />
58
+ <a href="classes/ServerSide/StaticFiles.html#M000024">serve_file (ServerSide::StaticFiles)</a><br />
59
+ <a href="classes/ServerSide/StaticFiles.html#M000026">serve_static (ServerSide::StaticFiles)</a><br />
60
+ <a href="classes/Daemon/Cluster.html#M000017">start (Daemon::Cluster)</a><br />
61
+ <a href="classes/Daemon.html#M000008">start (Daemon)</a><br />
62
+ <a href="classes/Daemon/Cluster.html#M000014">start_servers (Daemon::Cluster)</a><br />
63
+ <a href="classes/Daemon/Cluster.html#M000018">stop (Daemon::Cluster)</a><br />
64
+ <a href="classes/Daemon.html#M000009">stop (Daemon)</a><br />
65
+ <a href="classes/Daemon/Cluster.html#M000015">stop_servers (Daemon::Cluster)</a><br />
66
+ <a href="classes/Daemon/PidFile.html#M000010">store (Daemon::PidFile)</a><br />
67
+ <a href="classes/Daemon/Cluster/PidFile.html#M000020">store_pid (Daemon::Cluster::PidFile)</a><br />
68
+ <a href="classes/ServerSide/Connection/Base.html#M000039">stream (ServerSide::Connection::Base)</a><br />
54
69
  <a href="classes/Symbol.html#M000001">to_s (Symbol)</a><br />
55
- <a href="classes/String.html#M000002">uri_escape (String)</a><br />
56
- <a href="classes/String.html#M000003">uri_unescape (String)</a><br />
70
+ <a href="classes/ServerSide/Connection/Router.html#M000048">unhandled (ServerSide::Connection::Router)</a><br />
71
+ <a href="classes/String.html#M000004">uri_escape (String)</a><br />
72
+ <a href="classes/String.html#M000005">uri_unescape (String)</a><br />
57
73
  </div>
58
74
  </div>
59
75
  </body>
@@ -4,6 +4,23 @@ require File.join(File.dirname(__FILE__), 'connection')
4
4
 
5
5
  module ServerSide
6
6
  module Application
7
+ @@config = nil
8
+
9
+ def self.config=(c)
10
+ @@config = c
11
+ end
12
+
13
+ def self.daemonize(config, cmd)
14
+ config = @@config.merge(config) if @@config
15
+ daemon_class = Class.new(Daemon::Cluster) do
16
+ meta_def(:pid_fn) {Daemon::WorkingDirectory/'serverside.pid'}
17
+ meta_def(:server_loop) do |port|
18
+ ServerSide::Server.new(config[:host], port, ServerSide::Connection::Router)
19
+ end
20
+ meta_def(:ports) {config[:ports]}
21
+ end
22
+ Daemon.control(daemon_class, cmd)
23
+ end
7
24
 
8
25
  class Base < ServerSide::Connection::Base
9
26
  def self.configure(options)
@@ -32,9 +49,5 @@ module ServerSide
32
49
  Daemon.control(daemon_class, cmd)
33
50
  end
34
51
  end
35
-
36
- def self.daemonize
37
-
38
- end
39
52
  end
40
53
  end
@@ -1,3 +1,5 @@
1
+ require File.join(File.dirname(__FILE__), 'static')
2
+
1
3
  module ServerSide
2
4
  # The Connection module takes care of HTTP connection. While most HTTP servers
3
5
  # (at least the OO ones) will define separate classes for request and
@@ -10,7 +12,9 @@ module ServerSide
10
12
  module Const
11
13
  LineBreak = "\r\n".freeze
12
14
  # Here's a nice one - parses the first line of a request.
13
- RequestRegexp = /([A-Za-z0-9]+)\s(\/[^\/\?]*(\/[^\/\?]+)*)\/?(\?(.*))?\sHTTP\/(.+)\r/.freeze
15
+ # The expected format is as follows:
16
+ # <method> </path>[/][?<query>] HTTP/<version>
17
+ RequestRegexp = /([A-Za-z0-9]+)\s(\/[^\/\?]*(?:\/[^\/\?]+)*)\/?(?:\?(.*))?\sHTTP\/(.+)\r/.freeze
14
18
  # Regexp for parsing headers.
15
19
  HeaderRegexp = /([^:]+):\s?(.*)\r\n/.freeze
16
20
  ContentLength = 'Content-Length'.freeze
@@ -24,9 +28,11 @@ module ServerSide
24
28
  StatusClose = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%sContent-Length: %d\r\n\r\n".freeze
25
29
  StatusStream = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s\r\n".freeze
26
30
  StatusPersist = "HTTP/1.1 %d\r\nContent-Type: %s\r\n%sContent-Length: %d\r\n\r\n".freeze
31
+ StatusRedirect = "HTTP/1.1 %d\r\nConnection: close\r\nLocation: %s\r\n\r\n".freeze
27
32
  Header = "%s: %s\r\n".freeze
28
33
  Empty = ''.freeze
29
34
  Slash = '/'.freeze
35
+ Location = 'Location'
30
36
  end
31
37
 
32
38
  # This is the base request class. When a new request is created, it starts
@@ -35,6 +41,8 @@ module ServerSide
35
41
  # Connection::Base is overriden by applications to create
36
42
  # application-specific behavior.
37
43
  class Base
44
+ include StaticFiles
45
+
38
46
  # Initializes the request instance. A new thread is created for
39
47
  # processing requests.
40
48
  def initialize(conn)
@@ -51,7 +59,11 @@ module ServerSide
51
59
  respond
52
60
  break unless @persistent
53
61
  end
54
- rescue
62
+ rescue => e
63
+ puts '*******************'
64
+ puts e.message
65
+ puts e.backtrace.first
66
+ puts '*******************'
55
67
  # We don't care. Just close the connection.
56
68
  ensure
57
69
  @conn.close
@@ -63,7 +75,7 @@ module ServerSide
63
75
  # 'Connection' header).
64
76
  def parse_request
65
77
  return nil unless @conn.gets =~ Const::RequestRegexp
66
- @method, @path, @query, @version = $1.downcase.to_sym, $2, $5, $6
78
+ @method, @path, @query, @version = $1.downcase.to_sym, $2, $3, $4
67
79
  @parameters = @query ? parse_parameters(@query) : {}
68
80
  @headers = {}
69
81
  while (line = @conn.gets)
@@ -106,6 +118,14 @@ module ServerSide
106
118
  @persistent = false
107
119
  end
108
120
 
121
+ # Send a redirect response.
122
+ def redirect(location, permanent = false)
123
+ @conn << (Const::StatusRedirect % [permanent ? 301 : 302, location])
124
+ rescue
125
+ ensure
126
+ @persistent = false
127
+ end
128
+
109
129
  # Streams additional data to the client.
110
130
  def stream(body)
111
131
  (@conn << body if body) rescue (@persistent = false)
@@ -24,4 +24,19 @@ class Symbol
24
24
  def to_s
25
25
  @_to_s || (@_to_s = id2name)
26
26
  end
27
- end
27
+ end
28
+
29
+ class Proc
30
+ # Returns a unique proc tag. This method is used by the router.
31
+ def proc_tag
32
+ 'proc_' + object_id.to_s(36).sub('-', '_')
33
+ end
34
+ end
35
+
36
+ class Object
37
+ # Returns a unique tag for the object. This method is used by the router.
38
+ def const_tag
39
+ 'C' + object_id.to_s(36).upcase.sub('-', '_')
40
+ end
41
+ end
42
+
@@ -0,0 +1,119 @@
1
+ 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 containing keys corresponding to patterns.
6
+ class Router < Base
7
+ # Returns true if routes were defined.
8
+ def self.has_routes?
9
+ @@rules && !@@rules.empty? rescue false
10
+ end
11
+
12
+ # Adds a routing rule. The normalized rule is a hash containing keys (acting
13
+ # as instance variable names) with patterns as values. If the rule is not a
14
+ # hash, it is normalized into a pattern checked against the request path.
15
+ # Pattern values can also be arrays, in any array member is checked as a pattern.
16
+ def self.route(rule, &block)
17
+ @@rules ||= []
18
+ rule = {:path => rule} unless (Hash === rule) || (Proc === rule)
19
+ @@rules.unshift [rule, block]
20
+ compile_rules
21
+ end
22
+
23
+ # Compiles all rules into a respond method that is invoked when a request
24
+ # is received.
25
+ def self.compile_rules
26
+ @@rules ||= []
27
+ code = @@rules.inject('lambda {') {|m, r| m << rule_to_statement(r[0], r[1])}
28
+ code << 'default_handler}'
29
+ define_method(:respond, &eval(code))
30
+ end
31
+
32
+ # Converts a rule into an if statement. All keys in the rule are matched
33
+ # against their respective values.
34
+ def self.rule_to_statement(rule, block)
35
+ proc_tag = define_proc(&block)
36
+ if Proc === rule
37
+ cond = define_proc(&rule).to_s
38
+ else
39
+ cond = rule.to_a.map {|kv|
40
+ if Array === kv[1]
41
+ '(' + kv[1].map {|v| condition_part(kv[0], v)}.join('||') + ')'
42
+ else
43
+ condition_part(kv[0], kv[1])
44
+ end
45
+ }.join('&&')
46
+ end
47
+ "return #{proc_tag} if #{cond}\n"
48
+ end
49
+
50
+ # Pattern for finding parameters inside patterns. Parameters are parts of the
51
+ # pattern, which the routing pre-processor turns into sub-regexp that are
52
+ # used to extract parameter values from the pattern.
53
+ #
54
+ # For example, matching '/controller/show' against '/controller/:action' will
55
+ # give us @parameters[:action] #=> "show"
56
+ ParamRegexp = /(?::([a-z]+))/
57
+
58
+ # Returns the condition part for the key and value specified. The key is the
59
+ # name of an instance variable and the value is a pattern to match against.
60
+ # If the pattern contains parameters (for example, /controller/:action,) the
61
+ # method creates a lambda for extracting the parameter values.
62
+ def self.condition_part(key, value)
63
+ p_parse, p_count = '', 0
64
+ while (String === value) && (value =~ ParamRegexp)
65
+ value = value.dup
66
+ p_name = $1
67
+ p_count += 1
68
+ value.sub!(ParamRegexp, '(.+)')
69
+ p_parse << "@parameters[:#{p_name}] = $#{p_count}\n"
70
+ end
71
+ cond = "(@#{key} =~ #{cache_constant(Regexp.new(value))})"
72
+ if p_count == 0
73
+ cond
74
+ else
75
+ tag = define_proc(&eval(
76
+ "lambda {if #{cond}\n#{p_parse}true\nelse\nfalse\nend}"))
77
+ "(#{tag})"
78
+ end
79
+ end
80
+
81
+ # Converts a proc into a method, returning the method's name (as a symbol)
82
+ def self.define_proc(&block)
83
+ tag = block.proc_tag
84
+ define_method(tag.to_sym, &block) unless instance_methods.include?(tag)
85
+ tag.to_sym
86
+ end
87
+
88
+ # Converts a value into a local constant and freezes it. Returns the
89
+ # constant's tag name
90
+ def self.cache_constant(value)
91
+ tag = value.const_tag
92
+ class_eval "#{tag} = #{value.inspect}.freeze" rescue nil
93
+ tag
94
+ end
95
+
96
+ # Sets the default handler for incoming requests.
97
+ def self.route_default(&block)
98
+ define_method(:default_handler, &block)
99
+ compile_rules
100
+ end
101
+
102
+ def unhandled
103
+ send_response(403, 'text', 'No handler found.')
104
+ end
105
+
106
+ alias_method :default_handler, :unhandled
107
+ end
108
+ end
109
+
110
+ # Adds a routing rule. This is a convenience method.
111
+ def self.route(rule, &block)
112
+ Connection::Router.route(rule, &block)
113
+ end
114
+
115
+ # Sets the default request handler. This is a convenience method.
116
+ def self.route_default(&block)
117
+ Connection::Router.route_default(&block)
118
+ end
119
+ end
@@ -17,6 +17,7 @@ module ServerSide
17
17
  DirListingStart = '<html><head><title>Directory Listing for %s</title></head><body><h2>Directory listing for %s:</h2>'.freeze
18
18
  DirListing = '<a href="%s">%s</a><br/>'.freeze
19
19
  DirListingStop = '</body></html>'.freeze
20
+ FileNotFound = 'File not found.'.freeze
20
21
  end
21
22
 
22
23
  @@mime_types = Hash.new {|h, k| ServerSide::StaticFiles::Const::TextPlain}
@@ -66,31 +67,16 @@ module ServerSide
66
67
  } + Const::DirListingStop
67
68
  send_response(200, 'text/html', html)
68
69
  end
69
- end
70
-
71
- module Connection
72
- module Const
73
- WD = '.'.freeze
74
- FileNotFound = "Couldn't open file %s.".freeze
75
- end
76
70
 
77
- # A connection type for serving static files.
78
- class Static < Base
79
- include StaticFiles
80
-
81
- # Responds with a file's content or a directory listing. If the path
82
- # does not exist, a 404 response is rendered.
83
- def respond
84
- fn = './%s' % @path
85
- if File.file?(fn)
86
- serve_file(fn)
87
- elsif File.directory?(fn)
88
- serve_dir(fn)
89
- else
90
- send_response(404, 'text', Const::FileNotFound % @path)
91
- end
71
+ # Serves static files and directory listings.
72
+ def serve_static(path)
73
+ if File.file?(path)
74
+ serve_file(path)
75
+ elsif File.directory?(path)
76
+ serve_dir(path)
77
+ else
78
+ send_response(404, 'text', Const::FileNotFound)
92
79
  end
93
80
  end
94
81
  end
95
82
  end
96
-
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'fileutils'
3
+
4
+ FileUtils.cd(File.dirname(__FILE__))
5
+
6
+ trap('TERM') {exit}
7
+
8
+ ServerSide::route(:path => '^/static/:path') {serve_static('.'/@parameters[:path])}
9
+ ServerSide::route(:path => '/hello$') {send_response(200, 'text', 'Hello world!')}
10
+ ServerSide.route(:path => '/xml/:flavor/feed.xml') do
11
+ redirect('http://feeds.feedburner.com/RobbyOnRails')
12
+ end
13
+
14
+ ServerSide::Server.new('0.0.0.0', 8000, ServerSide::Connection::Router)
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'net/http'
3
+
4
+ class StaticServerTest < Test::Unit::TestCase
5
+ def test_basic
6
+ ServerSide::route(:path => '^/static/:path') {serve_static('.'/@parameters[:path])}
7
+ ServerSide::route(:path => '/hello$') {send_response(200, 'text', 'Hello world!')}
8
+ ServerSide.route(:path => '/xml/:flavor/feed.xml') do
9
+ redirect('http://feeds.feedburner.com/RobbyOnRails')
10
+ end
11
+
12
+ t = Thread.new {ServerSide::Server.new('0.0.0.0', 17654, ServerSide::Connection::Router)}
13
+ sleep 0.1
14
+
15
+ h = Net::HTTP.new('localhost', 17654)
16
+ resp, data = h.get('/hello', nil)
17
+ assert_equal 200, resp.code.to_i
18
+ assert_equal "Hello world!", data
19
+
20
+ h = Net::HTTP.new('localhost', 17654)
21
+ resp, data = h.get('/static/qqq.zzz', nil)
22
+ assert_equal 404, resp.code.to_i
23
+ assert_equal "File not found.", data
24
+
25
+ h = Net::HTTP.new('localhost', 17654)
26
+ resp, data = h.get("/static/#{__FILE__}", nil)
27
+ assert_equal 200, resp.code.to_i
28
+ assert_equal IO.read(__FILE__), data
29
+ assert_equal 'text/plain', resp['Content-Type']
30
+ # Net::HTTP includes this header in the request, so our server returns
31
+ # likewise.
32
+ assert_equal 'close', resp['Connection']
33
+
34
+ h = Net::HTTP.new('localhost', 17654)
35
+ resp, data = h.get('/xml/rss/feed.xml', nil)
36
+ assert_equal 302, resp.code.to_i
37
+ assert_equal 'http://feeds.feedburner.com/RobbyOnRails', resp['Location']
38
+
39
+ t.exit
40
+ end
41
+ end