serverside 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/CHANGELOG +10 -0
  2. data/README +3 -3
  3. data/Rakefile +4 -4
  4. data/bin/serverside +4 -4
  5. data/doc/rdoc/classes/Daemon.html +4 -4
  6. data/doc/rdoc/classes/ServerSide.html +14 -74
  7. data/doc/rdoc/classes/ServerSide/Application.html +13 -13
  8. data/doc/rdoc/classes/ServerSide/{Connection.html → HTTP.html} +12 -18
  9. data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +200 -0
  10. data/doc/rdoc/classes/ServerSide/{Connection → HTTP}/Const.html +6 -6
  11. data/doc/rdoc/classes/ServerSide/{Connection/Base.html → HTTP/Request.html} +213 -164
  12. data/doc/rdoc/classes/ServerSide/{Server.html → HTTP/Server.html} +22 -22
  13. data/doc/rdoc/classes/ServerSide/Router.html +496 -0
  14. data/doc/rdoc/classes/ServerSide/StaticFiles.html +31 -30
  15. data/doc/rdoc/classes/ServerSide/Template.html +12 -12
  16. data/doc/rdoc/created.rid +1 -1
  17. data/doc/rdoc/files/CHANGELOG.html +22 -4
  18. data/doc/rdoc/files/COPYING.html +1 -1
  19. data/doc/rdoc/files/README.html +4 -4
  20. data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
  21. data/doc/rdoc/files/lib/serverside/cluster_rb.html +1 -1
  22. data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
  23. data/doc/rdoc/files/lib/serverside/core_ext_rb.html +1 -1
  24. data/doc/rdoc/files/lib/serverside/daemon_rb.html +1 -1
  25. data/doc/rdoc/files/lib/serverside/request_rb.html +101 -0
  26. data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
  27. data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
  28. data/doc/rdoc/files/lib/serverside/static_rb.html +1 -1
  29. data/doc/rdoc/files/lib/serverside/template_rb.html +1 -1
  30. data/doc/rdoc/files/lib/serverside_rb.html +1 -1
  31. data/doc/rdoc/fr_class_index.html +6 -5
  32. data/doc/rdoc/fr_file_index.html +1 -0
  33. data/doc/rdoc/fr_method_index.html +31 -31
  34. data/lib/serverside/application.rb +1 -1
  35. data/lib/serverside/connection.rb +12 -141
  36. data/lib/serverside/request.rb +150 -0
  37. data/lib/serverside/routing.rb +117 -126
  38. data/lib/serverside/server.rb +13 -12
  39. data/lib/serverside/static.rb +1 -1
  40. data/test/functional/primitive_static_server_test.rb +3 -3
  41. data/test/functional/routing_server.rb +1 -1
  42. data/test/functional/routing_server_test.rb +1 -1
  43. data/test/functional/static_profile.rb +1 -1
  44. data/test/functional/static_server_test.rb +2 -2
  45. data/test/unit/connection_test.rb +30 -224
  46. data/test/unit/request_test.rb +248 -0
  47. data/test/unit/routing_test.rb +4 -4
  48. data/test/unit/server_test.rb +10 -8
  49. data/test/unit/static_test.rb +1 -1
  50. metadata +61 -56
  51. data/doc/rdoc/classes/ServerSide/Connection/Router.html +0 -493
@@ -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 Sep 03 09:49:09 IDT 2006</td>
59
+ <td>Sat Sep 30 23:04:12 IDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -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 Sep 03 11:36:19 IDT 2006</td>
59
+ <td>Sat Sep 30 22:55:21 IDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -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 Sep 03 11:33:15 IDT 2006</td>
59
+ <td>Mon Sep 04 18:10:56 IDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Mon Aug 21 10:57:08 IDT 2006</td>
59
+ <td>Thu Aug 24 18:47:25 IDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -29,11 +29,12 @@
29
29
  <a href="classes/Proc.html">Proc</a><br />
30
30
  <a href="classes/ServerSide.html">ServerSide</a><br />
31
31
  <a href="classes/ServerSide/Application.html">ServerSide::Application</a><br />
