serverside 0.2.5 → 0.2.6

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