serverside 0.1.59 → 0.2.0

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 (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