32
- <a href="classes/ServerSide/Connection.html">ServerSide::Connection</a><br />
33
- <a href="classes/ServerSide/Connection/Base.html">ServerSide::Connection::Base</a><br />
34
- <a href="classes/ServerSide/Connection/Const.html">ServerSide::Connection::Const</a><br />
35
- <a href="classes/ServerSide/Connection/Router.html">ServerSide::Connection::Router</a><br />
36
- <a href="classes/ServerSide/Server.html">ServerSide::Server</a><br />
32
+ <a href="classes/ServerSide/HTTP.html">ServerSide::HTTP</a><br />
33
+ <a href="classes/ServerSide/HTTP/Connection.html">ServerSide::HTTP::Connection</a><br />
34
+ <a href="classes/ServerSide/HTTP/Const.html">ServerSide::HTTP::Const</a><br />
35
+ <a href="classes/ServerSide/HTTP/Request.html">ServerSide::HTTP::Request</a><br />
36
+ <a href="classes/ServerSide/HTTP/Server.html">ServerSide::HTTP::Server</a><br />
37
+ <a href="classes/ServerSide/Router.html">ServerSide::Router</a><br />
37
38
  <a href="classes/ServerSide/StaticFiles.html">ServerSide::StaticFiles</a><br />
38
39
  <a href="classes/ServerSide/StaticFiles/Const.html">ServerSide::StaticFiles::Const</a><br />
39
40
  <a href="classes/ServerSide/Template.html">ServerSide::Template</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/request_rb.html">lib/serverside/request.rb</a><br />
32
33
  <a href="files/lib/serverside/routing_rb.html">lib/serverside/routing.rb</a><br />
33
34
  <a href="files/lib/serverside/server_rb.html">lib/serverside/server.rb</a><br />
34
35
  <a href="files/lib/serverside/static_rb.html">lib/serverside/static.rb</a><br />
@@ -21,44 +21,44 @@
21
21
  <h1 class="section-bar">Methods</h1>
22
22
  <div id="index-entries">
23
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#M000028">config= (ServerSide::Application)</a><br />
24
+ <a href="classes/ServerSide/Router.html#M000049">cache_constant (ServerSide::Router)</a><br />
25
+ <a href="classes/ServerSide/Router.html#M000045">compile_rules (ServerSide::Router)</a><br />
26
+ <a href="classes/ServerSide/Router.html#M000047">condition_part (ServerSide::Router)</a><br />
27
+ <a href="classes/ServerSide/Application.html#M000026">config= (ServerSide::Application)</a><br />
28
28
  <a href="classes/Object.html#M000003">const_tag (Object)</a><br />
29
29
  <a href="classes/Daemon.html#M000007">control (Daemon)</a><br />
30
30
  <a href="classes/Daemon/Cluster.html#M000016">daemon_loop (Daemon::Cluster)</a><br />
31
- <a href="classes/ServerSide/Application.html#M000029">daemonize (ServerSide::Application)</a><br />
32
- <a href="classes/ServerSide/Connection/Router.html#M000049">default_handler (ServerSide::Connection::Router)</a><br />
33
- <a href="classes/ServerSide/Connection/Router.html#M000045">define_proc (ServerSide::Connection::Router)</a><br />
31
+ <a href="classes/ServerSide/Application.html#M000027">daemonize (ServerSide::Application)</a><br />
32
+ <a href="classes/ServerSide/Router.html#M000052">default_handler (ServerSide::Router)</a><br />
33
+ <a href="classes/ServerSide/Router.html#M000048">define_proc (ServerSide::Router)</a><br />
34
34
  <a href="classes/Daemon/Cluster/PidFile.html#M000019">delete (Daemon::Cluster::PidFile)</a><br />
35
- <a href="classes/ServerSide/Connection/Base.html#M000039">delete_cookie (ServerSide::Connection::Base)</a><br />
35
+ <a href="classes/ServerSide/HTTP/Request.html#M000038">delete_cookie (ServerSide::HTTP::Request)</a><br />
36
36
  <a href="classes/Daemon/Cluster.html#M000013">fork_server (Daemon::Cluster)</a><br />
37
- <a href="classes/ServerSide/Connection/Router.html#M000040">has_routes? (ServerSide::Connection::Router)</a><br />
38
- <a href="classes/ServerSide/Server.html#M000050">new (ServerSide::Server)</a><br />
39
- <a href="classes/ServerSide/Connection/Base.html#M000030">new (ServerSide::Connection::Base)</a><br />
40
- <a href="classes/ServerSide/Connection/Base.html#M000034">parse_cookies (ServerSide::Connection::Base)</a><br />
41
- <a href="classes/ServerSide/Connection/Base.html#M000033">parse_parameters (ServerSide::Connection::Base)</a><br />
42
- <a href="classes/ServerSide/Connection/Base.html#M000032">parse_request (ServerSide::Connection::Base)</a><br />
37
+ <a href="classes/ServerSide/Router.html#M000043">has_routes? (ServerSide::Router)</a><br />
38
+ <a href="classes/ServerSide/HTTP/Request.html#M000029">new (ServerSide::HTTP::Request)</a><br />
39
+ <a href="classes/ServerSide/HTTP/Server.html#M000028">new (ServerSide::HTTP::Server)</a><br />
40
+ <a href="classes/ServerSide/HTTP/Connection.html#M000039">new (ServerSide::HTTP::Connection)</a><br />
41
+ <a href="classes/ServerSide/HTTP/Request.html#M000031">parse (ServerSide::HTTP::Request)</a><br />
42
+ <a href="classes/ServerSide/HTTP/Request.html#M000033">parse_cookies (ServerSide::HTTP::Request)</a><br />
43
+ <a href="classes/ServerSide/HTTP/Request.html#M000032">parse_parameters (ServerSide::HTTP::Request)</a><br />
43
44
  <a href="classes/Daemon/Base.html#M000012">pid_fn (Daemon::Base)</a><br />
44
45
  <a href="classes/Proc.html#M000002">proc_tag (Proc)</a><br />
45
- <a href="classes/ServerSide/Connection/Base.html#M000031">process (ServerSide::Connection::Base)</a><br />
46
+ <a href="classes/ServerSide/HTTP/Request.html#M000030">process (ServerSide::HTTP::Request)</a><br />
47
+ <a href="classes/ServerSide/HTTP/Connection.html#M000040">process (ServerSide::HTTP::Connection)</a><br />
46
48
  <a href="classes/Daemon/PidFile.html#M000011">recall (Daemon::PidFile)</a><br />
47
49
  <a href="classes/Daemon/Cluster/PidFile.html#M000021">recall_pids (Daemon::Cluster::PidFile)</a><br />
48
- <a href="classes/ServerSide/Connection/Base.html#M000036">redirect (ServerSide::Connection::Base)</a><br />
49
- <a href="classes/ServerSide/Template.html#M000052">render (ServerSide::Template)</a><br />
50
- <a href="classes/ServerSide.html#M000022">route (ServerSide)</a><br />
51
- <a href="classes/ServerSide/Connection/Router.html#M000041">route (ServerSide::Connection::Router)</a><br />
52
- <a href="classes/ServerSide.html#M000023">route_default (ServerSide)</a><br />
53
- <a href="classes/ServerSide/Connection/Router.html#M000047">route_default (ServerSide::Connection::Router)</a><br />
54
- <a href="classes/ServerSide/Connection/Router.html#M000043">rule_to_statement (ServerSide::Connection::Router)</a><br />
55
- <a href="classes/ServerSide/Connection/Base.html#M000035">send_response (ServerSide::Connection::Base)</a><br />
56
- <a href="classes/ServerSide/StaticFiles.html#M000025">serve_dir (ServerSide::StaticFiles)</a><br />
57
- <a href="classes/ServerSide/StaticFiles.html#M000024">serve_file (ServerSide::StaticFiles)</a><br />
58
- <a href="classes/ServerSide/StaticFiles.html#M000027">serve_static (ServerSide::StaticFiles)</a><br />
59
- <a href="classes/ServerSide/StaticFiles.html#M000026">serve_template (ServerSide::StaticFiles)</a><br />
60
- <a href="classes/ServerSide/Template.html#M000051">set (ServerSide::Template)</a><br />
61
- <a href="classes/ServerSide/Connection/Base.html#M000038">set_cookie (ServerSide::Connection::Base)</a><br />
50
+ <a href="classes/ServerSide/HTTP/Request.html#M000035">redirect (ServerSide::HTTP::Request)</a><br />
51
+ <a href="classes/ServerSide/Template.html#M000042">render (ServerSide::Template)</a><br />
52
+ <a href="classes/ServerSide/Router.html#M000044">route (ServerSide::Router)</a><br />
53
+ <a href="classes/ServerSide/Router.html#M000050">route_default (ServerSide::Router)</a><br />
54
+ <a href="classes/ServerSide/Router.html#M000046">rule_to_statement (ServerSide::Router)</a><br />
55
+ <a href="classes/ServerSide/HTTP/Request.html#M000034">send_response (ServerSide::HTTP::Request)</a><br />
56
+ <a href="classes/ServerSide/StaticFiles.html#M000023">serve_dir (ServerSide::StaticFiles)</a><br />
57
+ <a href="classes/ServerSide/StaticFiles.html#M000022">serve_file (ServerSide::StaticFiles)</a><br />
58
+ <a href="classes/ServerSide/StaticFiles.html#M000025">serve_static (ServerSide::StaticFiles)</a><br />
59
+ <a href="classes/ServerSide/StaticFiles.html#M000024">serve_template (ServerSide::StaticFiles)</a><br />
60
+ <a href="classes/ServerSide/Template.html#M000041">set (ServerSide::Template)</a><br />
61
+ <a href="classes/ServerSide/HTTP/Request.html#M000037">set_cookie (ServerSide::HTTP::Request)</a><br />
62
62
  <a href="classes/Daemon.html#M000008">start (Daemon)</a><br />
63
63
  <a href="classes/Daemon/Cluster.html#M000017">start (Daemon::Cluster)</a><br />
64
64
  <a href="classes/Daemon/Cluster.html#M000014">start_servers (Daemon::Cluster)</a><br />
@@ -67,9 +67,9 @@
67
67
  <a href="classes/Daemon/Cluster.html#M000015">stop_servers (Daemon::Cluster)</a><br />
68
68
  <a href="classes/Daemon/PidFile.html#M000010">store (Daemon::PidFile)</a><br />
69
69
  <a href="classes/Daemon/Cluster/PidFile.html#M000020">store_pid (Daemon::Cluster::PidFile)</a><br />
70
- <a href="classes/ServerSide/Connection/Base.html#M000037">stream (ServerSide::Connection::Base)</a><br />
70
+ <a href="classes/ServerSide/HTTP/Request.html#M000036">stream (ServerSide::HTTP::Request)</a><br />
71
71
  <a href="classes/Symbol.html#M000001">to_s (Symbol)</a><br />
72
- <a href="classes/ServerSide/Connection/Router.html#M000048">unhandled (ServerSide::Connection::Router)</a><br />
72
+ <a href="classes/ServerSide/Router.html#M000051">unhandled (ServerSide::Router)</a><br />
73
73
  <a href="classes/String.html#M000004">uri_escape (String)</a><br />
74
74
  <a href="classes/String.html#M000005">uri_unescape (String)</a><br />
75
75
  </div>
@@ -15,7 +15,7 @@ module ServerSide
15
15
  daemon_class = Class.new(Daemon::Cluster) do
16
16
  meta_def(:pid_fn) {Daemon::WorkingDirectory/'serverside.pid'}
17
17
  meta_def(:server_loop) do |port|
18
- ServerSide::Server.new(config[:host], port, ServerSide::Connection::Router)
18
+ ServerSide::HTTP::Server.new(config[:host], port, ServerSide::Router)
19
19
  end
20
20
  meta_def(:ports) {config[:ports]}
21
21
  end
@@ -1,161 +1,32 @@
1
1
  require File.join(File.dirname(__FILE__), 'static')
2
2
 
3
3
  module ServerSide
4
- # The Connection module takes care of HTTP connection. While most HTTP servers
5
- # (at least the OO ones) will define separate classes for request and
6
- # response, I chose to use the concept of a connection both for better
7
- # performance, and also because a single connection might handle multiple
8
- # requests, if using HTTP 1.1 persistent connection.
9
- module Connection
10
- # A bunch of frozen constants to make the parsing of requests and rendering
11
- # of responses faster than otherwise.
12
- module Const
13
- LineBreak = "\r\n".freeze
14
- # Here's a nice one - parses the first line of a request.
15
- # The expected format is as follows:
16
- # <method> </path>[/][?<query>] HTTP/<version>
17
- RequestRegexp = /([A-Za-z0-9]+)\s(\/[^\/\?]*(?:\/[^\/\?]+)*)\/?(?:\?(.*))?\sHTTP\/(.+)\r/.freeze
18
- # Regexp for parsing headers.
19
- HeaderRegexp = /([^:]+):\s?(.*)\r\n/.freeze
20
- ContentLength = 'Content-Length'.freeze
21
- Version_1_1 = '1.1'.freeze
22
- Connection = 'Connection'.freeze
23
- Close = 'close'.freeze
24
- Ampersand = '&'.freeze
25
- # Regexp for parsing URI parameters.
26
- ParameterRegexp = /(.+)=(.*)/.freeze
27
- EqualSign = '='.freeze
28
- StatusClose = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
29
- StatusStream = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s%s\r\n".freeze
30
- StatusPersist = "HTTP/1.1 %d\r\nContent-Type: %s\r\n%s%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
32
- Header = "%s: %s\r\n".freeze
33
- EmptyString = ''.freeze
34
- EmptyHash = {}.freeze
35
- Slash = '/'.freeze
36
- Location = 'Location'.freeze
37
- Cookie = 'Cookie'
38
- SetCookie = "Set-Cookie: %s=%s; path=/; expires=%s\r\n".freeze
39
- CookieSplit = /[;,] */n.freeze
40
- CookieRegexp = /\s*(.+)=(.*)\s*/.freeze
41
- CookieExpiredTime = Time.at(0).freeze
42
- end
43
-
44
- # This is the base request class. When a new request is created, it starts
45
- # a thread in which it is parsed and processed.
46
- #
47
- # Connection::Base is overriden by applications to create
48
- # application-specific behavior.
49
- class Base
50
- include StaticFiles
51
-
4
+ module HTTP
5
+ # The Connection class represents HTTP connections. Each connection
6
+ # instance creates a separate thread for execution and processes
7
+ # incoming requests in a loop until the connection is closed by
8
+ # either server or client, thus implementing HTTP 1.1 persistent
9
+ # connections.
10
+ class Connection
52
11
  # Initializes the request instance. A new thread is created for
53
12
  # processing requests.
54
- def initialize(conn)
55
- @conn = conn
13
+ def initialize(conn, request_class)
14
+ @conn, @request_class = conn, request_class
56
15
  @thread = Thread.new {process}
57
16
  end
58
17
 
59
18
  # Processes incoming requests by parsing them and then responding. If
60
- # any error occurs, or the connection is not persistent, the connection is
61
- # closed.
19
+ # any error occurs, or the connection is not persistent, the connection
20
+ # is closed.
62
21
  def process
63
22
  while true
64
- break unless parse_request
65
- respond
66
- break unless @persistent
23
+ break unless @request_class.new(@conn).process
67
24
  end
68
25
  rescue => e
69
26
  # We don't care. Just close the connection.
70
27
  ensure
71
28
  @conn.close
72
29
  end
73
-
74
- # Parses an HTTP request. If the request is not valid, nil is returned.
75
- # Otherwise, the HTTP headers are returned. Also determines whether the
76
- # connection is persistent (by checking the HTTP version and the
77
- # 'Connection' header).
78
- def parse_request
79
- return nil unless @conn.gets =~ Const::RequestRegexp
80
- @method, @path, @query, @version = $1.downcase.to_sym, $2, $3, $4
81
- @parameters = @query ? parse_parameters(@query) : {}
82
- @headers = {}
83
- while (line = @conn.gets)
84
- break if line.nil? || (line == Const::LineBreak)
85
- if line =~ Const::HeaderRegexp
86
- @headers[$1.freeze] = $2.freeze
87
- end
88
- end
89
- @persistent = (@version == Const::Version_1_1) &&
90
- (@headers[Const::Connection] != Const::Close)
91
- @cookies = @headers[Const::Cookie] ? parse_cookies : Const::EmptyHash
92
- @response_cookies = nil
93
-
94
- @headers
95
- end
96
-
97
- # Parses query parameters by splitting the query string and unescaping
98
- # parameter values.
99
- def parse_parameters(query)
100
- query.split(Const::Ampersand).inject({}) do |m, i|
101
- if i =~ Const::ParameterRegexp
102
- m[$1.to_sym] = $2.uri_unescape
103
- end
104
- m
105
- end
106
- end
107
-
108
- # Parses cookie values passed in the request
109
- def parse_cookies
110
- @headers[Const::Cookie].split(Const::CookieSplit).inject({}) do |m, i|
111
- if i =~ Const::CookieRegexp
112
- m[$1.to_sym] = $2.uri_unescape
113
- end
114
- m
115
- end
116
- end
117
-
118
- # Sends an HTTP response.
119
- def send_response(status, content_type, body = nil, content_length = nil,
120
- headers = nil)
121
- h = headers ?
122
- headers.inject('') {|m, kv| m << (Const::Header % kv)} : ''
123
-
124
- content_length = body.length if content_length.nil? && body
125
- @persistent = false if content_length.nil?
126
-
127
- # Select the right format to use according to circumstances.
128
- @conn << ((@persistent ? Const::StatusPersist :
129
- (body ? Const::StatusClose : Const::StatusStream)) %
130
- [status, content_type, h, @response_cookies, content_length])
131
- @conn << body if body
132
- rescue
133
- @persistent = false
134
- end
135
-
136
- # Send a redirect response.
137
- def redirect(location, permanent = false)
138
- @conn << (Const::StatusRedirect % [permanent ? 301 : 302, location])
139
- rescue
140
- ensure
141
- @persistent = false
142
- end
143
-
144
- # Streams additional data to the client.
145
- def stream(body)
146
- (@conn << body if body) rescue (@persistent = false)
147
- end
148
-
149
- # Sets a cookie to be included in the response.
150
- def set_cookie(name, value, expires)
151
- @response_cookies ||= ""
152
- @response_cookies << (Const::SetCookie % [name, value.to_s.uri_escape, expires.rfc2822])
153
- end
154
-
155
- # Marks a cookie as deleted. The cookie is given an expires stamp in the past.
156
- def delete_cookie(name)
157
- set_cookie(name, nil, Const::CookieExpiredTime)
158
- end
159
30
  end
160
31
  end
161
32
  end
@@ -0,0 +1,150 @@
1
+ require File.join(File.dirname(__FILE__), 'static')
2
+
3
+ module ServerSide
4
+ module HTTP
5
+ # A bunch of frozen constants to make the parsing of requests and rendering
6
+ # of responses faster than otherwise.
7
+ module Const
8
+ LineBreak = "\r\n".freeze
9
+ # Here's a nice one - parses the first line of a request.
10
+ # The expected format is as follows:
11
+ # <method> </path>[/][?<query>] HTTP/<version>
12
+ RequestRegexp = /([A-Za-z0-9]+)\s(\/[^\/\?]*(?:\/[^\/\?]+)*)\/?(?:\?(.*))?\sHTTP\/(.+)\r/.freeze
13
+ # Regexp for parsing headers.
14
+ HeaderRegexp = /([^:]+):\s?(.*)\r\n/.freeze
15
+ ContentLength = 'Content-Length'.freeze
16
+ Version_1_1 = '1.1'.freeze
17
+ Connection = 'Connection'.freeze
18
+ Close = 'close'.freeze
19
+ Ampersand = '&'.freeze
20
+ # Regexp for parsing URI parameters.
21
+ ParameterRegexp = /(.+)=(.*)/.freeze
22
+ EqualSign = '='.freeze
23
+ StatusClose = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
24
+ StatusStream = "HTTP/1.1 %d\r\nConnection: close\r\nContent-Type: %s\r\n%s%s\r\n".freeze
25
+ StatusPersist = "HTTP/1.1 %d\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
26
+ StatusRedirect = "HTTP/1.1 %d\r\nConnection: close\r\nLocation: %s\r\n\r\n".freeze
27
+ Header = "%s: %s\r\n".freeze
28
+ EmptyString = ''.freeze
29
+ EmptyHash = {}.freeze
30
+ Slash = '/'.freeze
31
+ Location = 'Location'.freeze
32
+ Cookie = 'Cookie'
33
+ SetCookie = "Set-Cookie: %s=%s; path=/; expires=%s\r\n".freeze
34
+ CookieSplit = /[;,] */n.freeze
35
+ CookieRegexp = /\s*(.+)=(.*)\s*/.freeze
36
+ CookieExpiredTime = Time.at(0).freeze
37
+ end
38
+
39
+ # The HTTPRequest class encapsulates HTTP requests. The request class
40
+ # contains methods for parsing the request and rendering a response.
41
+ # HTTP requests are created by the connection. Descendants of HTTPRequest
42
+ # can be created
43
+ # When a connection is created, it creates new requests in a loop until
44
+ # the connection is closed.
45
+ class Request
46
+ include StaticFiles
47
+
48
+ attr_reader :conn, :method, :path, :query, :version, :parameters,
49
+ :headers, :persistent, :cookies, :response_cookies
50
+
51
+ # Initializes the request instance. Any descendants of HTTP::Request
52
+ # which override the initialize method must receive conn as the
53
+ # single argument, and copy it to @conn.
54
+ def initialize(conn)
55
+ @conn = conn
56
+ end
57
+
58
+ # Processes the request by parsing it and then responding.
59
+ def process
60
+ parse && ((respond || true) && @persistent)
61
+ end
62
+
63
+ # Parses an HTTP request. If the request is not valid, nil is returned.
64
+ # Otherwise, the HTTP headers are returned. Also determines whether the
65
+ # connection is persistent (by checking the HTTP version and the
66
+ # 'Connection' header).
67
+ def parse
68
+ return nil unless @conn.gets =~ Const::RequestRegexp
69
+ @method, @path, @query, @version = $1.downcase.to_sym, $2, $3, $4
70
+ @parameters = @query ? parse_parameters(@query) : {}
71
+ @headers = {}
72
+ while (line = @conn.gets)
73
+ break if line.nil? || (line == Const::LineBreak)
74
+ if line =~ Const::HeaderRegexp
75
+ @headers[$1.freeze] = $2.freeze
76
+ end
77
+ end
78
+ @persistent = (@version == Const::Version_1_1) &&
79
+ (@headers[Const::Connection] != Const::Close)
80
+ @cookies = @headers[Const::Cookie] ? parse_cookies : Const::EmptyHash
81
+ @response_cookies = nil
82
+
83
+ @headers
84
+ end
85
+
86
+ # Parses query parameters by splitting the query string and unescaping
87
+ # parameter values.
88
+ def parse_parameters(query)
89
+ query.split(Const::Ampersand).inject({}) do |m, i|
90
+ if i =~ Const::ParameterRegexp
91
+ m[$1.to_sym] = $2.uri_unescape
92
+ end
93
+ m
94
+ end
95
+ end
96
+
97
+ # Parses cookie values passed in the request
98
+ def parse_cookies
99
+ @headers[Const::Cookie].split(Const::CookieSplit).inject({}) do |m, i|
100
+ if i =~ Const::CookieRegexp
101
+ m[$1.to_sym] = $2.uri_unescape
102
+ end
103
+ m
104
+ end
105
+ end
106
+
107
+ # Sends an HTTP response.
108
+ def send_response(status, content_type, body = nil, content_length = nil,
109
+ headers = nil)
110
+ h = headers ?
111
+ headers.inject('') {|m, kv| m << (Const::Header % kv)} : ''
112
+
113
+ content_length = body.length if content_length.nil? && body
114
+ @persistent = false if content_length.nil?
115
+
116
+ # Select the right format to use according to circumstances.
117
+ @conn << ((@persistent ? Const::StatusPersist :
118
+ (body ? Const::StatusClose : Const::StatusStream)) %
119
+ [status, content_type, h, @response_cookies, content_length])
120
+ @conn << body if body
121
+ rescue
122
+ @persistent = false
123
+ end
124
+
125
+ # Send a redirect response.
126
+ def redirect(location, permanent = false)
127
+ @conn << (Const::StatusRedirect % [permanent ? 301 : 302, location])
128
+ rescue
129
+ ensure
130
+ @persistent = false
131
+ end
132
+
133
+ # Streams additional data to the client.
134
+ def stream(body)
135
+ (@conn << body if body) rescue (@persistent = false)
136
+ end
137
+
138
+ # Sets a cookie to be included in the response.
139
+ def set_cookie(name, value, expires)
140
+ @response_cookies ||= ""
141
+ @response_cookies << (Const::SetCookie % [name, value.to_s.uri_escape, expires.rfc2822])
142
+ end
143
+
144
+ # Marks a cookie as deleted. The cookie is given an expires stamp in the past.
145
+ def delete_cookie(name)
146
+ set_cookie(name, nil, Const::CookieExpiredTime)
147
+ end
148
+ end
149
+ end
150
+ end